Packages

HTML templates → type-safe Gleam modules. Write familiar markup instead of nested function calls. No more bracket-counting nightmares.

Current section

Files

Jump to
ghtml src ghtml@parser.erl
Raw

src/ghtml@parser.erl

-module(ghtml@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/ghtml/parser.gleam").
-export([tokenize/1, parse/1, format_error/2, format_errors/2]).
-export_type([stack_frame/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Template parser for `.ghtml` files.\n"
"\n"
" Converts template source text into tokens and builds an AST representing\n"
" the template structure including HTML elements, expressions, and control flow.\n"
).
-type stack_frame() :: {element_frame,
binary(),
list(ghtml@types:attr()),
list(ghtml@types:node_()),
ghtml@types:span()} |
{if_frame,
binary(),
list(ghtml@types:node_()),
list(ghtml@types:node_()),
boolean(),
ghtml@types:span()} |
{each_frame,
binary(),
binary(),
gleam@option:option(binary()),
list(ghtml@types:node_()),
ghtml@types:span()} |
{case_frame,
binary(),
gleam@option:option(binary()),
list(ghtml@types:node_()),
list(ghtml@types:case_branch()),
ghtml@types:span()}.
-file("src/ghtml/parser.gleam", 154).
-spec advance_pos(ghtml@types:position(), binary()) -> ghtml@types:position().
advance_pos(Pos, Char) ->
case Char of
<<"\n"/utf8>> ->
{position, erlang:element(2, Pos) + 1, 1};
_ ->
{position, erlang:element(2, Pos), erlang:element(3, Pos) + 1}
end.
-file("src/ghtml/parser.gleam", 161).
-spec advance_pos_str(ghtml@types:position(), binary()) -> ghtml@types:position().
advance_pos_str(Pos, Str) ->
_pipe = gleam@string:to_graphemes(Str),
gleam@list:fold(_pipe, Pos, fun advance_pos/2).
-file("src/ghtml/parser.gleam", 232).
-spec split_params(binary(), list(binary()), binary(), integer()) -> list(binary()).
split_params(Input, Acc, Current, Depth) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
case Current of
<<""/utf8>> ->
lists:reverse(Acc);
_ ->
lists:reverse([Current | Acc])
end;
{ok, {Char, Rest}} ->
case Char of
<<"("/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth + 1
);
<<"["/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth + 1
);
<<"{"/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth + 1
);
<<")"/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth - 1
);
<<"]"/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth - 1
);
<<"}"/utf8>> ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth - 1
);
<<","/utf8>> when Depth =:= 0 ->
split_params(Rest, [Current | Acc], <<""/utf8>>, 0);
_ ->
split_params(
Rest,
Acc,
<<Current/binary, Char/binary>>,
Depth
)
end
end.
-file("src/ghtml/parser.gleam", 213).
-spec parse_param_list(binary()) -> list({binary(), binary()}).
parse_param_list(Content) ->
_pipe = Content,
_pipe@1 = gleam@string:trim(_pipe),
_pipe@2 = split_params(_pipe@1, [], <<""/utf8>>, 0),
gleam@list:filter_map(
_pipe@2,
fun(Param_str) ->
Param_str@1 = gleam@string:trim(Param_str),
case Param_str@1 of
<<""/utf8>> ->
{error, nil};
_ ->
case gleam@string:split_once(Param_str@1, <<":"/utf8>>) of
{ok, {Name, Type_str}} ->
{ok,
{gleam@string:trim(Name),
gleam@string:trim(Type_str)}};
{error, _} ->
{error, nil}
end
end
end
).
-file("src/ghtml/parser.gleam", 358).
-spec is_tag_name_char(binary()) -> boolean().
is_tag_name_char(Char) ->
case Char of
<<"a"/utf8>> ->
true;
<<"b"/utf8>> ->
true;
<<"c"/utf8>> ->
true;
<<"d"/utf8>> ->
true;
<<"e"/utf8>> ->
true;
<<"f"/utf8>> ->
true;
<<"g"/utf8>> ->
true;
<<"h"/utf8>> ->
true;
<<"i"/utf8>> ->
true;
<<"j"/utf8>> ->
true;
<<"k"/utf8>> ->
true;
<<"l"/utf8>> ->
true;
<<"m"/utf8>> ->
true;
<<"n"/utf8>> ->
true;
<<"o"/utf8>> ->
true;
<<"p"/utf8>> ->
true;
<<"q"/utf8>> ->
true;
<<"r"/utf8>> ->
true;
<<"s"/utf8>> ->
true;
<<"t"/utf8>> ->
true;
<<"u"/utf8>> ->
true;
<<"v"/utf8>> ->
true;
<<"w"/utf8>> ->
true;
<<"x"/utf8>> ->
true;
<<"y"/utf8>> ->
true;
<<"z"/utf8>> ->
true;
<<"A"/utf8>> ->
true;
<<"B"/utf8>> ->
true;
<<"C"/utf8>> ->
true;
<<"D"/utf8>> ->
true;
<<"E"/utf8>> ->
true;
<<"F"/utf8>> ->
true;
<<"G"/utf8>> ->
true;
<<"H"/utf8>> ->
true;
<<"I"/utf8>> ->
true;
<<"J"/utf8>> ->
true;
<<"K"/utf8>> ->
true;
<<"L"/utf8>> ->
true;
<<"M"/utf8>> ->
true;
<<"N"/utf8>> ->
true;
<<"O"/utf8>> ->
true;
<<"P"/utf8>> ->
true;
<<"Q"/utf8>> ->
true;
<<"R"/utf8>> ->
true;
<<"S"/utf8>> ->
true;
<<"T"/utf8>> ->
true;
<<"U"/utf8>> ->
true;
<<"V"/utf8>> ->
true;
<<"W"/utf8>> ->
true;
<<"X"/utf8>> ->
true;
<<"Y"/utf8>> ->
true;
<<"Z"/utf8>> ->
true;
<<"0"/utf8>> ->
true;
<<"1"/utf8>> ->
true;
<<"2"/utf8>> ->
true;
<<"3"/utf8>> ->
true;
<<"4"/utf8>> ->
true;
<<"5"/utf8>> ->
true;
<<"6"/utf8>> ->
true;
<<"7"/utf8>> ->
true;
<<"8"/utf8>> ->
true;
<<"9"/utf8>> ->
true;
<<"-"/utf8>> ->
true;
<<"_"/utf8>> ->
true;
_ ->
false
end.
-file("src/ghtml/parser.gleam", 342).
-spec extract_tag_name(binary(), binary(), ghtml@types:position()) -> {binary(),
binary(),
ghtml@types:position()}.
extract_tag_name(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{Acc, Input, Pos};
{ok, {Char, Rest}} ->
case is_tag_name_char(Char) of
true ->
extract_tag_name(
Rest,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
);
false ->
{Acc, Input, Pos}
end
end.
-file("src/ghtml/parser.gleam", 559).
-spec is_attr_name_char(binary()) -> boolean().
is_attr_name_char(Char) ->
case Char of
<<"a"/utf8>> ->
true;
<<"b"/utf8>> ->
true;
<<"c"/utf8>> ->
true;
<<"d"/utf8>> ->
true;
<<"e"/utf8>> ->
true;
<<"f"/utf8>> ->
true;
<<"g"/utf8>> ->
true;
<<"h"/utf8>> ->
true;
<<"i"/utf8>> ->
true;
<<"j"/utf8>> ->
true;
<<"k"/utf8>> ->
true;
<<"l"/utf8>> ->
true;
<<"m"/utf8>> ->
true;
<<"n"/utf8>> ->
true;
<<"o"/utf8>> ->
true;
<<"p"/utf8>> ->
true;
<<"q"/utf8>> ->
true;
<<"r"/utf8>> ->
true;
<<"s"/utf8>> ->
true;
<<"t"/utf8>> ->
true;
<<"u"/utf8>> ->
true;
<<"v"/utf8>> ->
true;
<<"w"/utf8>> ->
true;
<<"x"/utf8>> ->
true;
<<"y"/utf8>> ->
true;
<<"z"/utf8>> ->
true;
<<"A"/utf8>> ->
true;
<<"B"/utf8>> ->
true;
<<"C"/utf8>> ->
true;
<<"D"/utf8>> ->
true;
<<"E"/utf8>> ->
true;
<<"F"/utf8>> ->
true;
<<"G"/utf8>> ->
true;
<<"H"/utf8>> ->
true;
<<"I"/utf8>> ->
true;
<<"J"/utf8>> ->
true;
<<"K"/utf8>> ->
true;
<<"L"/utf8>> ->
true;
<<"M"/utf8>> ->
true;
<<"N"/utf8>> ->
true;
<<"O"/utf8>> ->
true;
<<"P"/utf8>> ->
true;
<<"Q"/utf8>> ->
true;
<<"R"/utf8>> ->
true;
<<"S"/utf8>> ->
true;
<<"T"/utf8>> ->
true;
<<"U"/utf8>> ->
true;
<<"V"/utf8>> ->
true;
<<"W"/utf8>> ->
true;
<<"X"/utf8>> ->
true;
<<"Y"/utf8>> ->
true;
<<"Z"/utf8>> ->
true;
<<"0"/utf8>> ->
true;
<<"1"/utf8>> ->
true;
<<"2"/utf8>> ->
true;
<<"3"/utf8>> ->
true;
<<"4"/utf8>> ->
true;
<<"5"/utf8>> ->
true;
<<"6"/utf8>> ->
true;
<<"7"/utf8>> ->
true;
<<"8"/utf8>> ->
true;
<<"9"/utf8>> ->
true;
<<"-"/utf8>> ->
true;
<<"_"/utf8>> ->
true;
<<":"/utf8>> ->
true;
_ ->
false
end.
-file("src/ghtml/parser.gleam", 543).
-spec extract_attr_name(binary(), binary(), ghtml@types:position()) -> {binary(),
binary(),
ghtml@types:position()}.
extract_attr_name(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{Acc, Input, Pos};
{ok, {Char, Rest}} ->
case is_attr_name_char(Char) of
true ->
extract_attr_name(
Rest,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
);
false ->
{Acc, Input, Pos}
end
end.
-file("src/ghtml/parser.gleam", 630).
-spec extract_quoted_value(binary(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_quoted_value(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error, {span, Pos, Pos}, <<"Unterminated string"/utf8>>}};
{ok, {<<"\""/utf8>>, Rest}} ->
End_pos = advance_pos(Pos, <<"\""/utf8>>),
{ok, {Acc, Rest, End_pos}};
{ok, {<<"\\"/utf8>>, Rest@1}} ->
case gleam_stdlib:string_pop_grapheme(Rest@1) of
{ok, {Escaped, Rest2}} ->
New_pos = advance_pos(
advance_pos(Pos, <<"\\"/utf8>>),
Escaped
),
extract_quoted_value(
Rest2,
<<Acc/binary, Escaped/binary>>,
New_pos
);
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated escape"/utf8>>}}
end;
{ok, {Char, Rest@2}} ->
extract_quoted_value(
Rest@2,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 657).
-spec extract_single_quoted_value(binary(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_single_quoted_value(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error, {span, Pos, Pos}, <<"Unterminated string"/utf8>>}};
{ok, {<<"'"/utf8>>, Rest}} ->
End_pos = advance_pos(Pos, <<"'"/utf8>>),
{ok, {Acc, Rest, End_pos}};
{ok, {<<"\\"/utf8>>, Rest@1}} ->
case gleam_stdlib:string_pop_grapheme(Rest@1) of
{ok, {Escaped, Rest2}} ->
New_pos = advance_pos(
advance_pos(Pos, <<"\\"/utf8>>),
Escaped
),
extract_single_quoted_value(
Rest2,
<<Acc/binary, Escaped/binary>>,
New_pos
);
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated escape"/utf8>>}}
end;
{ok, {Char, Rest@2}} ->
extract_single_quoted_value(
Rest@2,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 815).
-spec find_as_keyword(binary(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
find_as_keyword(Input, Acc, Pos) ->
case Input of
<<""/utf8>> ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Expected 'as' keyword in each"/utf8>>}};
<<" as "/utf8, Rest/binary>> ->
New_pos = advance_pos_str(Pos, <<" as "/utf8>>),
{ok, {Acc, Rest, New_pos}};
_ ->
case gleam_stdlib:string_pop_grapheme(Input) of
{ok, {Char, Rest@1}} ->
find_as_keyword(
Rest@1,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
);
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Expected 'as' keyword in each"/utf8>>}}
end
end.
-file("src/ghtml/parser.gleam", 844).
-spec extract_until_close_brace(binary(), binary(), ghtml@types:position()) -> {binary(),
binary(),
ghtml@types:position()}.
extract_until_close_brace(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{Acc, Input, Pos};
{ok, {<<"}"/utf8>>, Rest}} ->
End_pos = advance_pos(Pos, <<"}"/utf8>>),
{Acc, Rest, End_pos};
{ok, {Char, Rest@1}} ->
extract_until_close_brace(
Rest@1,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 860).
-spec parse_binding(binary()) -> {binary(), gleam@option:option(binary())}.
parse_binding(Binding) ->
Binding@1 = gleam@string:trim(Binding),
case gleam@string:split_once(Binding@1, <<","/utf8>>) of
{ok, {Item, Index}} ->
{gleam@string:trim(Item), {some, gleam@string:trim(Index)}};
{error, _} ->
{Binding@1, none}
end.
-file("src/ghtml/parser.gleam", 798).
-spec parse_each_header(binary(), ghtml@types:position()) -> {ok,
{binary(),
binary(),
gleam@option:option(binary()),
binary(),
ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_each_header(Input, Pos) ->
case find_as_keyword(Input, <<""/utf8>>, Pos) of
{ok, {Collection, Rest, New_pos}} ->
{Binding, Rest@1, End_pos} = extract_until_close_brace(
Rest,
<<""/utf8>>,
New_pos
),
{Item, Index} = parse_binding(Binding),
{ok, {gleam@string:trim(Collection), Item, Index, Rest@1, End_pos}};
{error, Err} ->
{error, Err}
end.
-file("src/ghtml/parser.gleam", 947).
-spec extract_string_literal(binary(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_string_literal(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated string literal"/utf8>>}};
{ok, {<<"\\"/utf8>>, Rest}} ->
case gleam_stdlib:string_pop_grapheme(Rest) of
{ok, {Escaped, Rest2}} ->
New_pos = advance_pos(
advance_pos(Pos, <<"\\"/utf8>>),
Escaped
),
extract_string_literal(
Rest2,
<<<<Acc/binary, "\\"/utf8>>/binary, Escaped/binary>>,
New_pos
);
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated escape sequence"/utf8>>}}
end;
{ok, {<<"\""/utf8>>, Rest@1}} ->
End_pos = advance_pos(Pos, <<"\""/utf8>>),
{ok, {Acc, Rest@1, End_pos}};
{ok, {Char, Rest@2}} ->
extract_string_literal(
Rest@2,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 897).
-spec extract_expression(binary(), integer(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_expression(Input, Depth, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unexpected end of input while parsing expression"/utf8>>}};
{ok, {<<"{"/utf8>>, Rest}} ->
extract_expression(
Rest,
Depth + 1,
<<Acc/binary, "{"/utf8>>,
advance_pos(Pos, <<"{"/utf8>>)
);
{ok, {<<"}"/utf8>>, Rest@1}} ->
case Depth of
0 ->
End_pos = advance_pos(Pos, <<"}"/utf8>>),
{ok, {Acc, Rest@1, End_pos}};
_ ->
extract_expression(
Rest@1,
Depth - 1,
<<Acc/binary, "}"/utf8>>,
advance_pos(Pos, <<"}"/utf8>>)
)
end;
{ok, {<<"\""/utf8>>, Rest@2}} ->
New_pos = advance_pos(Pos, <<"\""/utf8>>),
case extract_string_literal(Rest@2, <<""/utf8>>, New_pos) of
{ok, {Str, Remaining, After_pos}} ->
extract_expression(
Remaining,
Depth,
<<<<<<Acc/binary, "\""/utf8>>/binary, Str/binary>>/binary,
"\""/utf8>>,
After_pos
);
{error, Err} ->
{error, Err}
end;
{ok, {<<"("/utf8>>, Rest@3}} ->
extract_expression(
Rest@3,
Depth,
<<Acc/binary, "("/utf8>>,
advance_pos(Pos, <<"("/utf8>>)
);
{ok, {<<")"/utf8>>, Rest@4}} ->
extract_expression(
Rest@4,
Depth,
<<Acc/binary, ")"/utf8>>,
advance_pos(Pos, <<")"/utf8>>)
);
{ok, {Char, Rest@5}} ->
extract_expression(
Rest@5,
Depth,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 451).
-spec parse_single_attribute(binary(), ghtml@types:position()) -> {ok,
{ghtml@types:attr(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_single_attribute(Input, Pos) ->
case Input of
<<"@"/utf8, Rest/binary>> ->
Pos@1 = advance_pos(Pos, <<"@"/utf8>>),
{Event_name, Rest@1, New_pos} = extract_attr_name(
Rest,
<<""/utf8>>,
Pos@1
),
case Rest@1 of
<<"={"/utf8, After/binary>> ->
New_pos@1 = advance_pos_str(New_pos, <<"={"/utf8>>),
case extract_expression(After, 0, <<""/utf8>>, New_pos@1) of
{ok, {Handler, Remaining, End_pos}} ->
{ok,
{{event_attr, Event_name, Handler},
Remaining,
End_pos}};
{error, Err} ->
{error, Err}
end;
_ ->
{error,
{parse_error,
{span, New_pos, New_pos},
<<"Expected '={' after event attribute name"/utf8>>}}
end;
_ ->
{Attr_name, Rest@2, New_pos@2} = extract_attr_name(
Input,
<<""/utf8>>,
Pos
),
case Attr_name of
<<""/utf8>> ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Expected attribute name"/utf8>>}};
_ ->
case Rest@2 of
<<"={"/utf8, After@1/binary>> ->
New_pos@3 = advance_pos_str(
New_pos@2,
<<"={"/utf8>>
),
case extract_expression(
After@1,
0,
<<""/utf8>>,
New_pos@3
) of
{ok, {Expr, Remaining@1, End_pos@1}} ->
{ok,
{{dynamic_attr, Attr_name, Expr},
Remaining@1,
End_pos@1}};
{error, Err@1} ->
{error, Err@1}
end;
<<"=\""/utf8, After@2/binary>> ->
New_pos@4 = advance_pos_str(
New_pos@2,
<<"=\""/utf8>>
),
case extract_quoted_value(
After@2,
<<""/utf8>>,
New_pos@4
) of
{ok, {Value, Remaining@2, End_pos@2}} ->
{ok,
{{static_attr, Attr_name, Value},
Remaining@2,
End_pos@2}};
{error, Err@2} ->
{error, Err@2}
end;
<<"='"/utf8, After@3/binary>> ->
New_pos@5 = advance_pos_str(
New_pos@2,
<<"='"/utf8>>
),
case extract_single_quoted_value(
After@3,
<<""/utf8>>,
New_pos@5
) of
{ok, {Value@1, Remaining@3, End_pos@3}} ->
{ok,
{{static_attr, Attr_name, Value@1},
Remaining@3,
End_pos@3}};
{error, Err@3} ->
{error, Err@3}
end;
_ ->
{ok, {{boolean_attr, Attr_name}, Rest@2, New_pos@2}}
end
end
end.
-file("src/ghtml/parser.gleam", 983).
-spec parse_text(binary(), binary(), ghtml@types:position()) -> {binary(),
binary(),
ghtml@types:position()}.
parse_text(Input, Acc, Pos) ->
case Input of
<<"{{"/utf8, Rest/binary>> ->
parse_text(
Rest,
<<Acc/binary, "{"/utf8>>,
advance_pos_str(Pos, <<"{{"/utf8>>)
);
<<"}}"/utf8, Rest@1/binary>> ->
parse_text(
Rest@1,
<<Acc/binary, "}"/utf8>>,
advance_pos_str(Pos, <<"}}"/utf8>>)
);
<<"{"/utf8, _/binary>> ->
{Acc, Input, Pos};
<<"<"/utf8, _/binary>> ->
{Acc, Input, Pos};
<<"@import"/utf8, _/binary>> ->
{Acc, Input, Pos};
<<"@params"/utf8, _/binary>> ->
{Acc, Input, Pos};
<<""/utf8>> ->
{Acc, Input, Pos};
_ ->
case gleam_stdlib:string_pop_grapheme(Input) of
{ok, {Char, Rest@2}} ->
parse_text(
Rest@2,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
);
{error, _} ->
{Acc, Input, Pos}
end
end.
-file("src/ghtml/parser.gleam", 1019).
-spec skip_html_comment(binary(), ghtml@types:position()) -> {ok,
{binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
skip_html_comment(Input, Pos) ->
case Input of
<<""/utf8>> ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated HTML comment"/utf8>>}};
<<"-->"/utf8, Rest/binary>> ->
End_pos = advance_pos_str(Pos, <<"-->"/utf8>>),
{ok, {Rest, End_pos}};
_ ->
case gleam_stdlib:string_pop_grapheme(Input) of
{ok, {Char, Rest@1}} ->
skip_html_comment(Rest@1, advance_pos(Pos, Char));
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated HTML comment"/utf8>>}}
end
end.
-file("src/ghtml/parser.gleam", 1045).
-spec is_whitespace_only(binary()) -> boolean().
is_whitespace_only(Text) ->
_pipe = Text,
_pipe@1 = gleam@string:to_graphemes(_pipe),
gleam@list:all(
_pipe@1,
fun(C) ->
(((C =:= <<" "/utf8>>) orelse (C =:= <<"\t"/utf8>>)) orelse (C =:= <<"\n"/utf8>>))
orelse (C =:= <<"\r"/utf8>>)
end
).
-file("src/ghtml/parser.gleam", 1051).
-spec skip_whitespace(binary(), ghtml@types:position()) -> {binary(),
ghtml@types:position()}.
skip_whitespace(Input, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{ok, {<<" "/utf8>>, Rest}} ->
skip_whitespace(Rest, advance_pos(Pos, <<" "/utf8>>));
{ok, {<<"\t"/utf8>>, Rest@1}} ->
skip_whitespace(Rest@1, advance_pos(Pos, <<"\t"/utf8>>));
{ok, {<<"\n"/utf8>>, Rest@2}} ->
skip_whitespace(Rest@2, advance_pos(Pos, <<"\n"/utf8>>));
{ok, {<<"\r"/utf8>>, Rest@3}} ->
skip_whitespace(Rest@3, advance_pos(Pos, <<"\r"/utf8>>));
_ ->
{Input, Pos}
end.
-file("src/ghtml/parser.gleam", 258).
-spec parse_close_tag(binary(), ghtml@types:position(), ghtml@types:position()) -> {ok,
{ghtml@types:token(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_close_tag(Input, Start_pos, Pos) ->
{Tag_name, Rest, New_pos} = extract_tag_name(Input, <<""/utf8>>, Pos),
case Tag_name of
<<""/utf8>> ->
{error,
{parse_error, {span, Pos, Pos}, <<"Expected tag name"/utf8>>}};
_ ->
{Rest@1, New_pos@1} = skip_whitespace(Rest, New_pos),
case Rest@1 of
<<">"/utf8, Remaining/binary>> ->
End_pos = advance_pos(New_pos@1, <<">"/utf8>>),
Span = {span, Start_pos, End_pos},
{ok, {{html_close, Tag_name, Span}, Remaining, End_pos}};
_ ->
{error,
{parse_error,
{span, New_pos@1, New_pos@1},
<<"Expected '>' in closing tag"/utf8>>}}
end
end.
-file("src/ghtml/parser.gleam", 430).
-spec parse_attributes(
binary(),
ghtml@types:position(),
list(ghtml@types:attr())
) -> {ok, {list(ghtml@types:attr()), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_attributes(Input, Pos, Attrs) ->
{Input@1, Pos@1} = skip_whitespace(Input, Pos),
case Input@1 of
<<">"/utf8, _/binary>> ->
{ok, {Attrs, Input@1, Pos@1}};
<<"/>"/utf8, _/binary>> ->
{ok, {Attrs, Input@1, Pos@1}};
<<""/utf8>> ->
{ok, {Attrs, Input@1, Pos@1}};
_ ->
case parse_single_attribute(Input@1, Pos@1) of
{ok, {Attr, Remaining, New_pos}} ->
parse_attributes(Remaining, New_pos, [Attr | Attrs]);
{error, Err} ->
{error, Err}
end
end.
-file("src/ghtml/parser.gleam", 285).
-spec parse_open_tag(binary(), ghtml@types:position(), ghtml@types:position()) -> {ok,
{ghtml@types:token(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_open_tag(Input, Start_pos, Pos) ->
{Tag_name, Rest, New_pos} = extract_tag_name(Input, <<""/utf8>>, Pos),
case Tag_name of
<<""/utf8>> ->
{error,
{parse_error, {span, Pos, Pos}, <<"Expected tag name"/utf8>>}};
_ ->
case parse_attributes(Rest, New_pos, []) of
{ok, {Attrs, Remaining, Attr_end_pos}} ->
{Remaining@1, Attr_end_pos@1} = skip_whitespace(
Remaining,
Attr_end_pos
),
case Remaining@1 of
<<"/>"/utf8, After/binary>> ->
End_pos = advance_pos_str(
Attr_end_pos@1,
<<"/>"/utf8>>
),
Span = {span, Start_pos, End_pos},
{ok,
{{html_open,
Tag_name,
lists:reverse(Attrs),
true,
Span},
After,
End_pos}};
<<">"/utf8, After@1/binary>> ->
End_pos@1 = advance_pos(
Attr_end_pos@1,
<<">"/utf8>>
),
Span@1 = {span, Start_pos, End_pos@1},
{ok,
{{html_open,
Tag_name,
lists:reverse(Attrs),
false,
Span@1},
After@1,
End_pos@1}};
_ ->
{error,
{parse_error,
{span, Attr_end_pos@1, Attr_end_pos@1},
<<"Expected '>' or '/>' to close tag"/utf8>>}}
end;
{error, Err} ->
{error, Err}
end
end.
-file("src/ghtml/parser.gleam", 1061).
-spec extract_balanced_parens(
binary(),
integer(),
binary(),
ghtml@types:position()
) -> {ok, {binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_balanced_parens(Input, Depth, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Unterminated parentheses"/utf8>>}};
{ok, {<<"("/utf8>>, Rest}} ->
extract_balanced_parens(
Rest,
Depth + 1,
<<Acc/binary, "("/utf8>>,
advance_pos(Pos, <<"("/utf8>>)
);
{ok, {<<")"/utf8>>, Rest@1}} ->
case Depth of
0 ->
End_pos = advance_pos(Pos, <<")"/utf8>>),
{ok, {Acc, Rest@1, End_pos}};
_ ->
extract_balanced_parens(
Rest@1,
Depth - 1,
<<Acc/binary, ")"/utf8>>,
advance_pos(Pos, <<")"/utf8>>)
)
end;
{ok, {<<"\""/utf8>>, Rest@2}} ->
New_pos = advance_pos(Pos, <<"\""/utf8>>),
case extract_string_literal(Rest@2, <<""/utf8>>, New_pos) of
{ok, {Str, Remaining, After_pos}} ->
extract_balanced_parens(
Remaining,
Depth,
<<<<<<Acc/binary, "\""/utf8>>/binary, Str/binary>>/binary,
"\""/utf8>>,
After_pos
);
{error, Err} ->
{error, Err}
end;
{ok, {Char, Rest@3}} ->
extract_balanced_parens(
Rest@3,
Depth,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 168).
-spec parse_import(binary(), ghtml@types:position(), ghtml@types:position()) -> {ok,
{ghtml@types:token(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_import(Input, Start_pos, Pos) ->
case Input of
<<"("/utf8, Rest/binary>> ->
Pos@1 = advance_pos(Pos, <<"("/utf8>>),
case extract_balanced_parens(Rest, 0, <<""/utf8>>, Pos@1) of
{ok, {Content, Remaining, End_pos}} ->
Span = {span, Start_pos, End_pos},
{ok, {{import, Content, Span}, Remaining, End_pos}};
{error, Err} ->
{error, Err}
end;
_ ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Expected '(' after @import"/utf8>>}}
end.
-file("src/ghtml/parser.gleam", 191).
-spec parse_params(binary(), ghtml@types:position(), ghtml@types:position()) -> {ok,
{ghtml@types:token(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_params(Input, Start_pos, Pos) ->
case Input of
<<"("/utf8, Rest/binary>> ->
Pos@1 = advance_pos(Pos, <<"("/utf8>>),
case extract_balanced_parens(Rest, 0, <<""/utf8>>, Pos@1) of
{ok, {Content, Remaining, End_pos}} ->
Params = parse_param_list(Content),
Span = {span, Start_pos, End_pos},
{ok, {{params, Params, Span}, Remaining, End_pos}};
{error, Err} ->
{error, Err}
end;
_ ->
{error,
{parse_error,
{span, Pos, Pos},
<<"Expected '(' after @params"/utf8>>}}
end.
-file("src/ghtml/parser.gleam", 868).
-spec extract_pattern(binary(), binary(), ghtml@types:position()) -> {ok,
{binary(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
extract_pattern(Input, Acc, Pos) ->
case gleam_stdlib:string_pop_grapheme(Input) of
{error, _} ->
{error,
{parse_error, {span, Pos, Pos}, <<"Unterminated pattern"/utf8>>}};
{ok, {<<"}"/utf8>>, Rest}} ->
End_pos = advance_pos(Pos, <<"}"/utf8>>),
{ok, {Acc, Rest, End_pos}};
{ok, {<<"("/utf8>>, Rest@1}} ->
New_pos = advance_pos(Pos, <<"("/utf8>>),
case extract_balanced_parens(Rest@1, 0, <<""/utf8>>, New_pos) of
{ok, {Inner, Remaining, After_pos}} ->
extract_pattern(
Remaining,
<<<<<<Acc/binary, "("/utf8>>/binary, Inner/binary>>/binary,
")"/utf8>>,
After_pos
);
{error, Err} ->
{error, Err}
end;
{ok, {Char, Rest@2}} ->
extract_pattern(
Rest@2,
<<Acc/binary, Char/binary>>,
advance_pos(Pos, Char)
)
end.
-file("src/ghtml/parser.gleam", 686).
-spec parse_brace_content(
binary(),
ghtml@types:position(),
ghtml@types:position()
) -> {ok, {ghtml@types:token(), binary(), ghtml@types:position()}} |
{error, ghtml@types:parse_error()}.
parse_brace_content(Input, Start_pos, Pos) ->
case Input of
<<"#if "/utf8, Rest/binary>> ->
Pos@1 = advance_pos_str(Pos, <<"#if "/utf8>>),
case extract_expression(Rest, 0, <<""/utf8>>, Pos@1) of
{ok, {Condition, Remaining, End_pos}} ->
Span = {span, Start_pos, End_pos},
{ok,
{{if_start, gleam@string:trim(Condition), Span},
Remaining,
End_pos}};
{error, Err} ->
{error, Err}
end;
<<":else}"/utf8, Rest@1/binary>> ->
End_pos@1 = advance_pos_str(Pos, <<":else}"/utf8>>),
Span@1 = {span, Start_pos, End_pos@1},
{ok, {{'else', Span@1}, Rest@1, End_pos@1}};
<<"/if}"/utf8, Rest@2/binary>> ->
End_pos@2 = advance_pos_str(Pos, <<"/if}"/utf8>>),
Span@2 = {span, Start_pos, End_pos@2},
{ok, {{if_end, Span@2}, Rest@2, End_pos@2}};
<<"#each "/utf8, Rest@3/binary>> ->
Pos@2 = advance_pos_str(Pos, <<"#each "/utf8>>),
case parse_each_header(Rest@3, Pos@2) of
{ok, {Collection, Item, Index, Remaining@1, End_pos@3}} ->
Span@3 = {span, Start_pos, End_pos@3},
{ok,
{{each_start, Collection, Item, Index, Span@3},
Remaining@1,
End_pos@3}};
{error, Err@1} ->
{error, Err@1}
end;
<<"/each}"/utf8, Rest@4/binary>> ->
End_pos@4 = advance_pos_str(Pos, <<"/each}"/utf8>>),
Span@4 = {span, Start_pos, End_pos@4},
{ok, {{each_end, Span@4}, Rest@4, End_pos@4}};
<<"#case "/utf8, Rest@5/binary>> ->
Pos@3 = advance_pos_str(Pos, <<"#case "/utf8>>),
case extract_expression(Rest@5, 0, <<""/utf8>>, Pos@3) of
{ok, {Expr, Remaining@2, End_pos@5}} ->
Span@5 = {span, Start_pos, End_pos@5},
{ok,
{{case_start, gleam@string:trim(Expr), Span@5},
Remaining@2,
End_pos@5}};
{error, Err@2} ->
{error, Err@2}
end;
<<":"/utf8, Rest@6/binary>> ->
Pos@4 = advance_pos(Pos, <<":"/utf8>>),
case extract_pattern(Rest@6, <<""/utf8>>, Pos@4) of
{ok, {Pattern, Remaining@3, End_pos@6}} ->
Span@6 = {span, Start_pos, End_pos@6},
{ok,
{{case_pattern, Pattern, Span@6},
Remaining@3,
End_pos@6}};
{error, Err@3} ->
{error, Err@3}
end;
<<"/case}"/utf8, Rest@7/binary>> ->
End_pos@7 = advance_pos_str(Pos, <<"/case}"/utf8>>),
Span@7 = {span, Start_pos, End_pos@7},
{ok, {{case_end, Span@7}, Rest@7, End_pos@7}};
_ ->
case extract_expression(Input, 0, <<""/utf8>>, Pos) of
{ok, {Expr@1, Remaining@4, End_pos@8}} ->
Span@8 = {span, Start_pos, End_pos@8},
{ok, {{expr, Expr@1, Span@8}, Remaining@4, End_pos@8}};
{error, Err@4} ->
{error, Err@4}
end
end.
-file("src/ghtml/parser.gleam", 53).
-spec tokenize_loop(binary(), ghtml@types:position(), list(ghtml@types:token())) -> {ok,
list(ghtml@types:token())} |
{error, list(ghtml@types:parse_error())}.
tokenize_loop(Input, Pos, Tokens) ->
case Input of
<<""/utf8>> ->
{ok, Tokens};
<<"@import"/utf8, Rest/binary>> ->
Start_pos = Pos,
Pos@1 = advance_pos_str(Pos, <<"@import"/utf8>>),
case parse_import(Rest, Start_pos, Pos@1) of
{ok, {Token, Remaining, New_pos}} ->
tokenize_loop(Remaining, New_pos, [Token | Tokens]);
{error, Err} ->
{error, [Err]}
end;
<<"@params"/utf8, Rest@1/binary>> ->
Start_pos@1 = Pos,
Pos@2 = advance_pos_str(Pos, <<"@params"/utf8>>),
case parse_params(Rest@1, Start_pos@1, Pos@2) of
{ok, {Token@1, Remaining@1, New_pos@1}} ->
tokenize_loop(Remaining@1, New_pos@1, [Token@1 | Tokens]);
{error, Err@1} ->
{error, [Err@1]}
end;
<<"<!--"/utf8, Rest@2/binary>> ->
Pos@3 = advance_pos_str(Pos, <<"<!--"/utf8>>),
case skip_html_comment(Rest@2, Pos@3) of
{ok, {Remaining@2, New_pos@2}} ->
tokenize_loop(Remaining@2, New_pos@2, Tokens);
{error, Err@2} ->
{error, [Err@2]}
end;
<<"</"/utf8, Rest@3/binary>> ->
Start_pos@2 = Pos,
Pos@4 = advance_pos_str(Pos, <<"</"/utf8>>),
case parse_close_tag(Rest@3, Start_pos@2, Pos@4) of
{ok, {Token@2, Remaining@3, New_pos@3}} ->
tokenize_loop(Remaining@3, New_pos@3, [Token@2 | Tokens]);
{error, Err@3} ->
{error, [Err@3]}
end;
<<"<"/utf8, Rest@4/binary>> ->
Start_pos@3 = Pos,
Pos@5 = advance_pos(Pos, <<"<"/utf8>>),
case parse_open_tag(Rest@4, Start_pos@3, Pos@5) of
{ok, {Token@3, Remaining@4, New_pos@4}} ->
tokenize_loop(Remaining@4, New_pos@4, [Token@3 | Tokens]);
{error, Err@4} ->
{error, [Err@4]}
end;
<<"{"/utf8, Rest@5/binary>> ->
Start_pos@4 = Pos,
Pos@6 = advance_pos(Pos, <<"{"/utf8>>),
case parse_brace_content(Rest@5, Start_pos@4, Pos@6) of
{ok, {Token@4, Remaining@5, New_pos@5}} ->
tokenize_loop(Remaining@5, New_pos@5, [Token@4 | Tokens]);
{error, Err@5} ->
{error, [Err@5]}
end;
_ ->
Start_pos@5 = Pos,
{Text, Remaining@6, New_pos@6} = parse_text(Input, <<""/utf8>>, Pos),
case Text of
<<""/utf8>> ->
case gleam_stdlib:string_pop_grapheme(Input) of
{ok, {_, Rest@6}} ->
tokenize_loop(
Rest@6,
advance_pos(Pos, <<"_"/utf8>>),
Tokens
);
{error, _} ->
{ok, Tokens}
end;
_ ->
case is_whitespace_only(Text) of
true ->
tokenize_loop(Remaining@6, New_pos@6, Tokens);
false ->
Span = {span, Start_pos@5, New_pos@6},
Token@5 = {text, Text, Span},
tokenize_loop(
Remaining@6,
New_pos@6,
[Token@5 | Tokens]
)
end
end
end.
-file("src/ghtml/parser.gleam", 47).
?DOC(" Tokenize a template string into a list of tokens\n").
-spec tokenize(binary()) -> {ok, list(ghtml@types:token())} |
{error, list(ghtml@types:parse_error())}.
tokenize(Input) ->
Pos = {position, 1, 1},
_pipe = tokenize_loop(Input, Pos, []),
gleam@result:map(_pipe, fun(Tokens) -> lists:reverse(Tokens) end).
-file("src/ghtml/parser.gleam", 1136).
-spec extract_metadata_loop(
list(ghtml@types:token()),
list(binary()),
list(list({binary(), binary()})),
list(ghtml@types:token())
) -> {list(binary()), list({binary(), binary()}), list(ghtml@types:token())}.
extract_metadata_loop(Tokens, Imports, Params, Body) ->
case Tokens of
[] ->
{lists:reverse(Imports),
lists:append(lists:reverse(Params)),
lists:reverse(Body)};
[{import, Content, _} | Rest] ->
extract_metadata_loop(Rest, [Content | Imports], Params, Body);
[{params, P, _} | Rest@1] ->
extract_metadata_loop(Rest@1, Imports, [P | Params], Body);
[Token | Rest@2] ->
extract_metadata_loop(Rest@2, Imports, Params, [Token | Body])
end.
-file("src/ghtml/parser.gleam", 1130).
?DOC(" Extract imports and params from token list, returning remaining body tokens\n").
-spec extract_metadata(list(ghtml@types:token())) -> {list(binary()),
list({binary(), binary()}),
list(ghtml@types:token())}.
extract_metadata(Tokens) ->
extract_metadata_loop(Tokens, [], [], []).
-file("src/ghtml/parser.gleam", 1535).
?DOC(" Get children list from a frame\n").
-spec get_frame_children(stack_frame()) -> list(ghtml@types:node_()).
get_frame_children(Frame) ->
case Frame of
{element_frame, _, _, Children, _} ->
Children;
{if_frame, _, Nodes, _, false, _} ->
Nodes;
{if_frame, _, _, Nodes@1, true, _} ->
Nodes@1;
{each_frame, _, _, _, Body, _} ->
Body;
{case_frame, _, _, Body@1, _, _} ->
Body@1
end.
-file("src/ghtml/parser.gleam", 1564).
?DOC(" Set children list on a frame\n").
-spec set_frame_children(stack_frame(), list(ghtml@types:node_())) -> stack_frame().
set_frame_children(Frame, Children) ->
case Frame of
{element_frame, Tag, Attrs, _, Span} ->
{element_frame, Tag, Attrs, Children, Span};
{if_frame, Cond, _, Else_n, false, Span@1} ->
{if_frame, Cond, Children, Else_n, false, Span@1};
{if_frame, Cond@1, Then_n, _, true, Span@2} ->
{if_frame, Cond@1, Then_n, Children, true, Span@2};
{each_frame, Coll, Item, Idx, _, Span@3} ->
{each_frame, Coll, Item, Idx, Children, Span@3};
{case_frame, Expr, Pat, _, Br, Span@4} ->
{case_frame, Expr, Pat, Children, Br, Span@4}
end.
-file("src/ghtml/parser.gleam", 1513).
?DOC(" Add a node to the top frame's children, or create a root frame if stack is empty\n").
-spec add_node_to_stack(ghtml@types:node_(), list(stack_frame())) -> list(stack_frame()).
add_node_to_stack(Node, Stack) ->
case Stack of
[] ->
[{element_frame,
<<""/utf8>>,
[],
[Node],
{span, {position, 1, 1}, {position, 1, 1}}}];
[Frame | Rest] ->
New_children = get_frame_children(Frame),
Updated_frame = set_frame_children(Frame, [Node | New_children]),
[Updated_frame | Rest]
end.
-file("src/ghtml/parser.gleam", 1622).
?DOC(" Create an error for an unclosed frame\n").
-spec unclosed_frame_error(stack_frame()) -> ghtml@types:parse_error().
unclosed_frame_error(Frame) ->
case Frame of
{element_frame, Tag, _, _, Span} ->
{parse_error,
Span,
<<<<"Unclosed element <"/utf8, Tag/binary>>/binary, ">"/utf8>>};
{if_frame, _, _, _, _, Span@1} ->
{parse_error, Span@1, <<"Unclosed {#if} block"/utf8>>};
{each_frame, _, _, _, _, Span@2} ->
{parse_error, Span@2, <<"Unclosed {#each} block"/utf8>>};
{case_frame, _, _, _, _, Span@3} ->
{parse_error, Span@3, <<"Unclosed {#case} block"/utf8>>}
end.
-file("src/ghtml/parser.gleam", 1163).
?DOC(
" Build an AST from a list of tokens.\n"
" Uses a stack to track nesting. Each frame on the stack has its own children list.\n"
" When we close a frame, we create a node with its accumulated children and add it\n"
" to the parent frame's children (or to the result if no parent).\n"
).
-spec build_ast(
list(ghtml@types:token()),
list(stack_frame()),
list(ghtml@types:parse_error())
) -> {ok, list(ghtml@types:node_())} | {error, list(ghtml@types:parse_error())}.
build_ast(Tokens, Stack, Errors) ->
case Tokens of
[] ->
case {Stack, Errors} of
{[], []} ->
{ok, []};
{[], _} ->
{error, lists:reverse(Errors)};
{[{element_frame, <<""/utf8>>, _, Children, _}], []} ->
{ok, lists:reverse(Children)};
{[{element_frame, <<""/utf8>>, _, _, _}], _} ->
{error, lists:reverse(Errors)};
{[Frame | _], _} ->
Error = unclosed_frame_error(Frame),
{error, lists:reverse([Error | Errors])}
end;
[Token | Rest] ->
case Token of
{html_open, Tag, Attrs, true, Span} ->
Node = {element, Tag, Attrs, [], Span},
Stack@1 = add_node_to_stack(Node, Stack),
build_ast(Rest, Stack@1, Errors);
{html_open, Tag@1, Attrs@1, false, Span@1} ->
Frame@1 = {element_frame, Tag@1, Attrs@1, [], Span@1},
build_ast(Rest, [Frame@1 | Stack], Errors);
{html_close, Close_tag, Close_span} ->
case Stack of
[{element_frame, Open_tag, Attrs@2, Children@1, Span@2} |
Rest_stack] ->
case Open_tag =:= Close_tag of
true ->
Combined_span = {span,
erlang:element(2, Span@2),
erlang:element(3, Close_span)},
Node@1 = {element,
Open_tag,
Attrs@2,
lists:reverse(Children@1),
Combined_span},
New_stack = add_node_to_stack(
Node@1,
Rest_stack
),
build_ast(Rest, New_stack, Errors);
false ->
Error@1 = {parse_error,
Close_span,
<<<<<<<<"Mismatched closing tag: expected </"/utf8,
Open_tag/binary>>/binary,
"> but found </"/utf8>>/binary,
Close_tag/binary>>/binary,
">"/utf8>>},
build_ast(Rest, Stack, [Error@1 | Errors])
end;
_ ->
Error@2 = {parse_error,
Close_span,
<<<<"Unexpected closing tag </"/utf8,
Close_tag/binary>>/binary,
">"/utf8>>},
build_ast(Rest, Stack, [Error@2 | Errors])
end;
{text, Content, Span@3} ->
Node@2 = {text_node, Content, Span@3},
Stack@2 = add_node_to_stack(Node@2, Stack),
build_ast(Rest, Stack@2, Errors);
{expr, Content@1, Span@4} ->
Node@3 = {expr_node, Content@1, Span@4},
Stack@3 = add_node_to_stack(Node@3, Stack),
build_ast(Rest, Stack@3, Errors);
{if_start, Condition, Span@5} ->
Frame@2 = {if_frame, Condition, [], [], false, Span@5},
build_ast(Rest, [Frame@2 | Stack], Errors);
{'else', Else_span} ->
case Stack of
[{if_frame, Cond, Then_nodes, _, false, Span@6} |
Rest_stack@1] ->
New_frame = {if_frame,
Cond,
lists:reverse(Then_nodes),
[],
true,
Span@6},
build_ast(Rest, [New_frame | Rest_stack@1], Errors);
_ ->
Error@3 = {parse_error,
Else_span,
<<"{:else} without matching {#if}"/utf8>>},
build_ast(Rest, Stack, [Error@3 | Errors])
end;
{if_end, End_span} ->
case Stack of
[{if_frame,
Cond@1,
Then_nodes@1,
Else_nodes,
In_else,
Span@7} |
Rest_stack@2] ->
{Final_then, Final_else} = case In_else of
true ->
{Then_nodes@1, lists:reverse(Else_nodes)};
false ->
{lists:reverse(Then_nodes@1), []}
end,
Combined_span@1 = {span,
erlang:element(2, Span@7),
erlang:element(3, End_span)},
Node@4 = {if_node,
Cond@1,
Final_then,
Final_else,
Combined_span@1},
New_stack@1 = add_node_to_stack(
Node@4,
Rest_stack@2
),
build_ast(Rest, New_stack@1, Errors);
_ ->
Error@4 = {parse_error,
End_span,
<<"{/if} without matching {#if}"/utf8>>},
build_ast(Rest, Stack, [Error@4 | Errors])
end;
{each_start, Collection, Item, Index, Span@8} ->
Frame@3 = {each_frame, Collection, Item, Index, [], Span@8},
build_ast(Rest, [Frame@3 | Stack], Errors);
{each_end, End_span@1} ->
case Stack of
[{each_frame, Coll, Item@1, Index@1, Body, Span@9} |
Rest_stack@3] ->
Combined_span@2 = {span,
erlang:element(2, Span@9),
erlang:element(3, End_span@1)},
Node@5 = {each_node,
Coll,
Item@1,
Index@1,
lists:reverse(Body),
Combined_span@2},
New_stack@2 = add_node_to_stack(
Node@5,
Rest_stack@3
),
build_ast(Rest, New_stack@2, Errors);
_ ->
Error@5 = {parse_error,
End_span@1,
<<"{/each} without matching {#each}"/utf8>>},
build_ast(Rest, Stack, [Error@5 | Errors])
end;
{case_start, Expr, Span@10} ->
Frame@4 = {case_frame, Expr, none, [], [], Span@10},
build_ast(Rest, [Frame@4 | Stack], Errors);
{case_pattern, Pattern, Pattern_span} ->
case Stack of
[{case_frame,
Expr@1,
Curr_pat,
Body@1,
Branches,
Span@11} |
Rest_stack@4] ->
New_branches = case Curr_pat of
{some, Prev_pattern} ->
Branch = {case_branch,
Prev_pattern,
lists:reverse(Body@1),
Pattern_span},
[Branch | Branches];
none ->
Branches
end,
New_frame@1 = {case_frame,
Expr@1,
{some, Pattern},
[],
New_branches,
Span@11},
build_ast(
Rest,
[New_frame@1 | Rest_stack@4],
Errors
);
_ ->
Error@6 = {parse_error,
Pattern_span,
<<"Case pattern outside of {#case}"/utf8>>},
build_ast(Rest, Stack, [Error@6 | Errors])
end;
{case_end, End_span@2} ->
case Stack of
[{case_frame,
Expr@2,
Curr_pat@1,
Body@2,
Branches@1,
Span@12} |
Rest_stack@5] ->
Final_branches = case Curr_pat@1 of
{some, Pattern@1} ->
Branch@1 = {case_branch,
Pattern@1,
lists:reverse(Body@2),
End_span@2},
lists:reverse([Branch@1 | Branches@1]);
none ->
lists:reverse(Branches@1)
end,
Combined_span@3 = {span,
erlang:element(2, Span@12),
erlang:element(3, End_span@2)},
Node@6 = {case_node,
Expr@2,
Final_branches,
Combined_span@3},
New_stack@3 = add_node_to_stack(
Node@6,
Rest_stack@5
),
build_ast(Rest, New_stack@3, Errors);
_ ->
Error@7 = {parse_error,
End_span@2,
<<"{/case} without matching {#case}"/utf8>>},
build_ast(Rest, Stack, [Error@7 | Errors])
end;
{comment, _} ->
build_ast(Rest, Stack, Errors);
{import, _, _} ->
build_ast(Rest, Stack, Errors);
{params, _, _} ->
build_ast(Rest, Stack, Errors)
end
end.
-file("src/ghtml/parser.gleam", 1114).
?DOC(" Parse a template string into a Template AST\n").
-spec parse(binary()) -> {ok, ghtml@types:template()} |
{error, list(ghtml@types:parse_error())}.
parse(Input) ->
case tokenize(Input) of
{error, Errors} ->
{error, Errors};
{ok, Tokens} ->
{Imports, Params, Body_tokens} = extract_metadata(Tokens),
case build_ast(Body_tokens, [], []) of
{error, Errors@1} ->
{error, Errors@1};
{ok, Body} ->
{ok, {template, Imports, Params, Body}}
end
end.
-file("src/ghtml/parser.gleam", 1643).
?DOC(" Format a single parse error with source context\n").
-spec format_error(ghtml@types:parse_error(), binary()) -> binary().
format_error(Error, Source) ->
{parse_error, Span, Message} = Error,
Lines = gleam@string:split(Source, <<"\n"/utf8>>),
Line_num = erlang:element(2, erlang:element(2, Span)),
Line_content = begin
_pipe = Lines,
_pipe@1 = gleam@list:drop(_pipe, Line_num - 1),
_pipe@2 = gleam@list:first(_pipe@1),
gleam@result:unwrap(_pipe@2, <<""/utf8>>)
end,
Line_prefix = <<(erlang:integer_to_binary(Line_num))/binary, " | "/utf8>>,
Pointer_offset = gleam@string:repeat(
<<" "/utf8>>,
(string:length(Line_prefix) + erlang:element(3, erlang:element(2, Span)))
- 1
),
Pointer = <<Pointer_offset/binary, "^"/utf8>>,
<<<<<<<<<<<<<<<<<<<<"Error at line "/utf8,
(erlang:integer_to_binary(Line_num))/binary>>/binary,
", column "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:element(
3,
erlang:element(2, Span)
)
))/binary>>/binary,
": "/utf8>>/binary,
Message/binary>>/binary,
"\n"/utf8>>/binary,
Line_prefix/binary>>/binary,
Line_content/binary>>/binary,
"\n"/utf8>>/binary,
Pointer/binary>>.
-file("src/ghtml/parser.gleam", 1672).
?DOC(" Format multiple parse errors\n").
-spec format_errors(list(ghtml@types:parse_error()), binary()) -> binary().
format_errors(Errors, Source) ->
_pipe = Errors,
_pipe@1 = gleam@list:map(_pipe, fun(Err) -> format_error(Err, Source) end),
gleam@string:join(_pipe@1, <<"\n\n"/utf8>>).