src/gleeam_code@internal@stdlib_bundler.erl

-module(gleeam_code@internal@stdlib_bundler).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeam_code/internal/stdlib_bundler.gleam").
-export([bundle/2]).

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

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 114).
?DOC(false).
-spec is_local_call_start(binary(), list(binary())) -> boolean().
is_local_call_start(C, Current) ->
    case Current of
        [] ->
            gleeam_code@internal@char:is_lowercase(C) orelse (C =:= <<"_"/utf8>>);

        _ ->
            gleeam_code@internal@char:is_identifier(C)
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 83).
?DOC(false).
-spec scan_for_local_calls(list(binary()), list(binary()), list(binary())) -> list(binary()).
scan_for_local_calls(Chars, Current, Acc) ->
    case Chars of
        [] ->
            Acc;

        [C | Rest] ->
            case is_local_call_start(C, Current) of
                true ->
                    scan_for_local_calls(Rest, [C | Current], Acc);

                false ->
                    case C of
                        <<"("/utf8>> ->
                            Name = erlang:list_to_binary(lists:reverse(Current)),
                            case Name of
                                <<""/utf8>> ->
                                    scan_for_local_calls(Rest, [], Acc);

                                _ ->
                                    case gleam_stdlib:contains_string(
                                        Name,
                                        <<":"/utf8>>
                                    )
                                    orelse gleam_stdlib:contains_string(
                                        Name,
                                        <<"@"/utf8>>
                                    ) of
                                        true ->
                                            scan_for_local_calls(Rest, [], Acc);

                                        false ->
                                            scan_for_local_calls(
                                                Rest,
                                                [],
                                                [Name | Acc]
                                            )
                                    end
                            end;

                        _ ->
                            scan_for_local_calls(Rest, [], Acc)
                    end
            end
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 76).
?DOC(false).
-spec extract_called_identifiers(binary()) -> list(binary()).
extract_called_identifiers(Body) ->
    _pipe = Body,
    _pipe@1 = gleam@string:to_graphemes(_pipe),
    _pipe@2 = scan_for_local_calls(_pipe@1, [], []),
    gleam@list:unique(_pipe@2).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 121).
?DOC(false).
-spec is_defined_in_source(binary(), binary()) -> boolean().
is_defined_in_source(Name, Source) ->
    gleam_stdlib:contains_string(
        Source,
        <<<<"\n"/utf8, Name/binary>>/binary, "("/utf8>>
    ).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 63).
?DOC(false).
-spec find_local_calls(binary(), binary(), binary()) -> list(gleeam_code@internal@stdlib_scanner:stdlib_call()).
find_local_calls(Func_body, Module, Module_source) ->
    Exported = gleeam_code@internal@stdlib_extractor:list_exported(
        Module_source
    ),
    All_identifiers = extract_called_identifiers(Func_body),
    _pipe = All_identifiers,
    _pipe@1 = gleam@list:filter(
        _pipe,
        fun(Name) -> not gleam@list:contains(Exported, Name) end
    ),
    _pipe@2 = gleam@list:filter(
        _pipe@1,
        fun(Name@1) -> is_defined_in_source(Name@1, Module_source) end
    ),
    gleam@list:map(_pipe@2, fun(Name@2) -> {stdlib_call, Module, Name@2} end).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 177).
?DOC(false).
-spec do_rename_bare(list(binary()), binary(), binary()) -> binary().
do_rename_bare(Parts, Func_name, Replacement) ->
    case Parts of
        [] ->
            <<""/utf8>>;

        [Only] ->
            Only;

        [First | Rest] ->
            Should_rename = case gleam@string:last(First) of
                {ok, C} ->
                    not gleeam_code@internal@char:is_identifier_no_at(C);

                {error, _} ->
                    true
            end,
            case Should_rename of
                true ->
                    <<<<First/binary, Replacement/binary>>/binary,
                        (do_rename_bare(Rest, Func_name, Replacement))/binary>>;

                false ->
                    <<<<<<First/binary, Func_name/binary>>/binary, "("/utf8>>/binary,
                        (do_rename_bare(Rest, Func_name, Replacement))/binary>>
            end
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 168).
?DOC(false).
-spec rename_bare_calls(binary(), binary(), binary()) -> binary().
rename_bare_calls(Code, Func_name, Replacement) ->
    Target = <<Func_name/binary, "("/utf8>>,
    do_rename_bare(gleam@string:split(Code, Target), Func_name, Replacement).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 219).
