Packages

An ABNF parser generator updated from the original developed by @vanstee.

Current section

Files

Jump to
abnf2 lib abnf operators.ex
Raw

lib/abnf/operators.ex

defmodule Abnf.Operators do
defmacro defrule(name, do: block) do
quote do
def parse(unquote(name)) do
fn input ->
case unquote(block).(input) do
:error ->
:error
children ->
[{unquote(name), preview(children), children}]
end
end
end
end
end
# TODO: Support multiple chars instead of using this special case
def literal('=/') do
fn [?=, ?/|_] ->
[{:literal, "/=", []}]
_ ->
:error
end
end
def literal(element) do
fn input ->
literal(element, input)
end
end
def literal([element], input) do
case input do
[^element|_] ->
[{:literal, to_string([element]), []}]
^element ->
[{:literal, to_string([element]), []}]
_ ->
:error
end
end
def literal(element, input) do
literal([element], input)
end
def concatenate(elements) do
fn input ->
concatenate(elements, [], input)
end
end
defp concatenate([], acc, _input) do
acc
|> List.flatten
|> Enum.reverse
end
defp concatenate([element|elements], acc, input) do
case element.(input) do
:error ->
:error
children ->
input = advance(children, input)
children = Enum.reverse(children)
concatenate(elements, [children|acc], input)
end
end
def alternate(elements) do
fn input ->
alternate(elements, [], input)
end
end
defp alternate([], [], _input) do
:error
end
defp alternate([], acc, _input) do
Enum.max_by(acc, &length/1)
end
defp alternate([element|elements], acc, input) do
case element.(input) do
:error ->
alternate(elements, acc, input)
child ->
alternate(elements, [child|acc], input)
end
end
def repeat(min, max, element) do
fn input ->
repeat(min, max, element, 0, [], input)
end
end
defp repeat(_min, max, _element, max, acc, _input) do
acc
|> List.flatten
|> Enum.reverse
end
defp repeat(min, max, element, count, acc, input) do
case element.(input) do
:error when count >= min ->
repeat(min, max, element, max, acc, input)
:error ->
:error
children ->
input = advance(children, input)
children = Enum.reverse(children)
repeat(min, max, element, count + 1, [children|acc], input)
end
end
def range(min, max) do
fn input ->
range(min, max, input)
end
end
defp range(min, max, [head|_]) when head >= min and head <= max do
[{:literal, to_string([head]), []}]
end
defp range(_min, _max, _string) do
:error
end
def preview(children) do
children
|> Enum.map(fn {_, preview, _} -> preview end)
|> Enum.join
end
def advance(children, input) do
children
|> preview
|> String.to_charlist
|> string_subtract(input)
end
def string_subtract([], string), do: string
def string_subtract([h|substring], [h|string]) do
string_subtract(substring, string)
end
end