Current section
Files
Jump to
Current section
Files
priv/neotoma_parse.peg
%% Grammar and transformations
rules <- space? declaration_sequence space? code_block? space?
`
RootRule = verify_rules(),
Rules = unicode:characters_to_binary(lists:map(fun(R) -> [R, "\n\n"] end, lists:nth(2, Node))),
Code = case lists:nth(4, Node) of
{code, Block} -> Block;
_ -> []
end,
[{rules, Rules},
{code, Code},
{root, RootRule},
{transform, ets:lookup(memo_table_name(),gen_transform)},
{combinators, ets:lookup_element(memo_table_name(), combinators, 2)}]
`;
declaration_sequence <- head:declaration tail:(space declaration)*
`
FirstRule = proplists:get_value(head, Node),
OtherRules = [I || [_,I] <- proplists:get_value(tail, Node, [])],
[FirstRule|OtherRules]
`;
declaration <- nonterminal space* '<-' space* parsing_expression space? code_block? space? ';'
`
[{nonterminal,Symbol}|Tail] = Node,
add_lhs(Symbol, Index),
Transform = case lists:nth(6,Tail) of
{code, CodeBlock} -> CodeBlock;
_ ->
ets:insert_new(memo_table_name(),{gen_transform, true}),
["transform('",Symbol,"', Node, Idx)"]
end,
TransformArgs = case used_transform_variables(Transform) of
[] -> "_Node, _Idx";
['Idx'] -> "_Node, Idx";
['Node'] -> "Node, _Idx";
['Idx', 'Node'] -> "Node, Idx"
end,
["-spec '", Symbol, "'(input(), index()) -> parse_result().\n",
"'",Symbol,"'","(Input, Index) ->\n ",
"p(Input, Index, '",Symbol,"', fun(I,D) -> (",
lists:nth(4, Tail),
")(I,D) end, fun(", TransformArgs, ") ->",Transform," end)."]
`;
parsing_expression <- choice / sequence / primary ~;
choice <- head:alternative tail:(space '/' space alternative)+
`
Tail = [lists:last(S) || S <- proplists:get_value(tail, Node)],
Head = proplists:get_value(head, Node),
Statements = [[", ", TS] || TS <- Tail],
used_combinator(p_choose),
["p_choose([", Head, Statements, "])"]
`;
alternative <- sequence / labeled_primary ~;
primary <- prefix atomic / atomic suffix / atomic
`
case Node of
[Atomic, one_or_more] ->
used_combinator(p_one_or_more),
used_combinator(p_scan),
["p_one_or_more(", Atomic, ")"];
[Atomic, zero_or_more] ->
used_combinator(p_zero_or_more),
used_combinator(p_scan),
["p_zero_or_more(", Atomic, ")"];
[Atomic, optional] ->
used_combinator(p_optional),
["p_optional(", Atomic, ")"];
[assert, Atomic] ->
used_combinator(p_assert),
["p_assert(", Atomic, ")"];
[not_, Atomic] ->
used_combinator(p_not),
["p_not(", Atomic, ")"];
_ -> Node
end
`;
sequence <- head:labeled_primary tail:(space labeled_primary)+
`
Tail = [lists:nth(2, S) || S <- proplists:get_value(tail, Node)],
Head = proplists:get_value(head, Node),
Statements = [[", ", TS] || TS <- Tail],
used_combinator(p_seq),
["p_seq([", Head, Statements, "])"]
`;
labeled_primary <- label? primary
`
case hd(Node) of
[] -> lists:nth(2, Node);
Label ->
used_combinator(p_label),
["p_label('", Label, "', ", lists:nth(2, Node), ")"]
end
`;
label <- alpha_char alphanumeric_char* ':'
`
lists:sublist(Node, length(Node)-1)
`;
suffix <- repetition_suffix / optional_suffix
`
case Node of
<<"*">> -> zero_or_more;
<<"+">> -> one_or_more;
<<"?">> -> optional
end
`;
optional_suffix <- '?' ~;
repetition_suffix <- '+' / '*' ~;
prefix <- '&' / '!'
`
case Node of
<<"&">> -> assert;
<<"!">> -> not_
end
`;
atomic <- terminal / nonterminal / parenthesized_expression
`
case Node of
{nonterminal, Symbol} ->
[<<"fun '">>, Symbol, <<"'/2">>];
_ -> Node
end
`;
parenthesized_expression <- '(' space? parsing_expression space? ')' `lists:nth(3, Node)`;
nonterminal <- alpha_char alphanumeric_char*
`
Symbol = unicode:characters_to_binary(Node),
add_nt(Symbol, Idx),
{nonterminal, Symbol}
`;
terminal <- regexp_string / quoted_string / character_class / anything_symbol ~;
regexp_string <- '#' string:(!'#' ('\\#' / .))+ '#'
`
used_combinator(p_regexp),
["p_regexp(<<\"",
% Escape \ and " as they are used in erlang string. Other sumbol stay as is.
% \ -> \\
% " -> \"
re:replace(proplists:get_value(string, Node), "\"|\\\\", "\\\\&", [{return, binary}, global]),
"\">>)"]
`;
quoted_string <- single_quoted_string / double_quoted_string
`
used_combinator(p_string),
lists:flatten(["p_string(<<\"",
escape_string(unicode:characters_to_list(proplists:get_value(string, Node))),
"\">>)"])
`;
double_quoted_string <- '"' string:(!'"' ("\\\\" / '\\"' / .))* '"' ~;
single_quoted_string <- "'" string:(!"'" ("\\\\" / "\\'" / .))* "'" ~;
character_class <- '[' characters:(!']' ('\\\\' . / !'\\\\' .))+ ']'
`
used_combinator(p_charclass),
["p_charclass(<<\"[",
escape_string(unicode:characters_to_list(proplists:get_value(characters, Node))),
"]\">>)"]
`;
anything_symbol <- '.' ` used_combinator(p_anything), <<"p_anything()">> `;
alpha_char <- [A-Za-z_] ~;
alphanumeric_char <- alpha_char / [0-9] ~;
space <- (white / comment_to_eol)+ ~;
comment_to_eol <- !'%{' '%' (!"\n" .)* ~;
white <- [ \t\n\r] ~;
code_block <- ( '%{' code:('\\%' / '$%' / !'%}' .)+ '%}' ) /
('`' code:('\\`' / '$`' / !'`' .)+ '`') /
'~'
`
case Node of
<<"~">> -> {code, <<"Node">>};
_ -> {code, proplists:get_value('code', Node)}
end
`;
%% Extra functions
`
% insert escapes into a string
-spec escape_string(string()) -> string().
escape_string(String) -> escape_string(String, []).
-spec escape_string(string(), string()) -> string().
escape_string([], Output) ->
lists:reverse(Output);
escape_string([H|T], Output) ->
escape_string(T,
case H of
$/ -> [$/,$\\|Output];
$\" -> [$\",$\\|Output]; % " comment inserted to help some editors with highlighting the generated parser
$\' -> [$\',$\\|Output]; % ' comment inserted to help some editors with highlighting the generated parser
$\b -> [$b,$\\|Output];
$\d -> [$d,$\\|Output];
$\e -> [$e,$\\|Output];
$\f -> [$f,$\\|Output];
$\n -> [$n,$\\|Output];
$\r -> [$r,$\\|Output];
$\s -> [$s,$\\|Output];
$\t -> [$t,$\\|Output];
$\v -> [$v,$\\|Output];
_ -> [H|Output]
end).
-spec add_lhs(binary(), index()) -> true.
add_lhs(Symbol, Index) ->
case ets:lookup(memo_table_name(), lhs) of
[] ->
ets:insert(memo_table_name(), {lhs, [{Symbol,Index}]});
[{lhs, L}] when is_list(L) ->
ets:insert(memo_table_name(), {lhs, [{Symbol,Index}|L]})
end.
-spec add_nt(binary(), index()) -> true | ok.
add_nt(Symbol, Index) ->
case ets:lookup(memo_table_name(), nts) of
[] ->
ets:insert(memo_table_name(), {nts, [{Symbol,Index}]});
[{nts, L}] when is_list(L) ->
case proplists:is_defined(Symbol, L) of
true ->
ok;
_ ->
ets:insert(memo_table_name(), {nts, [{Symbol,Index}|L]})
end
end.
-spec verify_rules() -> ok | no_return().
verify_rules() ->
[{lhs, LHS}] = ets:lookup(memo_table_name(), lhs),
[{nts, NTs}] = ets:lookup(memo_table_name(), nts),
[Root|NonRoots] = lists:reverse(LHS),
lists:foreach(fun({Sym,Idx}) ->
case proplists:is_defined(Sym, NTs) of
true ->
ok;
_ ->
io:format("neotoma warning: rule '~s' is unused. ~p~n", [Sym,Idx])
end
end, NonRoots),
lists:foreach(fun({S,I}) ->
case proplists:is_defined(S, LHS) of
true ->
ok;
_ ->
io:format("neotoma error: nonterminal '~s' has no reduction. (found at ~p) No parser will be generated!~n", [S,I]),
exit({neotoma, {no_reduction, list_to_atom(binary_to_list(S))}})
end
end, NTs),
Root.
-spec used_combinator(atom()) -> true.
used_combinator(C) ->
case ets:lookup(memo_table_name(), combinators) of
[] ->
ets:insert(memo_table_name(), {combinators, ordsets:from_list([C])});
[{combinators, Cs}] ->
ets:insert(memo_table_name(), {combinators, ordsets:add_element(C, Cs)})
end.
-spec used_transform_variables(binary()) -> [ 'Node' | 'Idx' ].
used_transform_variables(Transform) ->
Code = unicode:characters_to_list(Transform),
{ok, Tokens, _} = erl_scan:string(Code),
used_transform_variables(Tokens, []).
used_transform_variables([{var, _, Name}|Tokens], Acc) ->
used_transform_variables(Tokens, case Name of
'Node' -> [Name | Acc];
'Idx' -> [Name | Acc];
_ -> Acc
end);
used_transform_variables([_|Tokens], Acc) ->
used_transform_variables(Tokens, Acc);
used_transform_variables([], Acc) ->
lists:usort(Acc).
`