?DOC(false).
-spec make_local_name(binary(), binary()) -> binary().
make_local_name(Module, Function) ->
    Safe_module = begin
        _pipe = Module,
        _pipe@1 = gleam@string:replace(_pipe, <<"@"/utf8>>, <<"_"/utf8>>),
        gleam@string:replace(_pipe@1, <<"."/utf8>>, <<"_"/utf8>>)
    end,
    <<<<Safe_module/binary, "__"/utf8>>/binary, Function/binary>>.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 152).
?DOC(false).
-spec rename_local_calls(
    binary(),
    binary(),
    list(gleeam_code@internal@stdlib_scanner:stdlib_call())
) -> binary().
rename_local_calls(Code, Current_module, All_calls) ->
    Local_funcs = begin
        _pipe = All_calls,
        _pipe@1 = gleam@list:filter(
            _pipe,
            fun(C) -> erlang:element(2, C) =:= Current_module end
        ),
        _pipe@2 = gleam@list:map(
            _pipe@1,
            fun(C@1) -> erlang:element(3, C@1) end
        ),
        gleam@list:unique(_pipe@2)
    end,
    gleam@list:fold(
        Local_funcs,
        Code,
        fun(Acc, Func_name) ->
            Renamed = <<(make_local_name(Current_module, Func_name))/binary,
                "("/utf8>>,
            rename_bare_calls(Acc, Func_name, Renamed)
        end
    ).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 203).
?DOC(false).
-spec rename_external_calls(
    binary(),
    list(gleeam_code@internal@stdlib_scanner:stdlib_call())
) -> binary().
rename_external_calls(Code, Calls) ->
    gleam@list:fold(
        Calls,
        Code,
        fun(Acc, Call) ->
            Original = <<<<<<(erlang:element(2, Call))/binary, ":"/utf8>>/binary,
                    (erlang:element(3, Call))/binary>>/binary,
                "("/utf8>>,
            Renamed = <<(make_local_name(
                    erlang:element(2, Call),
                    erlang:element(3, Call)
                ))/binary,
                "("/utf8>>,
            gleam@string:replace(Acc, Original, Renamed)
        end
    ).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 143).
?DOC(false).
-spec rename_in_extracted(
    binary(),
    binary(),
    list(gleeam_code@internal@stdlib_scanner:stdlib_call())
) -> binary().
rename_in_extracted(Code, Current_module, All_calls) ->
    Code1 = rename_external_calls(Code, All_calls),
    rename_local_calls(Code1, Current_module, All_calls).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 211).
?DOC(false).
-spec rename_calls_in_code(
    binary(),
    list(gleeam_code@internal@stdlib_scanner:stdlib_call())
) -> binary().
rename_calls_in_code(Code, Calls) ->
    gleam@list:fold(
        Calls,
        Code,
        fun(Acc, Call) ->
            Original = <<<<<<(erlang:element(2, Call))/binary, ":"/utf8>>/binary,
                    (erlang:element(3, Call))/binary>>/binary,
                "("/utf8>>,
            Renamed = <<(make_local_name(
                    erlang:element(2, Call),
                    erlang:element(3, Call)
                ))/binary,
                "("/utf8>>,
            gleam@string:replace(Acc, Original, Renamed)
        end
    ).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 227).
?DOC(false).
-spec module_to_path(binary(), binary()) -> binary().
module_to_path(Module, Stdlib_dir) ->
    <<<<<<Stdlib_dir/binary, "/"/utf8>>/binary, Module/binary>>/binary,
        ".erl"/utf8>>.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 47).
