Current section

Files

Jump to
jsonc lib exclude.combinators.ex
Raw

lib/exclude.combinators.ex

defmodule JSONC.Combinators do
@moduledoc false
def parse(text, pos \\ 0) when is_binary(text) and is_integer(pos) do
case data().(text, pos) do
{_, _, ast} -> ast
end
end
def data do
node(:data, chain([line(), repeat(chain([literal("\n"), line()]))]))
end
def line do
node(:line, chain([item(), repeat(chain([literal(","), item()]))]))
end
def item do
node(
:item,
chain([
ignore(literal(" ")),
repeat(
chain([
negate(literal(",")),
negate(literal("\n")),
# &any/2,
branch([chain([negate(literal(",")), negate(literal("\n"))]), literal(" "), &any/2])
])
),
ignore(literal(" "))
])
)
end
def node(type, pattern) when is_atom(type) and is_function(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
result = pattern.(text, pos)
case result do
nil ->
nil
{pattern_start, pattern_end, value} ->
{pattern_start, pattern_end,
%{type: type, value: value, start: pattern_start, end: pattern_end}}
end
end
end
def nothing(text, pos) when is_binary(text) and is_integer(pos) do
case String.split_at(text, pos) |> elem(1) |> String.starts_with?("") do
true ->
{pos, pos, ""}
false ->
nil
end
end
def any(text, pos) when is_binary(text) and is_integer(pos) do
case pos < String.length(text) do
true -> {pos, pos + 1, String.at(text, pos)}
false -> nil
end
end
def literal(pattern) when is_binary(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
case String.split_at(text, pos) |> elem(1) |> String.starts_with?(pattern) do
true ->
{pos, pos + String.length(pattern), pattern}
false ->
nil
end
end
end
def ignore(pattern) when is_function(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
case pattern.(text, pos) do
nil -> nil
{_, pattern_end, _} -> {pos, pattern_end, ""}
end
end
end
def chain(patterns) when is_list(patterns) do
check_patterns(patterns)
fn text, pos when is_binary(text) and is_integer(pos) ->
start = pos
case chain_check(patterns, text, pos) do
nil ->
nil
results ->
{_, chain_end, _} = results |> Enum.at(-1)
case results |> Enum.all?(fn {_, _, child} -> is_binary(child) end) do
true ->
{start, chain_end,
results |> Enum.map(fn {_, _, child} -> child end) |> Enum.join()}
false ->
{start, chain_end,
results
|> Enum.filter(fn {_, _, child} -> !is_binary(child) end)
|> Enum.map(fn {_, _, child} -> child end)
|> List.flatten()}
end
end
end
end
def branch(patterns) when is_list(patterns) do
check_patterns(patterns)
fn text, pos when is_binary(text) and is_integer(pos) ->
branch_check(patterns, text, pos)
end
end
def repeat(pattern) when is_function(pattern) do
repeat_internal(pattern)
end
def check_patterns(patterns) when is_list(patterns) do
patterns
|> Enum.each(fn p ->
case is_function(p) do
true -> nil
false -> raise "#{p |> inspect()} is not a function"
end
end)
end
def negate(pattern) when is_function(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
result = pattern.(text, pos)
case result do
nil ->
{pos, pos, ""}
_ ->
nil
end
end
end
def peek(pattern) when is_function(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
case pattern.(text, pos + 1) do
nil -> nil
_ -> {pos, pos, ""}
end
end
end
def chain_check(nil) do
nil
end
def chain_check(patterns, text, pos, results \\ [])
when is_list(patterns) and is_binary(text) and is_integer(pos) do
case patterns do
[] ->
results
[pattern | rest] ->
case pattern.(text, pos) do
nil ->
chain_check(nil)
{_pattern_start, pattern_end, _children} = result ->
chain_check(
rest,
text,
pattern_end,
results ++ [result]
)
end
end
end
def branch_check(patterns, text, pos) when is_list(patterns) do
case patterns do
[] ->
nil
[pattern | rest] ->
case pattern.(text, pos) do
nil -> branch_check(rest, text, pos)
result -> result
end
end
end
def repeat_internal(pattern) when is_function(pattern) do
fn text, pos when is_binary(text) and is_integer(pos) ->
branch([
chain([
pattern,
repeat_internal(pattern)
]),
&nothing/2
]).(text, pos)
end
end
end