cosmicrose

cosmicrose

I’m having trouble implementing a parser. Either it enters an infinite recursive loop, or it only parses the first part of the input and drops the rest.

For context, I’m writing a parser for a query language, and it contains the ability to nest boolean expressions. Here’s a stripped-down version of what I’m working with:

defmodule CESQL.Parsec do
  import NimbleParsec

  value_identifier = ascii_string([?a..?z], min: 1, max: 20)

  boolean_operation =
    parsec(:expr)
    |> ignore(string(" "))
    |> choice([
      string("AND"),
      string("OR"),
      string("XOR")
    ])
    |> ignore(string(" "))
    |> parsec(:expr)

  expr =
    choice([
      value_identifier,
      boolean_operation
    ])

  defparsec :expr, expr
end

My end goal is to parse a boolean expression, and I intend to allow for complex nested expressions like (a AND b) OR c. But for starters, this is what I want out of the above code:

iex> CESQL.Parsec.expr("a AND b")
{:ok, ["a", "AND", "b"], "", _, _, _}

However, when I place value_identifier first in expr’s argument to choice/2, the parser takes the first value and drops the rest of the string, which looks like this:

iex> CESQL.Parsec.expr("a AND b")
{:ok, ["a"], " AND b", _, _, _}

Alternatively, when I place boolean_operation as the first choice, I believe the parser enters an infinite loop trying to find the start of an expression, because my test times out.

How can I get this working the way I want it to? I’ve tried using NimbleParsec.lookahead/2 every way I can think of, but I might be misunderstanding how it works, because I’ve had no luck.

Showing Posts 1 to 7

jakemorrison

jakemorrison

cosmicrose

cosmicrose OP

I think I’m actually going to try leex and yecc instead of NimbleParsec. NimbleParsec is awesome, but since I’m trying to implement a query language (the CloudEvents query language), I think it’s more suited to the job. And I only recently discovered this additional feature of Erlang and I’m really excited to use it! For anyone else who wants to parse a language using traditional language-parsing tools, check out Tokenizing and parsing in Elixir with yecc and leex – Andrea Leopardi

jakemorrison

jakemorrison

Since there are ABNF grammars for SQL around, you might also look at ex_abnf

kip

kip

ex_cldr Core Team

Using Leex and Yecc is very workable (and what I used for the fundamentals of ex_cldr). But because parsers are fun, here’s a reasonable attempt at parsing your logical expressions in nimble_parsec using a common approach to de-structuring such parsers:

defmodule CESQL.Parsec do
  @moduledoc """
  Based upon the simple grammar of:

    Expression ⇒ Term {AND Term}
    Term ⇒ Factor {OR Factor}
    Factor ⇒ Item | "-" Factor
    Item ⇒ Identifier | "(" Expression ")"

  """
  import NimbleParsec

  whitespace = times(ascii_char([?\s, ?\t]), min: 1)

  # An expression
  defparsec(
    :expr,
    ignore(optional(whitespace))
    |> choice([
      parsec(:term) |> parsec(:op_and) |> parsec(:expr) |> reduce(:postfix),
      parsec(:term)
    ])
  )

  # A term
  defparsec(
    :term,
    choice([
      parsec(:factor) |> parsec(:op_or) |> parsec(:term) |> reduce(:postfix),
      parsec(:factor)
    ])
  )

  # A factor
  defparsec(
    :factor,
    choice([
      parsec(:identifier),
      ignore(ascii_char([?(]))
      |> ignore(optional(whitespace))
      |> parsec(:expr)
      |> ignore(optional(whitespace))
      |> ignore(ascii_char([?)]))
    ])
  )

  # OR operation
  defparsec(
    :op_or,
    ignore(whitespace) |> string("OR") |> ignore(whitespace)
  )

  # AND operation
  defparsec(
    :op_and,
    ignore(whitespace) |> string("AND") |> ignore(whitespace)
  )

  # An identifier (lower case letters)
  defparsec(
    :identifier,
    times(ascii_char([?a..?z]), min: 1) |> reduce({List, :to_string, []})
  )

  # Convert infix list to postfix for more regular "AST"
  def postfix([term_1, op, term_2]) do
    [op, term_1, term_2]
  end

  # Just pattern matching some examples
  def test do
    {:ok, [["AND", "a", "b"]], "", %{}, _, _} = expr("a AND b")
    {:ok, [["OR", "a", "b"]], "", %{}, _, _} = expr("a OR b")

    # precedence
    {:ok, [["AND", "a", ["OR", "b", "c"]]], "", %{}, _, _} = expr("a AND b OR c")
    {:ok, [["AND", ["OR", "a", "b"], "c"]], "", %{}, _, _} = expr("a OR b AND c")

    # nesting
    {:ok, [["OR", "a", ["AND", "b", "c"]]], "", %{}, _, _} = expr("a OR (b AND c)")

    :ok
  end
end
ityonemo

ityonemo

/Self-promotion but I also have pegasus: Pegasus — pegasus v1.0.0 if you like grammars that actually look like grammars

kip

kip

ex_cldr Core Team

@ityonemo wow, that’s very cool. Will definitely be taking that for a spin!

TwistingTwists

TwistingTwists

This post was so useful !
Writing recursive parsers here!
https://github.com/TwistingTwists/swift_class/blob/master/lib/bracket_attributes.ex#L50
Thanks for such insight!

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews