defmodule ParserBuilder do @moduledoc """ usage: defmodule MyParser do use ParserBuilder, file: "path_to_my_grammar.xml" end """ alias ParserBuilder.Grammar defmacro __using__(opts) do file = Keyword.get(opts, :file) {:ok, {'grammar', _attrs, rules}, _remainder} = :erlsom.simple_form_file( file, nameFun: fn name, _namespace, _prefix -> name end ) rules = rules |> convert_tree() |> Enum.map(&create_rule/1) |> Enum.reduce(Grammar.new(), fn {k, v}, acc -> Grammar.add_rule(acc, k, v) end) |> Macro.escape() quote do alias ParserBuilder.{ Runner, Grammar, Override } @external_resource file = unquote(file) def get_rules() do unquote(rules) end def parse_string(rule_overrides \\ Override.new(), start_rule_name, input_string) do parse_string_non_strict(rule_overrides, start_rule_name, input_string) end def parse_string_non_strict(rule_overrides \\ Override.new(), start_rule_name, input_string) do route(rule_overrides, start_rule_name, input_string, &Runner.parse_string_non_strict/3) end def parse_string_strict(rule_overrides \\ Override.new(), start_rule_name, input_string) do route(rule_overrides, start_rule_name, input_string, &Runner.parse_string_strict/3) end def from_rule_name(rule_overrides \\ Override.new(), rule_name) do fn input -> parse_string(rule_overrides, rule_name, input) end end def from_rule_name_non_strict(rule_overrides \\ Override.new(), rule_name) do fn input -> parse_string_non_strict(rule_overrides, rule_name, input) end end def from_rule_name_strict(rule_overrides \\ Override.new(), rule_name) do fn input -> parse_string_strict(rule_overrides, rule_name, input) end end @doc """ _seales_ the parser given. The resulting parser will not produces a continuation. """ def finalize(parser) do fn input -> case parser.(input) do {@continue, continuation} -> continuation.("") done -> done end end end defp route(rule_overrides, start_rule_name, input_string, runner_fun) do get_rules() |> Grammar.merge_grammar_with_overrides(rule_overrides) |> runner_fun.( start_rule_name, input_string ) end end end defp create_rule({:rule, %{id: name} = attrs, body}) do body = case attrs do %{postprocess: "tag"} -> [{:tag, %{name: name}, body}] %{postprocess: "ignore"} -> [{:ignore, %{}, body}] _ -> body end {name, body} end defp convert_tree(acc \\ [], rules) defp convert_tree(acc, []) do acc |> Enum.reverse() end defp convert_tree(acc, [{name, atts, children} | rest]) do name = name |> to_string() |> String.to_atom() atts = atts |> Enum.map(fn {k, v} -> { k |> to_string() |> String.to_atom(), v |> to_string() } end) |> Enum.into(%{}) children = convert_tree([], children) convert_tree([{name, atts, children} | acc], rest) end end