?DOC(false).
-spec find_deps_of(gleeam_code@internal@stdlib_scanner:stdlib_call(), binary()) -> list(gleeam_code@internal@stdlib_scanner:stdlib_call()).
find_deps_of(Call, Stdlib_dir) ->
    Module_file = module_to_path(erlang:element(2, Call), Stdlib_dir),
    case gleeam_code@internal@file:read(Module_file) of
        {error, _} ->
            [];

        {ok, Source} ->
            case gleeam_code@internal@stdlib_extractor:extract_function(
                Source,
                erlang:element(3, Call)
            ) of
                {error, _} ->
                    [];

                {ok, Func_body} ->
                    Inner_calls = gleeam_code@internal@stdlib_scanner:scan(
                        Func_body
                    ),
                    Local_calls = find_local_calls(
                        Func_body,
                        erlang:element(2, Call),
                        Source
                    ),
                    lists:append(Inner_calls, Local_calls)
            end
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 21).
?DOC(false).
-spec resolve_transitive(
    list(gleeam_code@internal@stdlib_scanner:stdlib_call()),
    binary(),
    list(gleeam_code@internal@stdlib_scanner:stdlib_call())
) -> list(gleeam_code@internal@stdlib_scanner:stdlib_call()).
resolve_transitive(Worklist, Stdlib_dir, Resolved) ->
    case Worklist of
        [] ->
            Resolved;

        _ ->
            New_calls = begin
                _pipe = Worklist,
                _pipe@1 = gleam@list:flat_map(
                    _pipe,
                    fun(Call) -> find_deps_of(Call, Stdlib_dir) end
                ),
                _pipe@2 = gleam@list:filter(
                    _pipe@1,
                    fun(C) -> not gleam@list:contains(Resolved, C) end
                ),
                gleam@list:unique(_pipe@2)
            end,
            case New_calls of
                [] ->
                    Resolved;

                _ ->
                    resolve_transitive(
                        New_calls,
                        Stdlib_dir,
                        lists:append(Resolved, New_calls)
                    )
            end
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 238).
?DOC(false).
-spec add_to_group(list({binary(), list(binary())}), binary(), binary()) -> list({binary(),
    list(binary())}).
add_to_group(Groups, Module, Func) ->
    case Groups of
        [] ->
            [{Module, [Func]}];

        [{M, Funcs} | Rest] ->
            case M =:= Module of
                true ->
                    case gleam@list:contains(Funcs, Func) of
                        true ->
                            [{M, Funcs} | Rest];

                        false ->
                            [{M, [Func | Funcs]} | Rest]
                    end;

                false ->
                    [{M, Funcs} | add_to_group(Rest, Module, Func)]
            end
    end.

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 231).
?DOC(false).
-spec group_by_module(list(gleeam_code@internal@stdlib_scanner:stdlib_call())) -> list({binary(),
    list(binary())}).
group_by_module(Calls) ->
    _pipe = Calls,
    gleam@list:fold(
        _pipe,
        [],
        fun(Acc, Call) ->
            add_to_group(Acc, erlang:element(2, Call), erlang:element(3, Call))
        end
    ).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 125).
?DOC(false).
-spec extract_and_rename(
    list(gleeam_code@internal@stdlib_scanner:stdlib_call()),
    binary()
) -> binary().
extract_and_rename(Calls, Stdlib_dir) ->
    Grouped = group_by_module(Calls),
    _pipe = Grouped,
    _pipe@1 = gleam@list:map(
        _pipe,
        fun(Group) ->
            {Module, Func_names} = Group,
            Module_file = module_to_path(Module, Stdlib_dir),
            case gleeam_code@internal@file:read(Module_file) of
                {error, _} ->
                    <<""/utf8>>;

                {ok, Source} ->
                    Extracted = gleeam_code@internal@stdlib_extractor:extract_functions(
                        Source,
                        Func_names
                    ),
                    rename_in_extracted(Extracted, Module, Calls)
            end
        end
    ),
    _pipe@2 = gleam@list:filter(_pipe@1, fun(S) -> S /= <<""/utf8>> end),
    gleam@string:join(_pipe@2, <<"\n\n"/utf8>>).

-file("src/gleeam_code/internal/stdlib_bundler.gleam", 8).
?DOC(false).
-spec bundle(binary(), binary()) -> binary().
bundle(Erl_code, Stdlib_dir) ->
    Calls = gleeam_code@internal@stdlib_scanner:scan(Erl_code),
    case Calls of
        [] ->
            Erl_code;

        _ ->
            All_calls = resolve_transitive(Calls, Stdlib_dir, Calls),
            Bundled_code = extract_and_rename(All_calls, Stdlib_dir),
            Renamed_solution = rename_calls_in_code(Erl_code, All_calls),
            <<<<Bundled_code/binary, "\n"/utf8>>/binary,
                Renamed_solution/binary>>
    end.