125 lines
2.6 KiB
Elixir
125 lines
2.6 KiB
Elixir
defmodule ExpressionParser do
|
|
import NimbleParsec
|
|
|
|
whitespace = ignore(optional(ascii_string([?\s, ?\t, ?\n, ?\r], min: 1)))
|
|
|
|
identifier =
|
|
ascii_string([?a..?z, ?A..?Z, ?_, ?0..?9], min: 1)
|
|
|> label("identifier")
|
|
|> unwrap_and_tag(:field)
|
|
|
|
quoted_string =
|
|
ignore(string("\""))
|
|
|> repeat(utf8_char(not: ?"))
|
|
|> reduce({List, :to_string, []})
|
|
|> ignore(string("\""))
|
|
|
|
number =
|
|
ascii_string([?0..?9], min: 1)
|
|
|> map({String, :to_integer, []})
|
|
|
|
atom_value =
|
|
ignore(string("'"))
|
|
|> ascii_string([?a..?z, ?A..?Z], min: 1)
|
|
|> ignore(string("'"))
|
|
|> map({__MODULE__, :identity, []})
|
|
|
|
list_item =
|
|
choice([
|
|
number,
|
|
atom_value,
|
|
quoted_string |> unwrap_and_tag(:string)
|
|
])
|
|
|
|
atom_list =
|
|
ignore(string("["))
|
|
|> optional(whitespace)
|
|
|> repeat(
|
|
list_item
|
|
|> optional(ignore(string(",")))
|
|
|> optional(whitespace)
|
|
)
|
|
|> ignore(string("]"))
|
|
|> tag(:list)
|
|
|
|
|
|
|
|
operator =
|
|
choice([
|
|
string("ilike") |> replace(:ilike),
|
|
string(">=") |> replace(:>=),
|
|
string("<=") |> replace(:<=),
|
|
string("!=") |> replace(:!=),
|
|
string(">") |> replace(:>),
|
|
string("<") |> replace(:<),
|
|
string("=") |> replace(:==),
|
|
string("in") |> replace(:in)
|
|
])
|
|
|
|
value =
|
|
choice([
|
|
quoted_string |> unwrap_and_tag(:string),
|
|
number |> unwrap_and_tag(:number),
|
|
atom_list
|
|
])
|
|
|
|
comparison =
|
|
identifier
|
|
|> ignore(whitespace)
|
|
|> concat(operator)
|
|
|> ignore(whitespace)
|
|
|> concat(value)
|
|
|> tag(:comparison)
|
|
|
|
defcombinatorp :expr, parsec(:logic_expr)
|
|
|
|
paren_expr =
|
|
ignore(string("("))
|
|
|> ignore(whitespace)
|
|
|> parsec(:expr)
|
|
|> ignore(whitespace)
|
|
|> ignore(string(")"))
|
|
|
|
logic_term =
|
|
choice([
|
|
paren_expr,
|
|
comparison
|
|
])
|
|
|
|
logical_op =
|
|
ignore(whitespace)
|
|
|> choice([
|
|
string("and") |> replace(:and),
|
|
string("or") |> replace(:or)
|
|
])
|
|
|> ignore(whitespace)
|
|
|
|
logic_expr =
|
|
logic_term
|
|
|> repeat(
|
|
logical_op
|
|
|> concat(logic_term)
|
|
)
|
|
|> reduce({__MODULE__, :reduce_logic, []})
|
|
|
|
defparsec :logic_expr, logic_expr
|
|
|
|
def parse(input) do
|
|
case logic_expr(input) do
|
|
{:ok, result, "", _, _, _} -> {:ok, result}
|
|
{:ok, _, rest, _, _, _} -> {:error, "Unexpected remaining input: #{inspect(rest)}"}
|
|
{:error, reason, _, _, _, _} -> {:error, reason}
|
|
end
|
|
end
|
|
|
|
def reduce_logic([head | tail]) do
|
|
Enum.chunk_every(tail, 2)
|
|
|> Enum.reduce(head, fn [op, right], acc ->
|
|
{op, acc, right}
|
|
end)
|
|
end
|
|
|
|
def identity(value), do: value
|
|
|
|
end
|