src/gleeam_code@internal@spec_parser.erl

-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.