-module(gleeam_code@internal@spec_parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeam_code/internal/spec_parser.gleam").
-export([erlang_type_to_gleam/1, uses_tree_node/1, uses_list_node/1, format_module_name/2, split_params/1, to_snake_case/1, parse_erlang_spec/1]).
-export_type([function_spec/0, param/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(false).
-type function_spec() :: {function_spec, binary(), list(param()), binary()}.
-type param() :: {param, binary(), binary()}.
-file("src/gleeam_code/internal/spec_parser.gleam", 20).
?DOC(false).
-spec erlang_type_to_gleam(binary()) -> binary().
erlang_type_to_gleam(Erl_type) ->
Trimmed = gleam@string:trim(Erl_type),
case Trimmed of
<<"integer()"/utf8>> ->
<<"Int"/utf8>>;
<<"float()"/utf8>> ->
<<"Float"/utf8>>;
<<"boolean()"/utf8>> ->
<<"Bool"/utf8>>;
<<"unicode:chardata()"/utf8>> ->
<<"String"/utf8>>;
<<"unicode:unicode_binary()"/utf8>> ->
<<"String"/utf8>>;
<<"char()"/utf8>> ->
<<"Int"/utf8>>;
<<"#tree_node{}"/utf8>> ->
<<"Option(TreeNode)"/utf8>>;
<<"'null' | #tree_node{}"/utf8>> ->
<<"Option(TreeNode)"/utf8>>;
<<"#tree_node{} | 'null'"/utf8>> ->
<<"Option(TreeNode)"/utf8>>;
<<"null | #tree_node{}"/utf8>> ->
<<"Option(TreeNode)"/utf8>>;
<<"#tree_node{} | null"/utf8>> ->
<<"Option(TreeNode)"/utf8>>;
<<"#list_node{}"/utf8>> ->
<<"Option(ListNode)"/utf8>>;
<<"'null' | #list_node{}"/utf8>> ->
<<"Option(ListNode)"/utf8>>;
<<"#list_node{} | 'null'"/utf8>> ->
<<"Option(ListNode)"/utf8>>;
<<"null | #list_node{}"/utf8>> ->
<<"Option(ListNode)"/utf8>>;
<<"#list_node{} | null"/utf8>> ->
<<"Option(ListNode)"/utf8>>;
_ ->
case gleam_stdlib:string_starts_with(Trimmed, <<"["/utf8>>) andalso gleam_stdlib:string_ends_with(
Trimmed,
<<"]"/utf8>>
) of
true ->
Inner = begin
_pipe = Trimmed,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1)
end,
<<<<"List("/utf8, (erlang_type_to_gleam(Inner))/binary>>/binary,
")"/utf8>>;
false ->
Trimmed
end
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 53).
?DOC(false).
-spec uses_tree_node(function_spec()) -> boolean().
uses_tree_node(Spec) ->
In_params = gleam@list:any(
erlang:element(3, Spec),
fun(P) ->
gleam_stdlib:contains_string(
erlang:element(3, P),
<<"TreeNode"/utf8>>
)
end
),
In_params orelse gleam_stdlib:contains_string(
erlang:element(4, Spec),
<<"TreeNode"/utf8>>
).
-file("src/gleeam_code/internal/spec_parser.gleam", 59).
?DOC(false).
-spec uses_list_node(function_spec()) -> boolean().
uses_list_node(Spec) ->
In_params = gleam@list:any(
erlang:element(3, Spec),
fun(P) ->
gleam_stdlib:contains_string(
erlang:element(3, P),
<<"ListNode"/utf8>>
)
end
),
In_params orelse gleam_stdlib:contains_string(
erlang:element(4, Spec),
<<"ListNode"/utf8>>
).
-file("src/gleeam_code/internal/spec_parser.gleam", 73).
?DOC(false).
-spec format_module_name(binary(), binary()) -> binary().
format_module_name(Frontend_id, Title_slug) ->
Padded_id = gleam@string:pad_start(Frontend_id, 4, <<"0"/utf8>>),
Snake_slug = gleam@string:replace(Title_slug, <<"-"/utf8>>, <<"_"/utf8>>),
<<<<<<"p"/utf8, Padded_id/binary>>/binary, "_"/utf8>>/binary,
Snake_slug/binary>>.
-file("src/gleeam_code/internal/spec_parser.gleam", 79).
?DOC(false).
-spec find_spec_line(list(binary())) -> {ok, binary()} | {error, binary()}.
find_spec_line(Lines) ->
case Lines of
[] ->
{error, <<"No -spec line found in snippet"/utf8>>};
[Line | Rest] ->
case gleam_stdlib:string_starts_with(Line, <<"-spec "/utf8>>) of
true ->
{ok, Line};
false ->
find_spec_line(Rest)
end
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 143).
?DOC(false).
-spec hd_char(list(binary())) -> binary().
hd_char(Chars) ->
case Chars of
[C | _] ->
C;
[] ->
<<""/utf8>>
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 121).
?DOC(false).
-spec do_split_params(list(binary()), integer(), binary(), list(binary())) -> list(binary()).
do_split_params(Chars, Depth, Current, Acc) ->
case Chars of
[] ->
case gleam@string:trim(Current) of
<<""/utf8>> ->
lists:reverse(Acc);
Trimmed ->
lists:reverse([Trimmed | Acc])
end;
[<<","/utf8>> | Rest] when Depth =:= 0 ->
do_split_params(
Rest,
0,
<<""/utf8>>,
[gleam@string:trim(Current) | Acc]
);
[<<"("/utf8>> | Rest@1] ->
do_split_params(
Rest@1,
Depth + 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[<<"["/utf8>> | Rest@1] ->
do_split_params(
Rest@1,
Depth + 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[<<"{"/utf8>> | Rest@1] ->
do_split_params(
Rest@1,
Depth + 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[<<")"/utf8>> | Rest@2] ->
do_split_params(
Rest@2,
Depth - 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[<<"]"/utf8>> | Rest@2] ->
do_split_params(
Rest@2,
Depth - 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[<<"}"/utf8>> | Rest@2] ->
do_split_params(
Rest@2,
Depth - 1,
<<Current/binary, (hd_char(Chars))/binary>>,
Acc
);
[C | Rest@3] ->
do_split_params(Rest@3, Depth, <<Current/binary, C/binary>>, Acc)
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 117).
?DOC(false).
-spec split_params(binary()) -> list(binary()).
split_params(S) ->
do_split_params(gleam@string:to_graphemes(S), 0, <<""/utf8>>, []).
-file("src/gleeam_code/internal/spec_parser.gleam", 181).
?DOC(false).
-spec is_upper(binary()) -> boolean().
is_upper(C) ->
Lower = string:lowercase(C),
(C /= Lower) andalso (Lower /= C).
-file("src/gleeam_code/internal/spec_parser.gleam", 162).
?DOC(false).
-spec do_snake_case(list(binary()), list(binary()), boolean()) -> list(binary()).
do_snake_case(Chars, Acc, Is_start) ->
case Chars of
[] ->
lists:reverse(Acc);
[C | Rest] ->
case is_upper(C) of
true ->
case Is_start of
true ->
do_snake_case(Rest, [C | Acc], false);
false ->
do_snake_case(Rest, [C, <<"_"/utf8>> | Acc], false)
end;
false ->
do_snake_case(Rest, [C | Acc], false)
end
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 65).
?DOC(false).
-spec to_snake_case(binary()) -> binary().
to_snake_case(Name) ->
_pipe = Name,
_pipe@1 = gleam@string:to_graphemes(_pipe),
_pipe@2 = do_snake_case(_pipe@1, [], true),
_pipe@3 = gleam@string:join(_pipe@2, <<""/utf8>>),
string:lowercase(_pipe@3).
-file("src/gleeam_code/internal/spec_parser.gleam", 150).
?DOC(false).
-spec parse_single_param(binary()) -> param().
parse_single_param(Param_str) ->
case gleam@string:split_once(Param_str, <<" :: "/utf8>>) of
{ok, {Name, Type_str}} ->
{param,
to_snake_case(Name),
erlang_type_to_gleam(gleam@string:trim(Type_str))};
{error, _} ->
{param,
<<"arg"/utf8>>,
erlang_type_to_gleam(gleam@string:trim(Param_str))}
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 110).
?DOC(false).
-spec parse_params(binary()) -> list(param()).
parse_params(Params_str) ->
case gleam@string:trim(Params_str) of
<<""/utf8>> ->
[];
S ->
_pipe = split_params(S),
gleam@list:map(_pipe, fun parse_single_param/1)
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 90).
?DOC(false).
-spec parse_spec_line(binary()) -> {ok, function_spec()} | {error, binary()}.
parse_spec_line(Line) ->
Trimmed = gleam@string:drop_start(Line, string:length(<<"-spec "/utf8>>)),
case gleam@string:split_once(Trimmed, <<"("/utf8>>) of
{error, _} ->
{error, <<"Invalid spec: no opening paren"/utf8>>};
{ok, {Name, Rest}} ->
case gleam@string:split_once(Rest, <<") ->"/utf8>>) of
{error, _} ->
{error, <<"Invalid spec: no ') ->' found"/utf8>>};
{ok, {Params_str, Return_str}} ->
Params = parse_params(Params_str),
Return_type = begin
_pipe = Return_str,
_pipe@1 = gleam@string:trim(_pipe),
_pipe@2 = gleam@string:drop_end(_pipe@1, 1),
erlang_type_to_gleam(_pipe@2)
end,
{ok, {function_spec, Name, Params, Return_type}}
end
end.
-file("src/gleeam_code/internal/spec_parser.gleam", 12).
?DOC(false).
-spec parse_erlang_spec(binary()) -> {ok, function_spec()} | {error, binary()}.
parse_erlang_spec(Snippet) ->
Lines = gleam@string:split(Snippet, <<"\n"/utf8>>),
case find_spec_line(Lines) of
{error, Err} ->
{error, Err};
{ok, Spec_line} ->
parse_spec_line(Spec_line)
end.