Skip to main content

src/molt@internal@path.erl

-module(molt@internal@path).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/molt/internal/path.gleam").
-export([parse/1, to_string/1, with_segment_key_name/3, split_last_segment/1, split_at_last_index/1, drop_last_segment/1, path_to_table_header/1, contains_index/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(false).

-file("src/molt/internal/path.gleam", 119).
?DOC(false).
-spec validate_segments(list(molt@types:path_segment())) -> {ok,
        list(molt@types:path_segment())} |
    {error, molt@error:molt_error()}.
validate_segments(Segments) ->
    case Segments of
        [{index_segment, _} | _] ->
            {error,
                {invalid_path,
                    <<"index segments must follow a key segment"/utf8>>}};

        _ ->
            {ok, Segments}
    end.

-file("src/molt/internal/path.gleam", 237).
?DOC(false).
-spec parse_path_bracket_index(list(binary()), binary()) -> {ok,
        {integer(), list(binary())}} |
    {error, molt@error:molt_error()}.
parse_path_bracket_index(Chars, Acc) ->
    case Chars of
        [] ->
            {error, {invalid_path, <<"unterminated bracket"/utf8>>}};

        [<<"]"/utf8>> | Rest] ->
            case gleam_stdlib:parse_int(Acc) of
                {ok, N} ->
                    {ok, {N, Rest}};

                {error, _} ->
                    {error,
                        {invalid_path,
                            <<"non-integer in brackets: "/utf8, Acc/binary>>}}
            end;

        [Ch | Rest@1] ->
            parse_path_bracket_index(Rest@1, <<Acc/binary, Ch/binary>>)
    end.

-file("src/molt/internal/path.gleam", 270).
?DOC(false).
-spec parse_path_double_quoted(
    list(binary()),
    list(molt@types:path_segment()),
    binary()
) -> {ok, list(molt@types:path_segment())} | {error, molt@error:molt_error()}.
parse_path_double_quoted(Chars, Segments, Acc) ->
    case Chars of
        [] ->
            {error, {invalid_path, <<"unterminated double quote"/utf8>>}};

        [<<"\\"/utf8>> | Chars@1] ->
            case Chars@1 of
                [] ->
                    {error,
                        {invalid_path, <<"unterminated double quote"/utf8>>}};

                [Escaped | Chars@2] ->
                    Ch = case Escaped of
                        <<"n"/utf8>> ->
                            <<"\n"/utf8>>;

                        <<"t"/utf8>> ->
                            <<"\t"/utf8>>;

                        <<"r"/utf8>> ->
                            <<"\r"/utf8>>;

                        <<"\\"/utf8>> ->
                            <<"\\"/utf8>>;

                        <<"\""/utf8>> ->
                            <<"\""/utf8>>;

                        _ ->
                            <<"\\"/utf8, Escaped/binary>>
                    end,
                    parse_path_double_quoted(
                        Chars@2,
                        Segments,
                        <<Acc/binary, Ch/binary>>
                    )
            end;

        [<<"\""/utf8>> | Chars@3] ->
            parse_path_segments(
                Chars@3,
                [{key_segment, Acc} | Segments],
                <<""/utf8>>,
                false
            );

        [Ch@1 | Chars@4] ->
            parse_path_double_quoted(
                Chars@4,
                Segments,
                <<Acc/binary, Ch@1/binary>>
            )
    end.

-file("src/molt/internal/path.gleam", 252).
?DOC(false).
-spec parse_path_single_quoted(
    list(binary()),
    list(molt@types:path_segment()),
    binary()
) -> {ok, list(molt@types:path_segment())} | {error, molt@error:molt_error()}.
parse_path_single_quoted(Chars, Segments, Acc) ->
    case Chars of
        [] ->
            {error, {invalid_path, <<"unterminated single quote"/utf8>>}};

        [<<"'"/utf8>> | Chars@1] ->
            parse_path_segments(
                Chars@1,
                [{key_segment, Acc} | Segments],
                <<""/utf8>>,
                false
            );

        [Ch | Chars@2] ->
            parse_path_single_quoted(
                Chars@2,
                Segments,
                <<Acc/binary, Ch/binary>>
            )
    end.

-file("src/molt/internal/path.gleam", 216).
?DOC(false).
-spec parse_path_after_bracket(list(binary()), list(molt@types:path_segment())) -> {ok,
        list(molt@types:path_segment())} |
    {error, molt@error:molt_error()}.
parse_path_after_bracket(Chars, Segments) ->
    case Chars of
        [] ->
            {ok, lists:reverse(Segments)};

        [<<"."/utf8>> | Rest] ->
            parse_path_segments(Rest, Segments, <<""/utf8>>, true);

        [<<"["/utf8>> | Rest@1] ->
            case parse_path_bracket_index(Rest@1, <<""/utf8>>) of
                {ok, {Index, Remaining}} ->
                    parse_path_after_bracket(
                        Remaining,
                        [{index_segment, Index} | Segments]
                    );

                {error, Error} ->
                    {error, Error}
            end;

        _ ->
            {error,
                {invalid_path, <<"expected dot or bracket after index"/utf8>>}}
    end.

-file("src/molt/internal/path.gleam", 127).
?DOC(false).
-spec parse_path_segments(
    list(binary()),
    list(molt@types:path_segment()),
    binary(),
    boolean()
) -> {ok, list(molt@types:path_segment())} | {error, molt@error:molt_error()}.
parse_path_segments(Chars, Segments, Current, Need_segment) ->
    case Chars of
        [] ->
            case {Current, Need_segment} of
                {<<""/utf8>>, true} ->
                    {error, {invalid_path, <<"trailing dot"/utf8>>}};

                {<<""/utf8>>, false} ->
                    {ok, lists:reverse(Segments)};

                {_, _} ->
                    {ok, lists:reverse([{key_segment, Current} | Segments])}
            end;

        [<<"."/utf8>> | Rest] ->
            case {Current, Need_segment orelse (Segments =:= [])} of
                {<<""/utf8>>, true} ->
                    {error, {invalid_path, <<"empty segment"/utf8>>}};

                {<<""/utf8>>, false} ->
                    parse_path_segments(Rest, Segments, <<""/utf8>>, true);

                {_, _} ->
                    parse_path_segments(
                        Rest,
                        [{key_segment, Current} | Segments],
                        <<""/utf8>>,
                        true
                    )
            end;

        [<<"["/utf8>> | Rest@1] ->
            gleam@bool:guard(
                Need_segment andalso (Current =:= <<""/utf8>>),
                {error, {invalid_path, <<"empty segment before bracket"/utf8>>}},
                fun() ->
                    Segments@1 = case Current of
                        <<""/utf8>> ->
                            Segments;

                        _ ->
                            [{key_segment, Current} | Segments]
                    end,
                    case parse_path_bracket_index(Rest@1, <<""/utf8>>) of
                        {ok, {Index, Remaining}} ->
                            parse_path_after_bracket(
                                Remaining,
                                [{index_segment, Index} | Segments@1]
                            );

                        {error, Error} ->
                            {error, Error}
                    end
                end
            );

        [<<"'"/utf8>> | Chars@1] ->
            gleam@bool:guard(
                Current /= <<""/utf8>>,
                {error, {invalid_path, <<"unexpected quote in bare key"/utf8>>}},
                fun() ->
                    parse_path_single_quoted(Chars@1, Segments, <<""/utf8>>)
                end
            );

        [<<"\""/utf8>> | Chars@2] ->
            gleam@bool:guard(
                Current /= <<""/utf8>>,
                {error, {invalid_path, <<"unexpected quote in bare key"/utf8>>}},
                fun() ->
                    parse_path_double_quoted(Chars@2, Segments, <<""/utf8>>)
                end
            );

        [Ch | Chars@3] ->
            gleam@bool:guard(
                not molt@internal@utils:is_bare_key(Ch),
                {error,
                    {invalid_path,
                        <<<<"invalid character '"/utf8, Ch/binary>>/binary,
                            "' in bare key segment; quote the segment with ' or \""/utf8>>}},
                fun() ->
                    parse_path_segments(
                        Chars@3,
                        Segments,
                        <<Current/binary, Ch/binary>>,
                        false
                    )
                end
            )
    end.

-file("src/molt/internal/path.gleam", 19).
?DOC(false).
-spec parse(binary()) -> {ok, list(molt@types:path_segment())} |
    {error, molt@error:molt_error()}.
parse(Input) ->
    case Input of
        <<""/utf8>> ->
            {ok, []};

        _ ->
            case parse_path_segments(
                gleam@string:to_graphemes(Input),
                [],
                <<""/utf8>>,
                false
            ) of
                {ok, Segments} ->
                    validate_segments(Segments);

                {error, Error} ->
                    {error, Error}
            end
    end.

-file("src/molt/internal/path.gleam", 37).
?DOC(false).
-spec to_string(list(molt@types:path_segment())) -> binary().
to_string(Segments) ->
    molt@internal@utils:path_to_string(Segments).

-file("src/molt/internal/path.gleam", 41).
?DOC(false).
-spec with_segment_key_name(
    molt@types:path_segment(),
    IHN,
    fun((binary()) -> IHN)
) -> IHN.
with_segment_key_name(Segment, Return, Do) ->
    case Segment of
        {key_segment, Name} ->
            Do(Name);

        {index_segment, _} ->
            Return
    end.

-file("src/molt/internal/path.gleam", 52).
?DOC(false).
-spec split_last_segment(list(molt@types:path_segment())) -> {list(molt@types:path_segment()),
    molt@types:path_segment()}.
split_last_segment(Segments) ->
    gleam@bool:guard(
        Segments =:= [],
        {[], {key_segment, <<""/utf8>>}},
        fun() -> case lists:reverse(Segments) of
                [Last | Rest] ->
                    {lists:reverse(Rest), Last};

                [] ->
                    {[], {key_segment, <<""/utf8>>}}
            end end
    ).

-file("src/molt/internal/path.gleam", 71).
?DOC(false).
-spec do_split_at_last_index(
    list(molt@types:path_segment()),
    list(molt@types:path_segment())
) -> {ok, {list(molt@types:path_segment()), list(molt@types:path_segment())}} |
    {error, nil}.
do_split_at_last_index(Rev_remaining, After) ->
    case Rev_remaining of
        [] ->
            {error, nil};

        [{index_segment, I} | Rest] ->
            {ok, {lists:reverse([{index_segment, I} | Rest]), After}};

        [Segment | Rest@1] ->
            do_split_at_last_index(Rest@1, [Segment | After])
    end.

-file("src/molt/internal/path.gleam", 67).
?DOC(false).
-spec split_at_last_index(list(molt@types:path_segment())) -> {ok,
        {list(molt@types:path_segment()), list(molt@types:path_segment())}} |
    {error, nil}.
split_at_last_index(Segments) ->
    do_split_at_last_index(lists:reverse(Segments), []).

-file("src/molt/internal/path.gleam", 83).
?DOC(false).
-spec drop_last_segment(list(molt@types:path_segment())) -> list(molt@types:path_segment()).
drop_last_segment(Path) ->
    {Path@1, _} = split_last_segment(Path),
    Path@1.

-file("src/molt/internal/path.gleam", 94).
?DOC(false).
-spec path_to_table_header(list(molt@types:path_segment())) -> list(binary()).
path_to_table_header(Segments) ->
    gleam@list:filter_map(Segments, fun(Seg) -> case Seg of
                {key_segment, K} ->
                    {ok, K};

                {index_segment, _} ->
                    {error, nil}
            end end).

-file("src/molt/internal/path.gleam", 112).
?DOC(false).
-spec is_index_segment(molt@types:path_segment()) -> boolean().
is_index_segment(Seg) ->
    case Seg of
        {index_segment, _} ->
            true;

        {key_segment, _} ->
            false
    end.

-file("src/molt/internal/path.gleam", 108).
?DOC(false).
-spec contains_index(list(molt@types:path_segment())) -> boolean().
contains_index(Segments) ->
    gleam@list:any(Segments, fun is_index_segment/1).