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) ]), ¬hing/2 ]).(text, pos) end end end