-module(rally@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/rally/parser.gleam").
-export([parse_page/2, parse_client_context/1]).
-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(
" Page module parser using Glance AST.\n"
" Parses page source files to extract the page contract:\n"
" custom types (ToServer, ToClient) with full variant/field info,\n"
" and function presence (server_update, server_init, load, etc.).\n"
).
-file("src/rally/parser.gleam", 204).
-spec has_function(list(glance:definition(glance:function_())), binary()) -> boolean().
has_function(Functions, Name) ->
gleam@list:any(
Functions,
fun(Def) ->
(erlang:element(3, erlang:element(3, Def)) =:= Name) andalso (erlang:element(
4,
erlang:element(3, Def)
)
=:= public)
end
).
-file("src/rally/parser.gleam", 229).
?DOC(
" Detect presence and variant of `pub const page_auth = auth.Required/Optional`.\n"
" Returns #(has_page_auth, page_auth_required).\n"
).
-spec detect_page_auth(list(glance:definition(glance:constant()))) -> {boolean(),
boolean()}.
detect_page_auth(Constants) ->
_pipe = gleam@list:find_map(
Constants,
fun(Def) ->
{definition, _, Constant} = Def,
case erlang:element(3, Constant) of
<<"page_auth"/utf8>> ->
Is_required = case erlang:element(6, Constant) of
{field_access, _, {variable, _, _}, <<"Required"/utf8>>} ->
true;
_ ->
false
end,
{ok, {true, Is_required}};
_ ->
{error, nil}
end
end
),
gleam@result:unwrap(_pipe, {false, false}).
-file("src/rally/parser.gleam", 300).
-spec type_contains_name(glance:type(), binary()) -> boolean().
type_contains_name(T, Name) ->
case T of
{named_type, _, N, _, Parameters} ->
(N =:= Name) orelse gleam@list:any(
Parameters,
fun(P) -> type_contains_name(P, Name) end
);
{tuple_type, _, Elements} ->
gleam@list:any(Elements, fun(E) -> type_contains_name(E, Name) end);
{function_type, _, Parameters@1, Return} ->
gleam@list:any(
Parameters@1,
fun(P@1) -> type_contains_name(P@1, Name) end
)
orelse type_contains_name(Return, Name);
{variable_type, _, _} ->
false;
{hole_type, _, _} ->
false
end.
-file("src/rally/parser.gleam", 281).
-spec update_returns_client_context_msg(
list(glance:definition(glance:function_()))
) -> boolean().
update_returns_client_context_msg(Functions) ->
case gleam@list:find(
Functions,
fun(Def) ->
(erlang:element(3, erlang:element(3, Def)) =:= <<"update"/utf8>>)
andalso (erlang:element(4, erlang:element(3, Def)) =:= public)
end
) of
{ok, Def@1} ->
case erlang:element(6, erlang:element(3, Def@1)) of
{some, {tuple_type, _, [_, _, Third]}} ->
type_contains_name(Third, <<"ClientContextMsg"/utf8>>);
_ ->
false
end;
{error, nil} ->
false
end.
-file("src/rally/parser.gleam", 182).
?DOC(" Extract the source text of a named public function using AST span positions.\n").
-spec extract_function_source(
binary(),
list(glance:definition(glance:function_())),
binary()
) -> binary().
extract_function_source(Source, Functions, Name) ->
case gleam@list:find(
Functions,
fun(Def) ->
(erlang:element(3, erlang:element(3, Def)) =:= Name) andalso (erlang:element(
4,
erlang:element(3, Def)
)
=:= public)
end
) of
{error, nil} ->
<<""/utf8>>;
{ok, Func_def} ->
{function, {span, Start, End}, _, _, _, _, _} = erlang:element(
3,
Func_def
),
case string:length(Source) >= End of
true ->
gleam@string:slice(Source, Start, End - Start);
false ->
<<""/utf8>>
end
end.
-file("src/rally/parser.gleam", 249).
?DOC(" Extract parameter names from the `init` function AST.\n").
-spec extract_init_params_from_ast(list(glance:definition(glance:function_()))) -> list(binary()).
extract_init_params_from_ast(Functions) ->
case gleam@list:find(
Functions,
fun(Def) ->
(erlang:element(3, erlang:element(3, Def)) =:= <<"init"/utf8>>)
andalso (erlang:element(4, erlang:element(3, Def)) =:= public)
end
) of
{error, nil} ->
[];
{ok, Func_def} ->
gleam@list:filter_map(
erlang:element(5, erlang:element(3, Func_def)),
fun(Param) -> case Param of
{function_parameter, {some, Label}, _, _} ->
{ok, Label};
{function_parameter, none, {named, Name}, {some, _}} ->
{ok, Name};
_ ->
{error, nil}
end end
)
end.
-file("src/rally/parser.gleam", 220).
-spec has_type_alias(list(glance:definition(glance:type_alias())), binary()) -> boolean().
has_type_alias(Type_aliases, Name) ->
gleam@list:any(
Type_aliases,
fun(Def) -> erlang:element(3, erlang:element(3, Def)) =:= Name end
).
-file("src/rally/parser.gleam", 213).
-spec has_custom_type(list(glance:definition(glance:custom_type())), binary()) -> boolean().
has_custom_type(Custom_types, Name) ->
gleam@list:any(
Custom_types,
fun(Def) -> erlang:element(3, erlang:element(3, Def)) =:= Name end
).
-file("src/rally/parser.gleam", 146).
?DOC(" Extract all variants of a named custom type, with field type info.\n").
-spec extract_variants(
glance:module_(),
binary(),
libero@glance_type_resolver:type_resolver(),
binary()
) -> {ok, list(rally@types:variant_info())} | {error, binary()}.
extract_variants(Ast, Type_name, Resolver, Module_path) ->
case gleam@list:find(
erlang:element(3, Ast),
fun(D) -> erlang:element(3, erlang:element(3, D)) =:= Type_name end
) of
{error, nil} ->
{ok, []};
{ok, Ct_def} ->
Custom_type = erlang:element(3, Ct_def),
gleam@list:try_map(
erlang:element(7, Custom_type),
fun(Variant) ->
gleam@result:'try'(
gleam@list:try_map(
erlang:element(3, Variant),
fun(Field) ->
{Label@1, Type_} = case Field of
{labelled_variant_field, Item, Label} ->
{Label, Item};
{unlabelled_variant_field, Item@1} ->
{<<"value"/utf8>>, Item@1}
end,
Path = <<<<<<<<Module_path/binary, "."/utf8>>/binary,
Type_name/binary>>/binary,
"."/utf8>>/binary,
Label@1/binary>>,
gleam@result:'try'(
libero@glance_type_resolver:type_to_field_type(
Type_,
Resolver,
Module_path,
{reject_unsupported, Path}
),
fun(Ft) ->
{ok, {variant_field, Label@1, Ft}}
end
)
end
),
fun(Fields) ->
{ok,
{variant_info,
erlang:element(2, Variant),
Fields}}
end
)
end
)
end.
-file("src/rally/parser.gleam", 273).
-spec glance_to_string(glance:error()) -> binary().
glance_to_string(Err) ->
case Err of
unexpected_end_of_input ->
<<"unexpected end of input"/utf8>>;
{unexpected_token, _, Position} ->
<<"unexpected token at byte offset "/utf8,
(erlang:integer_to_binary(erlang:element(2, Position)))/binary>>
end.
-file("src/rally/parser.gleam", 21).
?DOC(
" Parse a page module source to extract the contract.\n"
" Uses Glance AST parsing for robust type extraction.\n"
).
-spec parse_page(binary(), binary()) -> {ok, rally@types:page_contract()} |
{error, binary()}.
parse_page(Source, Module_path) ->
gleam@result:'try'(
begin
_pipe = glance:module(Source),
gleam@result:map_error(
_pipe,
fun(E) ->
gleam_stdlib:println_error(
<<"Parse error: "/utf8, (glance_to_string(E))/binary>>
),
<<"Parse error"/utf8>>
end
)
end,
fun(Ast) ->
gleam@result:'try'(
libero@glance_type_resolver:resolver_from_imports(
erlang:element(2, Ast)
),
fun(Resolver) ->
gleam@result:'try'(
extract_variants(
Ast,
<<"Model"/utf8>>,
Resolver,
Module_path
),
fun(Model_variants) ->
gleam@result:'try'(
extract_variants(
Ast,
<<"Msg"/utf8>>,
Resolver,
Module_path
),
fun(Msg_variants) ->
Functions_list = erlang:element(6, Ast),
Has_load = has_function(
Functions_list,
<<"load"/utf8>>
),
Has_init = has_function(
Functions_list,
<<"init"/utf8>>
),
Has_init_loaded = has_function(
Functions_list,
<<"init_loaded"/utf8>>
),
Has_model = has_custom_type(
erlang:element(3, Ast),
<<"Model"/utf8>>
)
orelse has_type_alias(
erlang:element(4, Ast),
<<"Model"/utf8>>
),
Param_names = extract_init_params_from_ast(
Functions_list
),
View_source = extract_function_source(
Source,
Functions_list,
<<"view"/utf8>>
),
Init_source = extract_function_source(
Source,
Functions_list,
<<"init"/utf8>>
),
Update_source = extract_function_source(
Source,
Functions_list,
<<"update"/utf8>>
),
Updates_client_context = update_returns_client_context_msg(
Functions_list
),
{Has_page_auth, Page_auth_required} = detect_page_auth(
erlang:element(5, Ast)
),
Has_authorize = has_function(
Functions_list,
<<"authorize"/utf8>>
),
{ok,
{page_contract,
Model_variants,
Msg_variants,
Has_load,
Has_init,
Has_init_loaded,
Has_model,
Updates_client_context,
Param_names,
Source,
View_source,
Init_source,
Update_source,
Has_page_auth,
Page_auth_required,
Has_authorize}}
end
)
end
)
end
)
end
).
-file("src/rally/parser.gleam", 103).
?DOC(" Parse a client_context.gleam source to extract the contract.\n").
-spec parse_client_context(binary()) -> {ok,
rally@types:client_context_contract()} |
{error, binary()}.
parse_client_context(Source) ->
gleam@result:'try'(
begin
_pipe = glance:module(Source),
gleam@result:map_error(
_pipe,
fun(E) ->
gleam_stdlib:println_error(
<<"Parse error: "/utf8, (glance_to_string(E))/binary>>
),
<<"Parse error"/utf8>>
end
)
end,
fun(Ast) ->
gleam@result:'try'(
libero@glance_type_resolver:resolver_from_imports(
erlang:element(2, Ast)
),
fun(Resolver) ->
gleam@result:'try'(
extract_variants(
Ast,
<<"ClientContext"/utf8>>,
Resolver,
<<"client_context"/utf8>>
),
fun(Context_variants) ->
gleam@result:'try'(
extract_variants(
Ast,
<<"ClientContextMsg"/utf8>>,
Resolver,
<<"client_context"/utf8>>
),
fun(Msg_variants) ->
Functions_list = erlang:element(6, Ast),
Has_init = has_function(
Functions_list,
<<"init"/utf8>>
),
Has_update = has_function(
Functions_list,
<<"update"/utf8>>
),
{ok,
{client_context_contract,
Context_variants,
Msg_variants,
Has_init,
Has_update}}
end
)
end
)
end
)
end
).