Skip to content

Instantly share code, notes, and snippets.

@slapers
Last active July 16, 2020 19:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slapers/d8c7f7532f5d340922dd142b7d852999 to your computer and use it in GitHub Desktop.
Save slapers/d8c7f7532f5d340922dd142b7d852999 to your computer and use it in GitHub Desktop.
defmodule Parser do
@moduledoc """
Sample parser for article on:
http://stefan.lapers.be/posts/elixir-writing-an-expression-parser-with-nimble-parsec/
"""
import NimbleParsec
not_ = string("!") |> label("!")
and_ = string("&&") |> replace(:&&) |> label("&&")
or_ = string("||") |> replace(:||) |> label("||")
lparen = ascii_char([?(]) |> label("(")
rparen = ascii_char([?)]) |> label(")")
# <const> :== "true" | "false"
true_ = string("true") |> replace(true) |> label("true")
false_ = string("false") |> replace(false) |> label("false")
const = choice([true_, false_]) |> label("boolean")
# <factor> :== <not> <expr> | "(" <expr> ")" | <const>
negation = not_ |> ignore |> parsec(:factor) |> tag(:!)
grouping = ignore(lparen) |> parsec(:expr) |> ignore(rparen)
defcombinatorp(:factor, choice([negation, grouping, const]))
# <term> :== <factor> {<and> <factor>}
defcombinatorp(
:term,
parsec(:factor)
|> repeat(and_ |> parsec(:factor))
|> reduce(:fold_infixl)
)
# <expr> :== <term> {<or> <term>}
defparsec(
:expr,
parsec(:term)
|> repeat(or_ |> parsec(:term))
|> reduce(:fold_infixl)
)
defp fold_infixl(acc) do
acc
|> Enum.reverse()
|> Enum.chunk_every(2)
|> List.foldr([], fn
[l], [] -> l
[r, op], l -> {op, [l, r]}
end)
end
end
defmodule ParserTest do
use ExUnit.Case
doctest Parser
defp parse(input), do: input |> Parser.expr() |> unwrap
defp unwrap({:ok, [acc], "", _, _, _}), do: acc
defp unwrap({:ok, _, rest, _, _, _}), do: {:error, "could not parse" <> rest}
defp unwrap({:error, reason, _rest, _, _, _}), do: {:error, reason}
@err "expected boolean while processing !, " <>
"followed by factor or (, followed by expr, followed by ) or boolean"
test "parses consts" do
assert true == parse("true")
assert false == parse("false")
assert {:error, @err} == parse("1")
end
test "parses negations" do
assert {:!, [true]} == parse("!true")
assert {:!, [false]} == parse("!false")
end
test "parses conjunctions" do
assert {:&&, [{:!, [true]}, false]} == parse("!true&&false")
assert {:!, [{:&&, [false, true]}]} == parse("!(false&&true)")
end
test "parses disjunctions" do
assert {:||, [true, {:&&, [true, false]}]} == parse("true||true&&false")
assert {:&&, [{:||, [true, true]}, false]} == parse("(true||true)&&false")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment