defmodule Sanskrit.Parser do
@moduledoc false
defmodule Behaviour do
@callback parser(term) :: list(term) | {:error, String.t()}
@callback parser() :: list(term) | {:error, String.t()}
end
use Combine
import Elixir.Combine.Parsers.Base
@doc "Given a string, apply many time each defined parser and succeed if the end of the binary is reached"
def parse(str) when is_binary(str) do
Combine.parse(str, many(generic_parser()) |> eof())
end
defp generic_parser(previous \\ nil) do
previous
|> choice([
ignore(spaces()),
func(),
filter_parser(),
has_attribute(),
has_no_attribute(),
wme(),
isa(),
ignore(newline()),
ignore(spaces())
])
end
def literal_string(previous \\ nil) do
string_regex = ~r/(?:\\"|[^\\"])*/
previous
|> between(
char("\""),
label(word_of(string_regex), "string"),
char("\"")
)
|> map(fn str -> String.replace(str, "\\\"", "\"") end)
end
def array(previous \\ nil) do
previous
|> between(
char("["),
many(
choice([
negative_number(),
float(),
integer(),
literal_string(),
ignore(spaces()),
ignore(char(","))
])
),
char("]")
)
end
def func(previous \\ nil) do
previous
|> pipe(
[
skip(ignore(spaces()))
|> string("let")
|> label("let_keyword")
|> skip(spaces())
|> variable()
|> label("variable_name")
|> skip(spaces())
|> string("=")
|> skip(spaces())
|> word()
|> skip(spaces())
|> between(
char("("),
many(
choice([
choice([
negative_number(),
variable(),
float(),
integer(),
literal_string()
]),
ignore(spaces()),
ignore(char(","))
])
),
char(")")
)
|> skip(spaces())
],
fn args ->
case args do
["let", _, variable_name, _, function_name, arguments] ->
{:fun, variable_name, function_name, arguments}
end
end
)
end
def filter_parser(previous \\ nil) do
previous
|> pipe(
[
skip(ignore(spaces()))
|> string("when")
|> ignore(spaces())
|> variable()
|> label("variable")
|> skip(spaces())
|> choice([
string("in"),
string("equal"),
string("not"),
string("greater"),
string("lesser")
])
|> skip(spaces())
|> choice([negative_number(), float(), integer(), literal_string(), array()])
|> skip(spaces())
],
fn args ->
case args do
[_, _, _, type, "equal", value] ->
{:filter, type, :==, value}
[_, _, _, type, "in", value] ->
{:filter, type, :in, value}
[_, _, _, type, "is", value] ->
{:filter, type, :==, value}
[_, _, _, type, "not", value] ->
{:filter, type, :!=, value}
[_, _, _, type, "lesser", value] ->
{:filter, type, :<, value}
[_, _, _, type, "greater", value] ->
{:filter, type, :>, value}
end
end
)
end
def has_no_attribute(previous \\ nil) do
previous
|> pipe(
[
skip(ignore(spaces()))
|> word()
|> label("entity")
|> skip(string("'s"))
|> skip(spaces())
|> word()
|> label("attribute")
|> skip(spaces())
|> skip(string("is"))
|> skip(spaces())
|> string("unknown")
|> skip(spaces())
],
fn [type, attr, "unknown"] ->
{:not_existing_attribute, type, attr}
end
)
end
defp boolean(previous \\ nil) do
previous
|> either(string("true"), string("false"))
|> map(fn str ->
case str do
"true" -> true
"false" -> false
end
end)
end
def has_attribute(previous \\ nil) do
previous
|> pipe(
[
skip(ignore(spaces()))
|> word()
|> label("entity")
|> skip(string("'s"))
|> skip(spaces())
|> word()
|> label("attribute")
|> skip(spaces())
|> skip(string("is"))
|> skip(spaces())
|> choice([
string("in"),
string("equal"),
string("not"),
string("greater"),
string("lesser"),
string("unknonw")
])
|> skip(spaces())
|> choice([
negative_number(),
float(),
integer(),
boolean(),
literal_string(),
variable(),
context_value(),
array()
])
|> skip(spaces())
],
fn args ->
case args do
[type, attr, "equal", value] ->
{:has_attribute, type, attr, :==, value}
[type, attr, "equal", _, value] ->
{:has_attribute, type, attr, :==, value}
[type, attr, "in", value] ->
{:has_attribute, type, attr, :in, value}
[type, attr, "not", value] ->
{:has_attribute, type, attr, :!=, value}
[type, attr, "lesser", value] ->
{:has_attribute, type, attr, :<, value}
[type, attr, "greater", value] ->
{:has_attribute, type, attr, :>, value}
[type, attr, "unknonw", _value] ->
{:not_existing_attribute, type, attr}
[type, attr, value] ->
{:has_attribute, type, attr, :==, value}
end
end
)
end
def is_not(previous \\ nil) do
previous
|> pipe(
[
skip(spaces())
|> variable()
|> label("variable")
|> skip(spaces())
|> string("not")
|> literal_string()
|> skip(spaces())
|> label("entity_name")
|> skip(spaces())
],
fn args ->
case args do
[_, variable, type] -> {:negation, variable, type}
end
end
)
end
def isa(previous \\ nil) do
previous
|> pipe(
[
skip(spaces())
|> variable()
|> label("variable")
|> skip(spaces())
|> choice([string("isa"), string("is_not")])
|> skip(spaces())
|> choice([word(), literal_string()])
|> label("entity_name")
|> skip(spaces())
],
fn args ->
case args do
[_, variable, "isa", type] -> {:isa, variable, type}
[_, variable, "is_not", type] -> {:not, variable, type}
end
end
)
end
def wme(previous \\ nil) do
previous
|> pipe(
[
skip(ignore(spaces()))
|> word()
|> label("entity")
|> skip(string("'s"))
|> skip(spaces())
|> word()
|> label("attribute")
|> skip(spaces())
|> skip(string("is"))
|> skip(spaces())
|> choice([
negative_number(),
float(),
integer(),
boolean(),
literal_string(),
variable(),
context_value(),
array()
])
|> label("value")
|> skip(spaces())
],
fn args ->
case args do
[type, attr, _is, value] -> {:wme, type, attr, value}
[type, attr, value] -> {:wme, type, attr, value}
end
end
)
end
def variable(previous \\ nil) do
previous
|> ignore(char("$"))
|> word()
|> map(fn word -> "$#{word}" end)
end
def context_value(previous \\ nil) do
previous
|> ignore(char("#"))
|> word()
|> map(fn word -> "##{word}" end)
end
def negative_number(previous \\ nil) do
previous
|> pipe(
[
ignore(char("-")),
choice([
float(),
integer()
])
],
fn [int] -> -int end
)
end
defmacro __using__(_opts) do
quote do
use Combine
import Elixir.Combine.Parsers.Base
@behaviour Sanskrit.Parser.Behaviour
end
end
end