Packages

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

Current section

Files

Jump to
abnf2 lib abnf generator.ex
Raw

lib/abnf/generator.ex

defmodule Abnf.Generator do
def generate([{:rulelist, _preview, _children} = rulelist], module) do
parsers = [parse_helpers(), generate(rulelist), forwarder(module)]
|> Enum.reject(&is_nil/1)
|> List.flatten
quote do
defmodule unquote(module) do
import Abnf.Operators
unquote_splicing(parsers)
end
end
end
def generate({:rulelist, _preview, children}) do
children
|> Enum.filter(&match?({:rule, _, _}, &1))
|> Enum.map(&generate/1)
end
def generate({:rule, _preview, [{_, rulename, _}, _, elements, _]}) do
rulename = String.to_atom(rulename)
elements = generate(elements)
quote do
defrule unquote(rulename) do
unquote(elements)
end
end
end
def generate({:rulename, preview, _children}) do
rulename = String.to_atom(preview)
quote do
parse(unquote(rulename))
end
end
def generate({:elements, _preview, [alternation|_]}) do
generate(alternation)
end
def generate({:alternation, _preview, [concatenation]}) do
generate(concatenation)
end
def generate({:alternation, _preview, children}) do
children = children
|> Enum.filter(&match?({:concatenation, _, _}, &1))
|> Enum.map(&generate/1)
quote do
alternate([unquote_splicing(children)])
end
end
def generate({:concatenation, _preview, [repetition]}) do
generate(repetition)
end
def generate({:concatenation, _preview, children}) do
children = children
|> Enum.filter(&match?({:repetition, _, _}, &1))
|> Enum.map(&generate/1)
quote do
concatenate([unquote_splicing(children)])
end
end
def generate({:repetition, _preview, [element]}) do
generate(element)
end
def generate({:repetition, _, [{:repeat, _, children}, element]}) do
{min, max} = case Enum.chunk_by(children, &match?({:literal, "*", []}, &1)) do
[[{:literal, "*", []}]] ->
{0, :infinity}
[min] ->
{generate_digits(min), generate_digits(min)}
[min, [{:literal, "*", []}]] ->
{generate_digits(min), :infinity}
[[{:literal, "*", []}], max] ->
{0, generate_digits(max)}
[min, [{:literal, "*", []}], max] ->
{generate_digits(min), generate_digits(max)}
end
element = generate(element)
quote do
repeat(unquote(min), unquote(max), unquote(element))
end
end
def generate({:element, _preview, [child]}) do
generate(child)
end
def generate({:group, _preview, children}) do
alternation = Enum.find(children, &match?({:alternation, _, _}, &1))
generate(alternation)
end
def generate({:option, _preview, children}) do
alternation = Enum.find(children, &match?({:alternation, _, _}, &1))
quote do
repeat(0, 1, unquote(generate(alternation)))
end
end
def generate({:"char-val", _preview, children}) do
char_val = children
|> Enum.filter(&match?({:literal, _, []}, &1))
|> Enum.map_join(&elem(&1, 1))
|> String.to_charlist
quote do
literal(unquote(char_val))
end
end
def generate({:"num-val", _preview, [_, child]}) do
generate(child)
end
def generate({rule, _preview, [_|children]}) when rule in [:"bin-val", :"dec-val", :"hex-val"] do
case Enum.chunk_by(children, &match?({:literal, "-", []}, &1)) do
[digits] ->
integer = generate_digits(digits, rule)
quote do
literal([unquote(integer)])
end
[min, _, max] ->
min = generate_digits(min, rule)
max = generate_digits(max, rule)
quote do
range(unquote(min), unquote(max))
end
end
end
defp parse_helpers do
[quote do
def parse(rule, input) when is_binary(input) do
parse(rule, String.to_charlist(input))
end
end,
quote do
def parse(rule, input) do
parse(rule).(input)
end
end]
end
defp forwarder(module) do
case module do
Abnf.Core ->
nil
_ ->
quote do
def parse(rule) do
Abnf.Core.parse(rule)
end
end
end
end
defp generate_digits(digits) do
generate_digits(digits, :"dec-val")
end
defp generate_digits(digits, type) do
base = case type do
:"bin-val" -> 2
:"dec-val" -> 10
:"hex-val" -> 16
end
digits
|> Enum.map_join(&elem(&1, 1))
|> String.to_integer(base)
end
end