Skip to content

Instantly share code, notes, and snippets.

@kipcole9
Last active June 21, 2021 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kipcole9/f7ea0a9dd4b55cf4dea88241e609b662 to your computer and use it in GitHub Desktop.
Save kipcole9/f7ea0a9dd4b55cf4dea88241e609b662 to your computer and use it in GitHub Desktop.
Parsing a simple expression grammar with maybe quoted strings
defmodule ParseHelpers do
import NimbleParsec
def whitespace() do
ascii_char([?\s])
|> repeat
end
def quote_mark(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ascii_char([?"])
end
def and_(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ascii_char([?&])
end
def or_(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ascii_char([?|])
end
def left_paren(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ascii_char([?(])
end
def right_paren(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ascii_char([?)])
end
def input_string(combinator \\ empty()) do
combinator
|> choice([
quoted_string(),
unquoted_string()
])
end
@syntax Enum.map([?", ?(, ?), ?&, ?|, ?\s], &{:not, &1})
def unquoted_string(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> repeat(ascii_char(@syntax))
|> reduce({List, :to_string, []})
end
def quoted_string(combinator \\ empty()) do
combinator
|> ignore(optional(whitespace()))
|> ignore(quote_mark())
|> repeat(maybe_escaped_char())
|> ignore(quote_mark())
|> reduce({List, :to_string, []})
end
def maybe_escaped_char(combinator \\ empty()) do
combinator
|> choice([
ignore(string("\\")) |> ascii_char([]),
ascii_char([{:not, ?"}])
])
end
def to_postfix([left, ?&, right]) do
[:and, left, right]
end
def to_postfix([left, ?|, right]) do
[:or, left, right]
end
end
defmodule ParserGrammar do
import NimbleParsec
import ParseHelpers
# quotation_mark ::= ?"
# left_bracket ::= ?(
# right_bracket ::= ?)
# or_ ::= ?|
# and_ ::= ?&
# Based upon knowledge @ https://www.youtube.com/watch?v=dDtZLm7HIJs#t=15m54s
# expression ::= term or_ expression | term
# term ::= factor and_ term | factor
# factor ::= left_bracket expression right_bracket | input_string
defparsec :expression,
choice([
parsec(:term) |> or_() |> parsec(:expression) |> reduce(:to_postfix),
parsec(:term)
])
defparsec :term,
ignore(optional(whitespace()))
|> choice([
parsec(:factor) |> and_() |> parsec(:term) |> reduce(:to_postfix),
parsec(:factor)
])
defparsec :factor,
ignore(optional(whitespace()))
|> choice([
ignore(left_paren()) |> parsec(:expression) |> ignore(right_paren()) |> wrap(),
input_string()
])
defparsec :parse,
parsec(:expression)
|> ignore(optional(whitespace()))
|> eos()
end
defmodule ParseTest do
@inputs [
"\"1 quote\"",
"\"2 quote\" | \"quote\"",
"\"3 quote\" & foo | \"quote\"",
"\"4 quote\" & (foo | bar) & \"quote\"",
"5 & (foo | (bar | \"qu( & )ux\" & batz))",
"6&(foo|(bar|\"qu( & )ux\"&batz))",
"7bar&\"qu( & )ux\"",
"8 | foo & (\"b a a r\" & quux) | batz",
"99 | ab & \"c d\"",
"10aa & bb | cc",
"11aa | bb & cc"
]
def test do
for string <- @inputs do
{:ok, result, "", _, _, _} = ParserGrammar.parse(string)
result
end
end
end
defmodule Evaluator do
def evaluate(term) when is_binary(term) do
inspect(term)
end
def evaluate([:or, left, right]) do
"#{evaluate(left)} OR #{evaluate(right)}"
end
def evaluate([:and, left, right]) do
"#{evaluate(left)} AND #{evaluate(right)}"
end
def evaluate([expression]) do
"( #{evaluate(expression)} )"
end
end
@kipcole9
Copy link
Author

kipcole9 commented Jun 6, 2021

One example:

iex> {:ok, result, "", _, _, _} = ParserGrammar.parse "6&(foo|(bar|\"qu( & )ux\"&batz))"
{:ok,
 [[:and, "6", [[:or, "foo", [[:or, "bar", [:and, "qu( & )ux", "batz"]]]]]]], "",
 %{}, {1, 0}, 30}
iex> Evaluator.evaluate result                                                          
"( \"6\" AND ( \"foo\" OR ( \"bar\" OR \"qu( & )ux\" AND \"batz\" ) ) )"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment