Current section

Files

Jump to
glemplate src glemplate@parser.erl
Raw

src/glemplate@parser.erl

-module(glemplate@parser).
-compile([no_auto_import, nowarn_unused_vars]).
-export([parse/1, parse_to_template/2]).
-export_type([incomplete_tag/0, incomplete_access/0, state/0]).
-type incomplete_tag() :: {'if',
glemplate@ast:var(),
gleam@option:option(list(glemplate@ast:node_()))} |
{iter, glemplate@ast:var(), binary()}.
-type incomplete_access() :: {field_access, binary()} |
{index_access, integer()}.
-type state() :: {state,
list(glemplate@ast:node_()),
gleam@string_builder:string_builder(),
list(glemplate@ast:node_()),
gleam@option:option(incomplete_tag()),
gleam@option:option(state())}.
-spec empty_state() -> state().
empty_state() ->
{state, [], gleam@string_builder:new(), [], none, none}.
-spec escaped_tag(state()) -> nibble:parser(state(), any()).
escaped_tag(State) ->
_pipe = nibble:string(<<"%"/utf8>>),
nibble:replace(
_pipe,
erlang:setelement(
4,
State,
[{text, <<"<%"/utf8>>} | erlang:element(4, State)]
)
).
-spec comment_tag(state()) -> nibble:parser(state(), any()).
comment_tag(State) ->
nibble:then(
nibble:string(<<"!--"/utf8>>),
fun(_) ->
_pipe@2 = nibble:loop(
nil,
fun(_) ->
nibble:one_of(
[begin
_pipe = nibble:string(<<"--"/utf8, "%>"/utf8>>),
nibble:map(
_pipe,
fun(Field@0) -> {break, Field@0} end
)
end,
begin
_pipe@1 = nibble:any(),
nibble:replace(_pipe@1, {continue, nil})
end]
)
end
),
nibble:replace(_pipe@2, State)
end
).
-spec end_tag(state()) -> nibble:parser(state(), any()).
end_tag(State) ->
nibble:then(
nibble:string(<<"end"/utf8>>),
fun(_) ->
Outer_state = gleam@option:lazy_unwrap(
erlang:element(6, State),
fun() -> empty_state() end
),
case erlang:element(5, State) of
{some, {'if', Var, If_true}} ->
{If_true@1, State@1} = case If_true of
{some, If_true_nodes} ->
{If_true_nodes, State};
none ->
{gleam@list:reverse(erlang:element(4, State)),
erlang:setelement(4, State, [])}
end,
If_false = gleam@list:reverse(erlang:element(4, State@1)),
nibble:succeed(
erlang:setelement(
4,
Outer_state,
[{dynamic, {'if', Var, If_true@1, If_false}} |
erlang:element(4, Outer_state)]
)
);
{some, {iter, Over, Binding}} ->
nibble:succeed(
erlang:setelement(
4,
Outer_state,
[{dynamic,
{iter,
Over,
Binding,
gleam@list:reverse(
erlang:element(4, State)
)}} |
erlang:element(4, Outer_state)]
)
);
_ ->
nibble:fail(
<<"Expected `if` or `for` tag for `end` tag"/utf8>>
)
end
end
).
-spec else_tag(state()) -> nibble:parser(state(), any()).
else_tag(State) ->
nibble:then(
nibble:string(<<"else"/utf8>>),
fun(_) ->
case erlang:element(5, State) of
{some, {'if', Var, none}} ->
nibble:succeed(
erlang:setelement(
4,
erlang:setelement(
5,
State,
{some,
{'if',
Var,
{some,
gleam@list:reverse(
erlang:element(4, State)
)}}}
),
[]
)
);
_ ->
nibble:fail(<<"Expected `if` to match `else` tag"/utf8>>)
end
end
).
-spec stringify_current_text(state()) -> state().
stringify_current_text(State) ->
erlang:setelement(
3,
erlang:setelement(
4,
State,
[{text, gleam@string_builder:to_string(erlang:element(3, State))} |
erlang:element(4, State)]
),
gleam@string_builder:new()
).
-spec variable_name() -> nibble:parser(binary(), any()).
variable_name() ->
nibble:take_if_and_while(
fun(Grapheme) ->
nibble@predicates:is_alphanum(Grapheme)
orelse (Grapheme
=:= <<"_"/utf8>>)
end,
<<"a variable name"/utf8>>
).
-spec field_access() -> nibble:parser(binary(), any()).
field_access() ->
nibble:take_if_and_while(
fun(Grapheme) ->
nibble@predicates:is_alphanum(Grapheme)
orelse (Grapheme
=:= <<"_"/utf8>>)
end,
<<"a field accessor"/utf8>>
).
-spec index_access() -> nibble:parser(binary(), any()).
index_access() ->
nibble:take_if_and_while(
fun nibble@predicates:is_digit/1,
<<"an index accessor"/utf8>>
).
-spec accessors() -> nibble:parser(list(incomplete_access()), any()).
accessors() ->
nibble:map(
nibble:many(
nibble:one_of([field_access(), index_access()]),
nibble:string(<<"."/utf8>>)
),
fun(Accessors) ->
gleam@list:map(
Accessors,
fun(Accessor) -> case gleam@int:parse(Accessor) of
{ok, Data} ->
{index_access, Data};
{error, _} ->
{field_access, Accessor}
end end
)
end
).
-spec variable() -> nibble:parser(glemplate@ast:var(), any()).
variable() ->
nibble:then(
variable_name(),
fun(Name) ->
nibble:then(
nibble:one_of(
[begin
nibble:then(
nibble:string(<<"."/utf8>>),
fun(_) ->
nibble:then(
accessors(),
fun(Accs) ->
nibble:succeed(Accs)
end
)
end
)
end,
nibble:succeed([])]
),
fun(Accessors) ->
nibble:succeed(
gleam@list:fold(
Accessors,
{assign, Name},
fun(Acc, Accessor) -> case Accessor of
{field_access, Field} ->
{field_access, Acc, Field};
{index_access, Index} ->
{index_access, Acc, Index}
end end
)
)
end
)
end
).
-spec output_tag(state()) -> nibble:parser(state(), any()).
output_tag(State) ->
nibble:then(
nibble:string(<<"="/utf8>>),
fun(_) ->
_pipe = nibble:one_of(
[begin
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
nibble:string(<<"raw"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable(),
fun(Var) ->
nibble:succeed(
erlang:setelement(
4,
State,
[{dynamic,
{raw_output,
Var}} |
erlang:element(
4,
State
)]
)
)
end
)
end
)
end
)
end
)
end,
begin
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable(),
fun(Var@1) ->
nibble:succeed(
erlang:setelement(
4,
State,
[{dynamic, {output, Var@1}} |
erlang:element(4, State)]
)
)
end
)
end
)
end]
),
_pipe@1 = nibble:drop(_pipe, nibble:whitespace()),
nibble:drop(_pipe@1, nibble:string(<<"%>"/utf8>>))
end
).
-spec if_tag(state()) -> nibble:parser(state(), any()).
if_tag(State) ->
nibble:then(
nibble:string(<<"if"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable(),
fun(Var) ->
nibble:succeed(
erlang:setelement(
6,
erlang:setelement(
5,
empty_state(),
{some, {'if', Var, none}}
),
{some, State}
)
)
end
)
end
)
end
).
-spec for_tag(state()) -> nibble:parser(state(), any()).
for_tag(State) ->
nibble:then(
nibble:string(<<"for"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable_name(),
fun(Binding) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
nibble:string(<<"in"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable(),
fun(Over) ->
nibble:succeed(
erlang:setelement(
6,
erlang:setelement(
5,
empty_state(
),
{some,
{iter,
Over,
Binding}}
),
{some,
State}
)
)
end
)
end
)
end
)
end
)
end
)
end
)
end
).
-spec filename() -> nibble:parser(binary(), any()).
filename() ->
nibble:take_if_and_while(
fun(Grapheme) ->
((nibble@predicates:is_alphanum(Grapheme)
orelse (Grapheme
=:= <<"_"/utf8>>))
orelse (Grapheme
=:= <<"-"/utf8>>))
orelse (Grapheme
=:= <<"."/utf8>>)
end,
<<"a filename without spaces"/utf8>>
).
-spec render_binding() -> nibble:parser({glemplate@ast:var(), binary()}, any()).
render_binding() ->
nibble:then(
variable_name(),
fun(To) ->
nibble:then(
nibble:string(<<":"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
variable(),
fun(From) ->
nibble:succeed({From, To})
end
)
end
)
end
)
end
).
-spec render_separator() -> nibble:parser(nil, any()).
render_separator() ->
nibble:then(
nibble:string(<<","/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:succeed(nil)
end
)
end
).
-spec render_tag(state()) -> nibble:parser(state(), any()).
render_tag(State) ->
nibble:then(
nibble:string(<<"render"/utf8>>),
fun(_) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
nibble:then(
filename(),
fun(Filename) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
_pipe = nibble:many(
render_binding(),
render_separator()
),
nibble:map(
_pipe,
fun(Bindings) ->
erlang:setelement(
4,
State,
[{dynamic,
{render,
Filename,
Bindings}} |
erlang:element(4, State)]
)
end
)
end
)
end
)
end
)
end
).
-spec other_tag(state()) -> nibble:parser(state(), any()).
other_tag(State) ->
nibble:then(
nibble:whitespace(),
fun(_) ->
_pipe = nibble:one_of(
[if_tag(State),
for_tag(State),
render_tag(State),
else_tag(State),
end_tag(State)]
),
_pipe@1 = nibble:drop(_pipe, nibble:whitespace()),
nibble:drop(_pipe@1, nibble:string(<<"%>"/utf8>>))
end
).
-spec tag_parser(state()) -> nibble:parser(state(), any()).
tag_parser(State) ->
nibble:then(
nibble:string(<<"<%"/utf8>>),
fun(_) ->
State@1 = stringify_current_text(State),
nibble:one_of(
[escaped_tag(State@1),
comment_tag(State@1),
output_tag(State@1),
other_tag(State@1)]
)
end
).
-spec base_parser(state()) -> nibble:parser(state(), any()).
base_parser(Old_state) ->
_pipe@5 = nibble:loop(
Old_state,
fun(State) ->
nibble:one_of(
[begin
_pipe = tag_parser(State),
nibble:map(
_pipe,
fun(Field@0) -> {continue, Field@0} end
)
end,
begin
_pipe@1 = nibble:any(),
_pipe@2 = nibble:map(
_pipe@1,
fun(C) ->
erlang:setelement(
3,
State,
gleam@string_builder:append(
erlang:element(3, State),
C
)
)
end
),
nibble:map(
_pipe@2,
fun(Field@0) -> {continue, Field@0} end
)
end,
begin
_pipe@3 = nibble:eof(),
_pipe@4 = nibble:replace(
_pipe@3,
stringify_current_text(State)
),
nibble:map(
_pipe@4,
fun(Field@0) -> {break, Field@0} end
)
end]
)
end
),
nibble:map(
_pipe@5,
fun(State@1) ->
erlang:setelement(
3,
erlang:setelement(
4,
erlang:setelement(
2,
State@1,
[{nodes, erlang:element(2, State@1)} |
gleam@list:reverse(erlang:element(4, State@1))]
),
[]
),
gleam@string_builder:new()
)
end
).
-spec parse(binary()) -> {ok, list(glemplate@ast:node_())} |
{error, list(nibble:dead_end(any()))}.
parse(Template) ->
_pipe = nibble:run(Template, base_parser(empty_state())),
gleam@result:map(_pipe, fun(State) -> erlang:element(2, State) end).
-spec parse_to_template(binary(), binary()) -> {ok, glemplate@ast:template()} |
{error, list(nibble:dead_end(any()))}.
parse_to_template(Template, Name) ->
gleam@result:then(
parse(Template),
fun(Parsed) ->
{ok, {template, Name, Parsed}}
end
).