Skip to main content

src/tomlet.erl

-module(tomlet).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/tomlet.gleam").
-export([date_from_string/1, time_from_string/1, datetime_from_string/1, date_to_string/1, time_to_string/1, datetime_to_string/1, new/0, parse/1, parse_with/2, value_get/2, get/2, parse_value/1, parse_bytes/1, parse_bytes_with/2, line_column/2, position_line/1, position_column/1, to_string/1, get_string/2, get_int/2, get_bool/2, get_float/2, get_date/2, get_time/2, get_datetime/2, table_keys/2, as_string/1, as_int/1, as_bool/1, as_float/1, as_date/1, as_time/1, as_datetime/1, set_string/3, set_int/3, set_bool/3, set_float/3, set_date/3, set_time/3, set_datetime/3, set_array/3, set_inline_table/3, append_array_of_tables/3, remove/2, insert_comment_before/3]).
-export_type([document/0, line_ending/0, parse_error/0, syntax_error_kind/0, get_error/0, expected_type/0, value/0, date/0, time/0, date_time/0, format_error/0, special_float/0, edit_error/0, toml_version/0, position/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(
    " A round-tripping TOML parser and writer.\n"
    "\n"
    " Tomlet parses TOML into an opaque `Document`, preserves comments and\n"
    " formatting during round-trips, and provides checked helpers for common\n"
    " reads and edits.\n"
).

-opaque document() :: {document,
        tomlet@ast:table(),
        binary(),
        line_ending(),
        gleam@option:option(binary())}.

-type line_ending() :: lf | crlf.

-type parse_error() :: invalid_encoding |
    {invalid_syntax, syntax_error_kind(), integer()} |
    {duplicate_key, list(binary()), integer()}.

-type syntax_error_kind() :: expected_value |
    expected_key |
    expected_table_header |
    invalid_toml.

-type get_error() :: {key_not_found, list(binary())} |
    {wrong_type, list(binary()), expected_type()}.

-type expected_type() :: expected_string |
    expected_int |
    expected_bool |
    expected_float |
    expected_date |
    expected_time |
    expected_date_time.

-type value() :: {string_value, binary()} |
    {int_value, integer()} |
    {float_value, float()} |
    {special_float_value, special_float()} |
    {bool_value, boolean()} |
    {date_value, date()} |
    {time_value, time()} |
    {date_time_value, date_time()} |
    {array_value, list(value())} |
    {inline_table_value, list({list(binary()), value()})} |
    {standard_table_value, list({list(binary()), value()})} |
    {array_of_tables_value, list(list({list(binary()), value()}))}.

-opaque date() :: {date, binary()}.

-opaque time() :: {time, binary()}.

-opaque date_time() :: {date_time, binary()}.

-type format_error() :: {invalid_date, binary()} |
    {invalid_time, binary()} |
    {invalid_date_time, binary()}.

-type special_float() :: positive_infinity | negative_infinity | not_a_number.

-type edit_error() :: empty_key_path |
    {invalid_key_segment, binary()} |
    invalid_comment_text |
    {missing_edit_key, list(binary())} |
    {key_conflict, list(binary())} |
    {inline_table_insert_unsupported, list(binary())} |
    invalid_value.

-type toml_version() :: toml10 | toml11.

-opaque position() :: {position, integer(), integer()}.

-file("src/tomlet.gleam", 174).
?DOC(
    " Construct a `Date` from its TOML lexical form (e.g. `\"1979-05-27\"`).\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(date) = tomlet.date_from_string(\"1979-05-27\")\n"
    " let assert Ok(doc) = tomlet.set_date(tomlet.new(), [\"released\"], date)\n"
    " tomlet.to_string(doc)\n"
    " // -> \"released = 1979-05-27\\n\"\n"
    " ```\n"
).
-spec date_from_string(binary()) -> {ok, date()} | {error, format_error()}.
date_from_string(Text) ->
    gleam@bool:guard(
        not tomlet@parser:date_repr_is_valid(Text),
        {error, {invalid_date, Text}},
        fun() -> {ok, {date, Text}} end
    ).

-file("src/tomlet.gleam", 190).
?DOC(
    " Construct a `Time` from its TOML lexical form (e.g. `\"07:32:00\"`).\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(time) = tomlet.time_from_string(\"07:32:00\")\n"
    " let assert Ok(doc) = tomlet.set_time(tomlet.new(), [\"alarm\"], time)\n"
    " tomlet.to_string(doc)\n"
    " // -> \"alarm = 07:32:00\\n\"\n"
    " ```\n"
).
-spec time_from_string(binary()) -> {ok, time()} | {error, format_error()}.
time_from_string(Text) ->
    gleam@bool:guard(
        not tomlet@parser:time_repr_is_valid(Text),
        {error, {invalid_time, Text}},
        fun() -> {ok, {time, Text}} end
    ).

-file("src/tomlet.gleam", 209).
?DOC(
    " Construct a `DateTime` from its TOML lexical form\n"
    " (e.g. `\"1979-05-27T07:32:00Z\"`).\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(datetime) =\n"
    "   tomlet.datetime_from_string(\"1979-05-27T07:32:00Z\")\n"
    " let assert Ok(doc) =\n"
    "   tomlet.set_datetime(tomlet.new(), [\"published\"], datetime)\n"
    " tomlet.to_string(doc)\n"
    " // -> \"published = 1979-05-27T07:32:00Z\\n\"\n"
    " ```\n"
).
-spec datetime_from_string(binary()) -> {ok, date_time()} |
    {error, format_error()}.
datetime_from_string(Text) ->
    gleam@bool:guard(
        not tomlet@parser:datetime_repr_is_valid(Text),
        {error, {invalid_date_time, Text}},
        fun() -> {ok, {date_time, Text}} end
    ).

-file("src/tomlet.gleam", 218).
?DOC(" Return the original lexical form of a TOML date value.\n").
-spec date_to_string(date()) -> binary().
date_to_string(Date) ->
    erlang:element(2, Date).

-file("src/tomlet.gleam", 223).
?DOC(" Return the original lexical form of a TOML time value.\n").
-spec time_to_string(time()) -> binary().
time_to_string(Time) ->
    erlang:element(2, Time).

-file("src/tomlet.gleam", 228).
?DOC(" Return the original lexical form of a TOML date-time value.\n").
-spec datetime_to_string(date_time()) -> binary().
datetime_to_string(Datetime) ->
    erlang:element(2, Datetime).

-file("src/tomlet.gleam", 286).
?DOC(
    " Create an empty TOML document.\n"
    "\n"
    " Equivalent to `parse(\"\")` for downstream callers; the only observable\n"
    " difference is that `parse(\"\")` initially round-trips to `\"\"` even after the\n"
    " document is modified, while the document returned by `new` always emits its\n"
    " current content.\n"
).
-spec new() -> document().
new() ->
    {document, {table, [], none}, <<""/utf8>>, lf, none}.

-file("src/tomlet.gleam", 620).
-spec normalized_offset_to_original_next(
    list(integer()),
    integer(),
    integer(),
    integer()
) -> integer().
normalized_offset_to_original_next(Codepoints, Target, Normalized, Original) ->
    case Codepoints of
        [] ->
            Original;

        [Codepoint | Rest] ->
            Width = erlang:byte_size(
                gleam_stdlib:utf_codepoint_list_to_string([Codepoint])
            ),
            normalized_offset_to_original_loop(
                Rest,
                Target,
                Normalized + Width,
                Original + Width,
                false
            )
    end.

-file("src/tomlet.gleam", 569).
-spec normalized_offset_to_original_loop(
    list(integer()),
    integer(),
    integer(),
    integer(),
    boolean()
) -> integer().
normalized_offset_to_original_loop(
    Codepoints,
    Target,
    Normalized,
    Original,
    At_start
) ->
    case {Normalized >= Target, Codepoints} of
        {true, _} ->
            Original;

        {false, []} ->
            Original;

        {false, [First, Second | Rest]} ->
            case {gleam_stdlib:identity(First),
                gleam_stdlib:identity(Second),
                At_start} of
                {65279, _, true} ->
                    normalized_offset_to_original_loop(
                        [Second | Rest],
                        Target,
                        Normalized,
                        Original + 3,
                        false
                    );

                {13, 10, _} ->
                    normalized_offset_to_original_loop(
                        Rest,
                        Target,
                        Normalized + 1,
                        Original + 2,
                        false
                    );

                {_, _, _} ->
                    normalized_offset_to_original_next(
                        [First, Second | Rest],
                        Target,
                        Normalized,
                        Original
                    )
            end;

        {false, Remaining} ->
            normalized_offset_to_original_next(
                Remaining,
                Target,
                Normalized,
                Original
            )
    end.

-file("src/tomlet.gleam", 559).
-spec normalized_offset_to_original(binary(), integer()) -> integer().
normalized_offset_to_original(Input, Target) ->
    normalized_offset_to_original_loop(
        gleam@string:to_utf_codepoints(Input),
        Target,
        0,
        0,
        true
    ).

-file("src/tomlet.gleam", 641).
-spec syntax_error_kind(tomlet@parser:expected_token_kind()) -> syntax_error_kind().
syntax_error_kind(Expected) ->
    case Expected of
        expected_value ->
            expected_value;

        expected_key ->
            expected_key;

        expected_table_header ->
            expected_table_header;

        expected_syntax ->
            invalid_toml
    end.

-file("src/tomlet.gleam", 552).
-spec to_parser_version(toml_version()) -> tomlet@parser:version().
to_parser_version(Version) ->
    case Version of
        toml10 ->
            toml10;

        toml11 ->
            toml11
    end.

-file("src/tomlet.gleam", 513).
-spec parse_string_with(binary(), toml_version()) -> {ok, document()} |
    {error, parse_error()}.
parse_string_with(Input, Version) ->
    Line_ending = case gleam_stdlib:contains_string(Input, <<"\r\n"/utf8>>) of
        true ->
            crlf;

        false ->
            lf
    end,
    Input_without_initial_bom = case gleam@string:to_graphemes(Input) of
        [<<"\x{FEFF}"/utf8>> | Rest] ->
            erlang:list_to_binary(Rest);

        _ ->
            Input
    end,
    case gleam_stdlib:contains_string(
        Input_without_initial_bom,
        <<"\x{FEFF}"/utf8>>
    ) of
        true ->
            {error, invalid_encoding};

        false ->
            Normalized = gleam@string:replace(
                Input_without_initial_bom,
                <<"\r\n"/utf8>>,
                <<"\n"/utf8>>
            ),
            case tomlet@parser:parse(Normalized, to_parser_version(Version)) of
                {ok, Root} ->
                    {ok,
                        {document,
                            Root,
                            <<""/utf8>>,
                            Line_ending,
                            {some, Input}}};

                {error, {unexpected, _, Expected, Offset}} ->
                    {error,
                        {invalid_syntax,
                            syntax_error_kind(Expected),
                            normalized_offset_to_original(Input, Offset)}};

                {error, {key_already_in_use, Key, Offset@1}} ->
                    {error,
                        {duplicate_key,
                            Key,
                            normalized_offset_to_original(Input, Offset@1)}}
            end
    end.

-file("src/tomlet.gleam", 308).
?DOC(
    " Parse TOML 1.1 text into a document. Use `parse_with(input, Toml10)` for\n"
    " strict TOML 1.0 parsing that rejects 1.1-only syntax.\n"
    "\n"
    " Successful parses return an opaque `Document` that preserves comments,\n"
    " formatting trivia, key order, and the original line ending style for\n"
    " round-tripping. Invalid text returns `ParseError`, including byte offsets for\n"
    " syntax and duplicate-key diagnostics.\n"
).
-spec parse(binary()) -> {ok, document()} | {error, parse_error()}.
parse(Input) ->
    parse_string_with(Input, toml11).

-file("src/tomlet.gleam", 313).
?DOC(" Parse TOML text against the given language version.\n").
-spec parse_with(binary(), toml_version()) -> {ok, document()} |
    {error, parse_error()}.
parse_with(Input, Version) ->
    parse_string_with(Input, Version).

-file("src/tomlet.gleam", 356).
-spec value_offset(integer(), integer()) -> integer().
value_offset(Offset, Prefix_size) ->
    case Offset < Prefix_size of
        true ->
            0;

        false ->
            Offset - Prefix_size
    end.

-file("src/tomlet.gleam", 346).
-spec parse_value_error(parse_error(), integer()) -> parse_error().
parse_value_error(Error, Prefix_size) ->
    case Error of
        invalid_encoding ->
            invalid_encoding;

        {invalid_syntax, Kind, Offset} ->
            {invalid_syntax, Kind, value_offset(Offset, Prefix_size)};

        {duplicate_key, _, Offset@1} ->
            {invalid_syntax, invalid_toml, value_offset(Offset@1, Prefix_size)}
    end.

-file("src/tomlet.gleam", 961).
-spec value_get_index(list(value()), binary(), list(binary()), list(binary())) -> {ok,
        value()} |
    {error, get_error()}.
value_get_index(Items, Index_text, Rest, Key) ->
    case gleam_stdlib:parse_int(Index_text) of
        {ok, Index} when Index >= 0 ->
            case begin
                _pipe = Items,
                _pipe@1 = gleam@list:drop(_pipe, Index),
                gleam@list:first(_pipe@1)
            end of
                {ok, Item} ->
                    value_get(Item, Rest);

                {error, nil} ->
                    {error, {key_not_found, Key}}
            end;

        _ ->
            {error, {key_not_found, Key}}
    end.

-file("src/tomlet.gleam", 938).
-spec value_get_table(
    list({list(binary()), value()}),
    binary(),
    list(binary()),
    list(binary())
) -> {ok, value()} | {error, get_error()}.
value_get_table(Entries, Head, Rest, Key) ->
    Matched = gleam@list:filter_map(
        Entries,
        fun(Entry) ->
            {Entry_key, Entry_value} = Entry,
            case Entry_key of
                [First | Tail] when First =:= Head ->
                    {ok, {Tail, Entry_value}};

                _ ->
                    {error, nil}
            end
        end
    ),
    case {Matched, Rest} of
        {[], _} ->
            {error, {key_not_found, Key}};

        {[{[], Inner}], []} ->
            {ok, Inner};

        {[{[], Inner@1}], _} ->
            value_get(Inner@1, Rest);

        {_, []} ->
            {ok, {standard_table_value, Matched}};

        {_, _} ->
            value_get({standard_table_value, Matched}, Rest)
    end.

-file("src/tomlet.gleam", 924).
?DOC(
    " Descend into a `Value` by key path without returning to the document root.\n"
    "\n"
    " Table-shaped values are descended by key; arrays and arrays of tables are\n"
    " descended by a non-negative decimal index (e.g. `\"0\"`). An empty path\n"
    " returns the value unchanged. A missing key, a non-numeric or out-of-range\n"
    " index, or descent into a scalar all yield `KeyNotFound`.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.parse(\"[[packages]]\\nname = \\\"gleam_stdlib\\\"\\n\")\n"
    " let assert Ok(packages) = tomlet.get(doc, [\"packages\"])\n"
    " tomlet.value_get(packages, [\"0\", \"name\"])\n"
    " // -> Ok(tomlet.StringValue(\"gleam_stdlib\"))\n"
    " ```\n"
).
-spec value_get(value(), list(binary())) -> {ok, value()} | {error, get_error()}.
value_get(Value, Key) ->
    case {Key, Value} of
        {[], _} ->
            {ok, Value};

        {[Head | Rest], {inline_table_value, Entries}} ->
            value_get_table(Entries, Head, Rest, Key);

        {[Head@1 | Rest@1], {standard_table_value, Entries@1}} ->
            value_get_table(Entries@1, Head@1, Rest@1, Key);

        {[Head@2 | Rest@2], {array_value, Items}} ->
            value_get_index(Items, Head@2, Rest@2, Key);

        {[Head@3 | Rest@3], {array_of_tables_value, Tables}} ->
            value_get_index(
                gleam@list:map(
                    Tables,
                    fun(Field@0) -> {standard_table_value, Field@0} end
                ),
                Head@3,
                Rest@3,
                Key
            );

        {_, _} ->
            {error, {key_not_found, Key}}
    end.

-file("src/tomlet.gleam", 2259).
-spec key_to_strings(tomlet@ast:key()) -> list(binary()).
key_to_strings(Key) ->
    tomlet@key:to_strings(Key).

-file("src/tomlet.gleam", 1020).
-spec public_special_float(tomlet@ast:special_float()) -> special_float().
public_special_float(Value) ->
    case Value of
        positive_infinity ->
            positive_infinity;

        negative_infinity ->
            negative_infinity;

        not_a_number ->
            not_a_number
    end.

-file("src/tomlet.gleam", 2249).
-spec header_key(tomlet@ast:header()) -> list(binary()).
header_key(Header) ->
    {header, Key, _, _} = Header,
    key_to_strings(Key).

-file("src/tomlet.gleam", 2254).
-spec header_is_standard_table(tomlet@ast:header()) -> boolean().
header_is_standard_table(Header) ->
    {header, _, Kind, _} = Header,
    Kind =:= standard_table.

-file("src/tomlet.gleam", 1047).
-spec entry_defines_target_table(tomlet@ast:entry(), list(binary())) -> boolean().
entry_defines_target_table(Entry, Target) ->
    case Entry of
        {table_header, Header} ->
            header_is_standard_table(Header) andalso (header_key(Header) =:= Target);

        _ ->
            false
    end.

-file("src/tomlet.gleam", 1041).
-spec public_table_entries(tomlet@ast:table()) -> list({list(binary()), value()}).
public_table_entries(Table) ->
    {table, Entries, _} = Table,
    {Table_entries, _} = collect_table_entries(Entries, [], [], true, []),
    Table_entries.

-file("src/tomlet.gleam", 1033).
-spec public_inline_table_entry(tomlet@ast:inline_table_entry()) -> {list(binary()),
    value()}.
public_inline_table_entry(Entry) ->
    {inline_table_entry, _, Key, Value, _} = Entry,
    {key_to_strings(Key), public_value(Value)}.

-file("src/tomlet.gleam", 1028).
-spec public_array_item(tomlet@ast:array_item()) -> value().
public_array_item(Item) ->
    {array_item, _, Value, _} = Item,
    public_value(Value).

-file("src/tomlet.gleam", 1000).
-spec public_value(tomlet@ast:value()) -> value().
public_value(Value) ->
    case Value of
        {int, Value@1, _} ->
            {int_value, Value@1};

        {float, Value@2, _} ->
            {float_value, Value@2};

        {special_float, Value@3, _} ->
            {special_float_value, public_special_float(Value@3)};

        {bool, Value@4, _} ->
            {bool_value, Value@4};

        {string, Value@5, _, _} ->
            {string_value, Value@5};

        {date, Source_text} ->
            {date_value, {date, Source_text}};

        {time, Source_text@1} ->
            {time_value, {time, Source_text@1}};

        {date_time, Source_text@2} ->
            {date_time_value, {date_time, Source_text@2}};

        {array, Items, _} ->
            {array_value, gleam@list:map(Items, fun public_array_item/1)};

        {inline_table, Entries, _} ->
            {inline_table_value,
                gleam@list:map(Entries, fun public_inline_table_entry/1)};

        {array_of_tables, Items@1} ->
            {array_of_tables_value,
                gleam@list:map(Items@1, fun public_table_entries/1)}
    end.

-file("src/tomlet.gleam", 1055).
-spec collect_key_value_entry(
    tomlet@ast:key(),
    tomlet@ast:value(),
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    list(binary()),
    boolean(),
    list({list(binary()), value()})
) -> {list({list(binary()), value()}), boolean()}.
collect_key_value_entry(
    Key,
    Value,
    Rest,
    Active_table,
    Next_active_table,
    Target,
    Next_found,
    Collected
) ->
    Full_key = lists:append(Active_table, key_to_strings(Key)),
    case tomlet@key:starts_with(Full_key, Target) andalso (Full_key /= Target) of
        true ->
            collect_table_entries(
                Rest,
                Next_active_table,
                Target,
                true,
                [{gleam@list:drop(Full_key, erlang:length(Target)),
                        public_value(Value)} |
                    Collected]
            );

        false ->
            collect_table_entries(
                Rest,
                Next_active_table,
                Target,
                Next_found,
                Collected
            )
    end.

-file("src/tomlet.gleam", 1083).
-spec collect_table_entries(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    boolean(),
    list({list(binary()), value()})
) -> {list({list(binary()), value()}), boolean()}.
collect_table_entries(Entries, Active_table, Target, Found, Collected) ->
    case Entries of
        [] ->
            {lists:reverse(Collected), Found};

        [Entry | Rest] ->
            Next_active_table = case Entry of
                {table_header, Header} ->
                    header_key(Header);

                _ ->
                    Active_table
            end,
            Next_found = Found orelse entry_defines_target_table(Entry, Target),
            case Entry of
                {key_value, _, Key, Value, _} ->
                    collect_key_value_entry(
                        Key,
                        Value,
                        Rest,
                        Active_table,
                        Next_active_table,
                        Target,
                        Next_found,
                        Collected
                    );

                _ ->
                    collect_table_entries(
                        Rest,
                        Next_active_table,
                        Target,
                        Next_found,
                        Collected
                    )
            end
    end.

-file("src/tomlet.gleam", 982).
-spec get_table_value(document(), list(binary())) -> {ok, value()} |
    {error, get_error()}.
get_table_value(Doc, Key) ->
    case Key of
        [] ->
            {error, {key_not_found, Key}};

        _ ->
            {document, {table, Entries, _}, _, _, _} = Doc,
            {Table_entries, Found} = collect_table_entries(
                Entries,
                [],
                Key,
                false,
                []
            ),
            case Found of
                true ->
                    {ok, {standard_table_value, Table_entries}};

                false ->
                    {error, {key_not_found, Key}}
            end
    end.

-file("src/tomlet.gleam", 977).
-spec get_value(document(), list(binary())) -> {ok, tomlet@ast:value()} |
    {error, get_error()}.
get_value(Doc, Key) ->
    _pipe = tomlet@path:get(erlang:element(2, Doc), Key),
    gleam@result:replace_error(_pipe, {key_not_found, Key}).

-file("src/tomlet.gleam", 719).
-spec get_indexed_loop(document(), list(binary()), integer()) -> {ok, value()} |
    {error, get_error()}.
get_indexed_loop(Doc, Key, Split) ->
    gleam@bool:guard(
        Split < 1,
        {error, {key_not_found, Key}},
        fun() -> _pipe = get(Doc, gleam@list:take(Key, Split)),
            _pipe@1 = gleam@result:'try'(
                _pipe,
                fun(_capture) ->
                    value_get(_capture, gleam@list:drop(Key, Split))
                end
            ),
            gleam@result:lazy_or(
                _pipe@1,
                fun() -> get_indexed_loop(Doc, Key, Split - 1) end
            ) end
    ).

-file("src/tomlet.gleam", 715).
-spec get_indexed(document(), list(binary())) -> {ok, value()} |
    {error, get_error()}.
get_indexed(Doc, Key) ->
    get_indexed_loop(Doc, Key, erlang:length(Key) - 1).

-file("src/tomlet.gleam", 694).
?DOC(
    " Read a TOML value at a key path.\n"
    "\n"
    " Use `get` instead of the typed `get_*` helpers when you need to inspect\n"
    " arrays, inline tables, standard tables, arrays of tables, or special floats.\n"
    "\n"
    " Path segments that name an array or array of tables can be followed by a\n"
    " non-negative decimal index to descend into it, e.g.\n"
    " `get(doc, [\"packages\", \"0\", \"name\"])`. For a typed read of an indexed path,\n"
    " compose with the `as_*` converters: `get(doc, path) |> result.try(as_string)`.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.parse(\"package = { name = \\\"tomato\\\", downloads = 42 }\\n\")\n"
    " let assert Ok(value) = tomlet.get(doc, [\"package\"])\n"
    " // -> tomlet.InlineTableValue([\n"
    " //   #([\"name\"], tomlet.StringValue(\"tomato\")),\n"
    " //   #([\"downloads\"], tomlet.IntValue(42)),\n"
    " // ])\n"
    " ```\n"
).
-spec get(document(), list(binary())) -> {ok, value()} | {error, get_error()}.
get(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, Value} ->
            {ok, public_value(Value)};

        {error, {key_not_found, _}} ->
            case get_table_value(Doc, Key) of
                {ok, Value@1} ->
                    {ok, Value@1};

                {error, {key_not_found, _}} ->
                    get_indexed(Doc, Key);

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

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

-file("src/tomlet.gleam", 332).
?DOC(
    " Parse a standalone TOML value literal.\n"
    "\n"
    " The returned value uses Tomlet's stable public `Value` variants. Trailing\n"
    " non-comment syntax is rejected rather than ignored.\n"
    "\n"
    " ```gleam\n"
    " tomlet.parse_value(\"\\\"tomato\\\"\")\n"
    " // -> Ok(tomlet.StringValue(\"tomato\"))\n"
    "\n"
    " tomlet.parse_value(\"[8000, 8001]\")\n"
    " // -> Ok(tomlet.ArrayValue([tomlet.IntValue(8000), tomlet.IntValue(8001)]))\n"
    " ```\n"
).
-spec parse_value(binary()) -> {ok, value()} | {error, parse_error()}.
parse_value(Input) ->
    Key = <<"__tomlet_value__"/utf8>>,
    Prefix = <<Key/binary, " = "/utf8>>,
    Source = <<<<Prefix/binary, Input/binary>>/binary, "\n"/utf8>>,
    case parse_string_with(Source, toml11) of
        {ok, Doc} ->
            case get(Doc, [Key]) of
                {ok, Value} ->
                    {ok, Value};

                {error, _} ->
                    {error, {invalid_syntax, invalid_toml, 0}}
            end;

        {error, Error} ->
            {error, parse_value_error(Error, erlang:byte_size(Prefix))}
    end.

-file("src/tomlet.gleam", 504).
-spec bit_array_contains_utf8_bom(bitstring()) -> boolean().
bit_array_contains_utf8_bom(Input) ->
    case Input of
        <<>> ->
            false;

        <<239, 187, 191, _/bitstring>> ->
            true;

        <<_, Rest/bitstring>> ->
            bit_array_contains_utf8_bom(Rest);

        _ ->
            false
    end.

-file("src/tomlet.gleam", 387).
-spec parse_bytes_versioned(bitstring(), toml_version()) -> {ok, document()} |
    {error, parse_error()}.
parse_bytes_versioned(Input, Version) ->
    Input_without_initial_bom = case Input of
        <<239, 187, 191, Rest/bitstring>> ->
            Rest;

        _ ->
            Input
    end,
    case bit_array_contains_utf8_bom(Input_without_initial_bom) of
        true ->
            {error, invalid_encoding};

        false ->
            case gleam@bit_array:to_string(Input_without_initial_bom) of
                {ok, Decoded} ->
                    parse_string_with(Decoded, Version);

                {error, _} ->
                    {error, invalid_encoding}
            end
    end.

-file("src/tomlet.gleam", 375).
?DOC(
    " Parse TOML bytes into a document.\n"
    "\n"
    " This validates UTF-8 input and accepts a UTF-8 byte order mark only at the\n"
    " start of the input.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) = tomlet.parse_bytes(<<\"answer = 42\\n\":utf8>>)\n"
    " let assert Ok(answer) = tomlet.get_int(doc, [\"answer\"])\n"
    "\n"
    " tomlet.parse_bytes(<<110, 97, 109, 101, 32, 61, 32, 255, 10>>)\n"
    " // -> Error(tomlet.InvalidEncoding)\n"
    " ```\n"
).
-spec parse_bytes(bitstring()) -> {ok, document()} | {error, parse_error()}.
parse_bytes(Input) ->
    parse_bytes_versioned(Input, toml11).

-file("src/tomlet.gleam", 380).
?DOC(" Parse TOML bytes against the given language version.\n").
-spec parse_bytes_with(bitstring(), toml_version()) -> {ok, document()} |
    {error, parse_error()}.
parse_bytes_with(Input, Version) ->
    parse_bytes_versioned(Input, Version).

-file("src/tomlet.gleam", 482).
-spec line_column_next(
    list(integer()),
    integer(),
    integer(),
    integer(),
    integer()
) -> {integer(), integer()}.
line_column_next(Codepoints, Target, Current, Line, Column) ->
    case Codepoints of
        [] ->
            {Line, Column};

        [Codepoint | Rest] ->
            case gleam_stdlib:identity(Codepoint) of
                10 ->
                    line_column_loop(Rest, Target, Current + 1, Line + 1, 1);

                13 ->
                    line_column_loop(Rest, Target, Current + 1, Line + 1, 1);

                _ ->
                    Width = erlang:byte_size(
                        gleam_stdlib:utf_codepoint_list_to_string([Codepoint])
                    ),
                    line_column_loop(
                        Rest,
                        Target,
                        Current + Width,
                        Line,
                        Column + 1
                    )
            end
    end.

-file("src/tomlet.gleam", 451).
-spec line_column_loop(
    list(integer()),
    integer(),
    integer(),
    integer(),
    integer()
) -> {integer(), integer()}.
line_column_loop(Codepoints, Target, Current, Line, Column) ->
    case {Current >= Target, Codepoints} of
        {true, _} ->
            {Line, Column};

        {false, []} ->
            {Line, Column};

        {false, [First, Second | Rest]} ->
            case {gleam_stdlib:identity(First), gleam_stdlib:identity(Second)} of
                {13, 10} ->
                    line_column_loop(Rest, Target, Current + 2, Line + 1, 1);

                {_, _} ->
                    line_column_next(
                        [First, Second | Rest],
                        Target,
                        Current,
                        Line,
                        Column
                    )
            end;

        {false, Remaining} ->
            line_column_next(Remaining, Target, Current, Line, Column)
    end.

-file("src/tomlet.gleam", 435).
?DOC(
    " Convert a byte offset into a one-based line and column.\n"
    "\n"
    " Offsets beyond the end of the input return the position just after the last\n"
    " character. CRLF is treated as a single line break.\n"
    "\n"
    " ```gleam\n"
    " let input = \"name = \\n\"\n"
    " case tomlet.parse(input) {\n"
    "   Error(tomlet.InvalidSyntax(_, offset)) -> {\n"
    "     let position = tomlet.line_column(input, offset)\n"
    "     let line = tomlet.position_line(position)\n"
    "     let column = tomlet.position_column(position)\n"
    "     // Show line and column in your application's diagnostic.\n"
    "   }\n"
    "   _ -> Nil\n"
    " }\n"
    " ```\n"
).
-spec line_column(binary(), integer()) -> position().
line_column(Input, Offset) ->
    {Line, Column} = line_column_loop(
        gleam@string:to_utf_codepoints(Input),
        Offset,
        0,
        1,
        1
    ),
    {position, Line, Column}.

-file("src/tomlet.gleam", 442).
?DOC(" Return the one-based line number for a source position.\n").
-spec position_line(position()) -> integer().
position_line(Position) ->
    erlang:element(2, Position).

-file("src/tomlet.gleam", 447).
?DOC(" Return the one-based column number for a source position.\n").
-spec position_column(position()) -> integer().
position_column(Position) ->
    erlang:element(3, Position).

-file("src/tomlet.gleam", 1747).
-spec emit_key_segment(tomlet@ast:key_segment()) -> binary().
emit_key_segment(Segment) ->
    case Segment of
        {bare_key_segment, Text} ->
            Text;

        {quoted_key_segment, _, Source_text} ->
            Source_text
    end.

-file("src/tomlet.gleam", 1740).
-spec emit_key(tomlet@ast:key()) -> binary().
emit_key(Key) ->
    {key, Segments} = Key,
    _pipe = Segments,
    _pipe@1 = gleam@list:map(_pipe, fun emit_key_segment/1),
    gleam@string:join(_pipe@1, <<"."/utf8>>).

-file("src/tomlet.gleam", 1732).
-spec emit_header(tomlet@ast:header()) -> binary().
emit_header(Header) ->
    {header, Key, Kind, _} = Header,
    case Kind of
        standard_table ->
            <<<<"["/utf8, (emit_key(Key))/binary>>/binary, "]"/utf8>>;

        array_of_tables_header ->
            <<<<"[["/utf8, (emit_key(Key))/binary>>/binary, "]]"/utf8>>
    end.

-file("src/tomlet.gleam", 1796).
-spec emit_trivia(tomlet@ast:trivia()) -> binary().
emit_trivia(Trivia) ->
    {trivia, Text} = Trivia,
    Text.

-file("src/tomlet.gleam", 1754).
-spec emit_value(tomlet@ast:value()) -> binary().
emit_value(Value) ->
    case Value of
        {int, _, Source_text} ->
            Source_text;

        {float, _, Source_text@1} ->
            Source_text@1;

        {special_float, _, Source_text@2} ->
            Source_text@2;

        {bool, _, Source_text@3} ->
            Source_text@3;

        {string, _, _, Source_text@4} ->
            Source_text@4;

        {date, Source_text@5} ->
            Source_text@5;

        {time, Source_text@6} ->
            Source_text@6;

        {date_time, Source_text@7} ->
            Source_text@7;

        {array, _, Source_text@8} ->
            Source_text@8;

        {inline_table, _, Source_text@9} ->
            Source_text@9;

        {array_of_tables, Items} ->
            _pipe = Items,
            _pipe@1 = gleam@list:map(_pipe, fun emit_table/1),
            gleam@string:join(_pipe@1, <<""/utf8>>)
    end.

-file("src/tomlet.gleam", 1718).
-spec emit_entry(tomlet@ast:entry()) -> binary().
emit_entry(Entry) ->
    case Entry of
        {key_value, Leading, Key, Value, Trailing} ->
            <<<<<<<<(emit_trivia(Leading))/binary, (emit_key(Key))/binary>>/binary,
                        " = "/utf8>>/binary,
                    (emit_value(Value))/binary>>/binary,
                (emit_trivia(Trailing))/binary>>;

        {table_header, Header} ->
            <<(emit_header(Header))/binary, "\n"/utf8>>;

        {comment, Text} ->
            <<Text/binary, "\n"/utf8>>;

        blank_line ->
            <<"\n"/utf8>>
    end.

-file("src/tomlet.gleam", 1678).
-spec emit_table(tomlet@ast:table()) -> binary().
emit_table(Table) ->
    case Table of
        {table, [], _} ->
            <<""/utf8>>;

        {table, Entries, _} ->
            _pipe = Entries,
            _pipe@1 = gleam@list:map(_pipe, fun emit_entry/1),
            gleam@string:join(_pipe@1, <<""/utf8>>)
    end.

-file("src/tomlet.gleam", 653).
?DOC(
    " Emit a document as TOML text.\n"
    "\n"
    " Unedited parsed documents round-trip to their original source text.\n"
).
-spec to_string(document()) -> binary().
to_string(Doc) ->
    case erlang:element(5, Doc) of
        {some, Source} ->
            Source;

        none ->
            Output = <<(emit_table(erlang:element(2, Doc)))/binary,
                (erlang:element(3, Doc))/binary>>,
            case erlang:element(4, Doc) of
                lf ->
                    Output;

                crlf ->
                    gleam@string:replace(Output, <<"\n"/utf8>>, <<"\r\n"/utf8>>)
            end
    end.

-file("src/tomlet.gleam", 671).
-spec with_root(document(), tomlet@ast:table()) -> document().
with_root(Doc, Root) ->
    {document, Root, erlang:element(3, Doc), erlang:element(4, Doc), none}.

-file("src/tomlet.gleam", 731).
?DOC(" Read a TOML string value at a key path.\n").
-spec get_string(document(), list(binary())) -> {ok, binary()} |
    {error, get_error()}.
get_string(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {string, Value, _, _}} ->
            {ok, Value};

        {ok, _} ->
            {error, {wrong_type, Key, expected_string}};

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

-file("src/tomlet.gleam", 743).
?DOC(" Read a TOML integer value at a key path.\n").
-spec get_int(document(), list(binary())) -> {ok, integer()} |
    {error, get_error()}.
get_int(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {int, Value, _}} ->
            {ok, Value};

        {ok, _} ->
            {error, {wrong_type, Key, expected_int}};

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

-file("src/tomlet.gleam", 752).
?DOC(" Read a TOML boolean value at a key path.\n").
-spec get_bool(document(), list(binary())) -> {ok, boolean()} |
    {error, get_error()}.
get_bool(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {bool, Value, _}} ->
            {ok, Value};

        {ok, _} ->
            {error, {wrong_type, Key, expected_bool}};

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

-file("src/tomlet.gleam", 764).
?DOC(
    " Read a TOML float value at a key path.\n"
    "\n"
    " Special floats (`inf`, `-inf`, `nan`) are not returned here; reading one\n"
    " yields `WrongType`. Use `get` and match on `SpecialFloatValue` for those.\n"
).
-spec get_float(document(), list(binary())) -> {ok, float()} |
    {error, get_error()}.
get_float(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {float, Value, _}} ->
            {ok, Value};

        {ok, _} ->
            {error, {wrong_type, Key, expected_float}};

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

-file("src/tomlet.gleam", 773).
?DOC(" Read a TOML local date value at a key path.\n").
-spec get_date(document(), list(binary())) -> {ok, date()} |
    {error, get_error()}.
get_date(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {date, Source_text}} ->
            {ok, {date, Source_text}};

        {ok, _} ->
            {error, {wrong_type, Key, expected_date}};

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

-file("src/tomlet.gleam", 782).
?DOC(" Read a TOML local time value at a key path.\n").
-spec get_time(document(), list(binary())) -> {ok, time()} |
    {error, get_error()}.
get_time(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {time, Source_text}} ->
            {ok, {time, Source_text}};

        {ok, _} ->
            {error, {wrong_type, Key, expected_time}};

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

-file("src/tomlet.gleam", 791).
?DOC(" Read a TOML date-time value at a key path.\n").
-spec get_datetime(document(), list(binary())) -> {ok, date_time()} |
    {error, get_error()}.
get_datetime(Doc, Key) ->
    case get_value(Doc, Key) of
        {ok, {date_time, Source_text}} ->
            {ok, {date_time, Source_text}};

        {ok, _} ->
            {error, {wrong_type, Key, expected_date_time}};

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

-file("src/tomlet.gleam", 835).
-spec top_level_keys(list({list(binary()), value()})) -> list(binary()).
top_level_keys(Entries) ->
    _pipe = Entries,
    _pipe@1 = gleam@list:filter_map(
        _pipe,
        fun(Entry) -> case erlang:element(1, Entry) of
                [First | _] ->
                    {ok, First};

                [] ->
                    {error, nil}
            end end
    ),
    gleam@list:unique(_pipe@1).

-file("src/tomlet.gleam", 821).
?DOC(
    " Return the top-level keys of the table at a key path, in source order.\n"
    "\n"
    " Works on standard tables (`[table]`) and inline tables (`{ ... }`).\n"
    " Dotted keys and subtables collapse to their first segment, so\n"
    " `a.b` and `[t.sub]` both contribute a single `\"a\"` / `\"sub\"` key, and\n"
    " duplicates are removed while preserving first-occurrence order.\n"
    "\n"
    " A missing path yields `KeyNotFound`. A path that resolves to a non-table\n"
    " value (including an array of tables) yields `WrongType`. The\n"
    " `ExpectedType` reported for the non-table case is a placeholder until a\n"
    " dedicated `ExpectedTable` variant is introduced (see issue #28); match on\n"
    " the `WrongType` constructor rather than the specific `ExpectedType`.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.parse(\"[dependencies]\\ngleam_stdlib = \\\">= 0.40.0\\\"\\n\")\n"
    " tomlet.table_keys(doc, [\"dependencies\"])\n"
    " // -> Ok([\"gleam_stdlib\"])\n"
    " ```\n"
).
-spec table_keys(document(), list(binary())) -> {ok, list(binary())} |
    {error, get_error()}.
table_keys(Doc, Path) ->
    case get(Doc, Path) of
        {ok, {standard_table_value, Entries}} ->
            {ok, top_level_keys(Entries)};

        {ok, {inline_table_value, Entries@1}} ->
            {ok, top_level_keys(Entries@1)};

        {ok, _} ->
            {error, {wrong_type, Path, expected_string}};

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

-file("src/tomlet.gleam", 852).
?DOC(
    " Read a string from a `Value`.\n"
    "\n"
    " Mirrors `get_string`, but operates on a `Value` already obtained via `get`,\n"
    " so nested data can be decoded without re-walking from the document root. On\n"
    " a type mismatch the error carries an empty key path, since a bare `Value`\n"
    " has no path context.\n"
).
-spec as_string(value()) -> {ok, binary()} | {error, get_error()}.
as_string(Value) ->
    case Value of
        {string_value, Text} ->
            {ok, Text};

        _ ->
            {error, {wrong_type, [], expected_string}}
    end.

-file("src/tomlet.gleam", 860).
?DOC(" Read an integer from a `Value`. See `as_string` for the error convention.\n").
-spec as_int(value()) -> {ok, integer()} | {error, get_error()}.
as_int(Value) ->
    case Value of
        {int_value, Number} ->
            {ok, Number};

        _ ->
            {error, {wrong_type, [], expected_int}}
    end.

-file("src/tomlet.gleam", 868).
?DOC(" Read a boolean from a `Value`. See `as_string` for the error convention.\n").
-spec as_bool(value()) -> {ok, boolean()} | {error, get_error()}.
as_bool(Value) ->
    case Value of
        {bool_value, Boolean} ->
            {ok, Boolean};

        _ ->
            {error, {wrong_type, [], expected_bool}}
    end.

-file("src/tomlet.gleam", 879).
?DOC(
    " Read a float from a `Value`. See `as_string` for the error convention.\n"
    "\n"
    " Special floats (`inf`, `-inf`, `nan`) are not returned here; reading one\n"
    " yields `WrongType`. Match on `SpecialFloatValue` for those.\n"
).
-spec as_float(value()) -> {ok, float()} | {error, get_error()}.
as_float(Value) ->
    case Value of
        {float_value, Number} ->
            {ok, Number};

        _ ->
            {error, {wrong_type, [], expected_float}}
    end.

-file("src/tomlet.gleam", 887).
?DOC(" Read a local date from a `Value`. See `as_string` for the error convention.\n").
-spec as_date(value()) -> {ok, date()} | {error, get_error()}.
as_date(Value) ->
    case Value of
        {date_value, Date} ->
            {ok, Date};

        _ ->
            {error, {wrong_type, [], expected_date}}
    end.

-file("src/tomlet.gleam", 895).
?DOC(" Read a local time from a `Value`. See `as_string` for the error convention.\n").
-spec as_time(value()) -> {ok, time()} | {error, get_error()}.
as_time(Value) ->
    case Value of
        {time_value, Time} ->
            {ok, Time};

        _ ->
            {error, {wrong_type, [], expected_time}}
    end.

-file("src/tomlet.gleam", 903).
?DOC(" Read a date-time from a `Value`. See `as_string` for the error convention.\n").
-spec as_datetime(value()) -> {ok, date_time()} | {error, get_error()}.
as_datetime(Value) ->
    case Value of
        {date_time_value, Datetime} ->
            {ok, Datetime};

        _ ->
            {error, {wrong_type, [], expected_date_time}}
    end.

-file("src/tomlet.gleam", 2344).
-spec padded_hex(integer()) -> binary().
padded_hex(Value) ->
    Hex@1 = case gleam@int:to_base_string(Value, 16) of
        {ok, Hex} -> Hex;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"tomlet"/utf8>>,
                        function => <<"padded_hex"/utf8>>,
                        line => 2348,
                        value => _assert_fail,
                        start => 68936,
                        'end' => 68986,
                        pattern_start => 68947,
                        pattern_end => 68954})
    end,
    case string:length(Hex@1) of
        1 ->
            <<"000"/utf8, Hex@1/binary>>;

        2 ->
            <<"00"/utf8, Hex@1/binary>>;

        3 ->
            <<"0"/utf8, Hex@1/binary>>;

        _ ->
            Hex@1
    end.

-file("src/tomlet.gleam", 2323).
-spec escape_basic_string_codepoints(list(integer())) -> binary().
escape_basic_string_codepoints(Codepoints) ->
    case Codepoints of
        [] ->
            <<""/utf8>>;

        [Codepoint | Rest] ->
            Codepoint_int = gleam_stdlib:identity(Codepoint),
            Escaped = case Codepoint_int of
                8 ->
                    <<"\\b"/utf8>>;

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

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

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

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

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

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

                I when (I < 32) orelse (I =:= 127) ->
                    <<"\\u"/utf8, (padded_hex(I))/binary>>;

                _ ->
                    gleam_stdlib:utf_codepoint_list_to_string([Codepoint])
            end,
            <<Escaped/binary, (escape_basic_string_codepoints(Rest))/binary>>
    end.

-file("src/tomlet.gleam", 2317).
-spec escape_basic_string(binary()) -> binary().
escape_basic_string(Value) ->
    _pipe = Value,
    _pipe@1 = gleam@string:to_utf_codepoints(_pipe),
    escape_basic_string_codepoints(_pipe@1).

-file("src/tomlet.gleam", 2313).
-spec basic_string_repr(binary()) -> binary().
basic_string_repr(Value) ->
    <<<<"\""/utf8, (escape_basic_string(Value))/binary>>/binary, "\""/utf8>>.

-file("src/tomlet.gleam", 2267).
-spec key_segment_from_string(binary()) -> tomlet@ast:key_segment().
key_segment_from_string(Segment) ->
    case tomlet@key:is_bare_key(Segment) of
        true ->
            {bare_key_segment, Segment};

        false ->
            {quoted_key_segment, Segment, basic_string_repr(Segment)}
    end.

-file("src/tomlet.gleam", 2210).
-spec new_key_value(binary(), tomlet@ast:value()) -> tomlet@ast:entry().
new_key_value(Key, Value) ->
    {key_value,
        {trivia, <<""/utf8>>},
        {key, [key_segment_from_string(Key)]},
        Value,
        {trivia, <<"\n"/utf8>>}}.

-file("src/tomlet.gleam", 2263).
-spec key_from_strings(list(binary())) -> tomlet@ast:key().
key_from_strings(Segments) ->
    {key, gleam@list:map(Segments, fun key_segment_from_string/1)}.

-file("src/tomlet.gleam", 2228).
-spec new_table_header(list(binary())) -> tomlet@ast:entry().
new_table_header(Key) ->
    {table_header,
        {header, key_from_strings(Key), standard_table, {trivia, <<""/utf8>>}}}.

-file("src/tomlet.gleam", 2121).
-spec append_inside_table(list(tomlet@ast:entry()), tomlet@ast:entry()) -> {list(tomlet@ast:entry()),
    boolean()}.
append_inside_table(Entries, New_entry) ->
    case Entries of
        [] ->
            {[New_entry], true};

        [Entry | Rest] ->
            case Entry of
                {table_header, _} ->
                    {[New_entry, Entry | Rest], true};

                _ ->
                    {Updated_rest, Found} = append_inside_table(Rest, New_entry),
                    {[Entry | Updated_rest], Found}
            end
    end.

-file("src/tomlet.gleam", 2089).
-spec append_table_entry(
    list(tomlet@ast:entry()),
    list(binary()),
    tomlet@ast:entry()
) -> {list(tomlet@ast:entry()), boolean()}.
append_table_entry(Entries, Parent, New_entry) ->
    case Entries of
        [] ->
            {[], false};

        [Entry | Rest] ->
            case Entry of
                {table_header, Header} ->
                    case (header_key(Header) =:= Parent) andalso header_is_standard_table(
                        Header
                    ) of
                        true ->
                            {Updated_rest, Found} = append_inside_table(
                                Rest,
                                New_entry
                            ),
                            {[Entry | Updated_rest], Found};

                        false ->
                            {Updated_rest@1, Found@1} = append_table_entry(
                                Rest,
                                Parent,
                                New_entry
                            ),
                            {[Entry | Updated_rest@1], Found@1}
                    end;

                _ ->
                    {Updated_rest@2, Found@2} = append_table_entry(
                        Rest,
                        Parent,
                        New_entry
                    ),
                    {[Entry | Updated_rest@2], Found@2}
            end
    end.

-file("src/tomlet.gleam", 2075).
-spec append_root_entry(list(tomlet@ast:entry()), tomlet@ast:entry()) -> list(tomlet@ast:entry()).
append_root_entry(Entries, New_entry) ->
    case Entries of
        [] ->
            [New_entry];

        [Entry | Rest] ->
            case Entry of
                {table_header, _} ->
                    [New_entry, Entry | Rest];

                _ ->
                    [Entry | append_root_entry(Rest, New_entry)]
            end
    end.

-file("src/tomlet.gleam", 2057).
-spec append_new_entry(
    list(tomlet@ast:entry()),
    list(binary()),
    tomlet@ast:entry()
) -> list(tomlet@ast:entry()).
append_new_entry(Entries, Parent, New_entry) ->
    case Parent of
        [] ->
            append_root_entry(Entries, New_entry);

        _ ->
            {Updated_entries, Found} = append_table_entry(
                Entries,
                Parent,
                New_entry
            ),
            case Found of
                true ->
                    Updated_entries;

                false ->
                    lists:append(Entries, [new_table_header(Parent), New_entry])
            end
    end.

-file("src/tomlet.gleam", 2219).
-spec new_dotted_key_value(list(binary()), tomlet@ast:value()) -> tomlet@ast:entry().
new_dotted_key_value(Path, Value) ->
    {key_value,
        {trivia, <<""/utf8>>},
        key_from_strings(Path),
        Value,
        {trivia, <<"\n"/utf8>>}}.

-file("src/tomlet.gleam", 2030).
-spec dotted_key_context(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary())
) -> {ok, list(binary())} | {error, nil}.
dotted_key_context(Entries, Active_table, Parent) ->
    case Entries of
        [] ->
            {error, nil};

        [Entry | Rest] ->
            Next_active_table = case Entry of
                {table_header, Header} ->
                    header_key(Header);

                _ ->
                    Active_table
            end,
            case Entry of
                {key_value, _, Key, _, _} ->
                    Full_key = lists:append(Active_table, key_to_strings(Key)),
                    gleam@bool:guard(
                        tomlet@key:starts_with(Full_key, Parent) andalso (Full_key
                        /= Parent),
                        {ok, Active_table},
                        fun() ->
                            dotted_key_context(Rest, Next_active_table, Parent)
                        end
                    );

                _ ->
                    dotted_key_context(Rest, Next_active_table, Parent)
            end
    end.

-file("src/tomlet.gleam", 2007).
-spec insert_appended_entry(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    binary(),
    tomlet@ast:value()
) -> list(tomlet@ast:entry()).
insert_appended_entry(Entries, Key, Parent, Leaf, Value) ->
    Dotted_anchor = case Parent of
        [] ->
            {error, nil};

        _ ->
            dotted_key_context(Entries, [], Parent)
    end,
    case Dotted_anchor of
        {ok, Anchor} ->
            Relative = gleam@list:drop(Key, erlang:length(Anchor)),
            append_new_entry(
                Entries,
                Anchor,
                new_dotted_key_value(Relative, Value)
            );

        {error, nil} ->
            append_new_entry(Entries, Parent, new_key_value(Leaf, Value))
    end.

-file("src/tomlet.gleam", 2236).
-spec parent_and_leaf(list(binary())) -> {ok, {list(binary()), binary()}} |
    {error, nil}.
parent_and_leaf(Key) ->
    case Key of
        [] ->
            {error, nil};

        [Leaf] ->
            {ok, {[], Leaf}};

        [Segment | Rest] ->
            case parent_and_leaf(Rest) of
                {ok, {Parent, Leaf@1}} ->
                    {ok, {[Segment | Parent], Leaf@1}};

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

-file("src/tomlet.gleam", 2206).
-spec key_path_conflicts(list(binary()), list(binary())) -> boolean().
key_path_conflicts(Existing, Target) ->
    tomlet@key:conflicts(Existing, Target).

-file("src/tomlet.gleam", 2194).
-spec header_conflicts_with_new_key(tomlet@ast:header(), list(binary())) -> boolean().
header_conflicts_with_new_key(Header, Target) ->
    {header, _, Kind, _} = Header,
    Key = header_key(Header),
    case Kind of
        standard_table ->
            (Target =:= Key) orelse tomlet@key:starts_with(Key, Target);

        array_of_tables_header ->
            key_path_conflicts(Key, Target)
    end.

-file("src/tomlet.gleam", 2166).
-spec new_key_conflicts_with_table(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary())
) -> boolean().
new_key_conflicts_with_table(Entries, Active_table, Target) ->
    case Entries of
        [] ->
            false;

        [Entry | Rest] ->
            Next_active_table = case Entry of
                {table_header, Header} ->
                    header_key(Header);

                _ ->
                    Active_table
            end,
            case Entry of
                {table_header, Header@1} ->
                    header_conflicts_with_new_key(Header@1, Target) orelse new_key_conflicts_with_table(
                        Rest,
                        Next_active_table,
                        Target
                    );

                {key_value, _, Key, _, _} ->
                    Full_key = lists:append(Active_table, key_to_strings(Key)),
                    key_path_conflicts(Full_key, Target) orelse new_key_conflicts_with_table(
                        Rest,
                        Next_active_table,
                        Target
                    );

                _ ->
                    new_key_conflicts_with_table(
                        Rest,
                        Next_active_table,
                        Target
                    )
            end
    end.

-file("src/tomlet.gleam", 2138).
-spec new_key_conflicts(list(tomlet@ast:entry()), list(binary())) -> boolean().
new_key_conflicts(Entries, Target) ->
    new_key_conflicts_with_table(Entries, [], Target).

-file("src/tomlet.gleam", 2142).
-spec inline_table_blocks_key(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary())
) -> boolean().
inline_table_blocks_key(Entries, Active_table, Target) ->
    case Entries of
        [] ->
            false;

        [Entry | Rest] ->
            Next_active_table = case Entry of
                {table_header, Header} ->
                    header_key(Header);

                _ ->
                    Active_table
            end,
            Blocks = case Entry of
                {key_value, _, Key, {inline_table, _, _}, _} ->
                    Full_key = lists:append(Active_table, key_to_strings(Key)),
                    tomlet@key:starts_with(Target, Full_key) andalso (Full_key
                    /= Target);

                _ ->
                    false
            end,
            Blocks orelse inline_table_blocks_key(
                Rest,
                Next_active_table,
                Target
            )
    end.

-file("src/tomlet.gleam", 1787).
-spec emit_inline_table_entry(tomlet@ast:inline_table_entry()) -> binary().
emit_inline_table_entry(Entry) ->
    {inline_table_entry, Leading, Key, Value, Trailing} = Entry,
    <<<<<<<<(emit_trivia(Leading))/binary, (emit_key(Key))/binary>>/binary,
                " = "/utf8>>/binary,
            (emit_value(Value))/binary>>/binary,
        (emit_trivia(Trailing))/binary>>.

-file("src/tomlet.gleam", 1773).
-spec emit_inline_table(list(tomlet@ast:inline_table_entry())) -> binary().
emit_inline_table(Entries) ->
    case Entries of
        [] ->
            <<"{}"/utf8>>;

        _ ->
            <<<<"{ "/utf8,
                    (begin
                        _pipe = Entries,
                        _pipe@1 = gleam@list:map(
                            _pipe,
                            fun emit_inline_table_entry/1
                        ),
                        gleam@string:join(_pipe@1, <<", "/utf8>>)
                    end)/binary>>/binary,
                " }"/utf8>>
    end.

-file("src/tomlet.gleam", 1948).
-spec update_inline_entry(
    tomlet@ast:trivia(),
    tomlet@ast:key(),
    tomlet@ast:value(),
    tomlet@ast:trivia(),
    list(tomlet@ast:inline_table_entry()),
    list(binary()),
    list(binary()),
    tomlet@ast:value()
) -> {list(tomlet@ast:inline_table_entry()), boolean()}.
update_inline_entry(
    Leading,
    Key,
    Entry_value,
    Trailing,
    Rest,
    Active_path,
    Target,
    Value
) ->
    Full_key = lists:append(Active_path, key_to_strings(Key)),
    gleam@bool:guard(
        Full_key =:= Target,
        {[{inline_table_entry, Leading, Key, Value, Trailing} | Rest], true},
        fun() -> case Entry_value of
                {inline_table, Nested_entries, _} ->
                    {Updated_nested_entries, Nested_found} = update_inline_entries(
                        Nested_entries,
                        Full_key,
                        Target,
                        Value
                    ),
                    gleam@bool:lazy_guard(
                        not Nested_found,
                        fun() ->
                            {Updated_rest, Found} = update_inline_entries(
                                Rest,
                                Active_path,
                                Target,
                                Value
                            ),
                            {[{inline_table_entry,
                                        Leading,
                                        Key,
                                        Entry_value,
                                        Trailing} |
                                    Updated_rest],
                                Found}
                        end,
                        fun() ->
                            Updated_value = {inline_table,
                                Updated_nested_entries,
                                emit_inline_table(Updated_nested_entries)},
                            {[{inline_table_entry,
                                        Leading,
                                        Key,
                                        Updated_value,
                                        Trailing} |
                                    Rest],
                                true}
                        end
                    );

                _ ->
                    {Updated_rest@1, Found@1} = update_inline_entries(
                        Rest,
                        Active_path,
                        Target,
                        Value
                    ),
                    {[{inline_table_entry, Leading, Key, Entry_value, Trailing} |
                            Updated_rest@1],
                        Found@1}
            end end
    ).

-file("src/tomlet.gleam", 1918).
-spec update_inline_entries(
    list(tomlet@ast:inline_table_entry()),
    list(binary()),
    list(binary()),
    tomlet@ast:value()
) -> {list(tomlet@ast:inline_table_entry()), boolean()}.
update_inline_entries(Entries, Active_path, Target, Value) ->
    case Entries of
        [] ->
            {[], false};

        [{inline_table_entry, Leading, Key, Entry_value, Trailing} | Rest] ->
            update_inline_entry(
                Leading,
                Key,
                Entry_value,
                Trailing,
                Rest,
                Active_path,
                Target,
                Value
            )
    end.

-file("src/tomlet.gleam", 1834).
-spec update_key_value_entry(
    tomlet@ast:entry(),
    tomlet@ast:trivia(),
    tomlet@ast:key(),
    tomlet@ast:value(),
    tomlet@ast:trivia(),
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    list(binary()),
    tomlet@ast:value()
) -> {list(tomlet@ast:entry()), boolean()}.
update_key_value_entry(
    Entry,
    Leading,
    Key,
    Entry_value,
    Trailing,
    Rest,
    Active_table,
    Next_active_table,
    Target,
    Value
) ->
    Full_key = lists:append(Active_table, key_to_strings(Key)),
    gleam@bool:guard(
        Full_key =:= Target,
        {[{key_value, Leading, Key, Value, Trailing} | Rest], true},
        fun() -> case Entry_value of
                {inline_table, Inline_entries, _} ->
                    {Updated_inline_entries, Inline_found} = update_inline_entries(
                        Inline_entries,
                        Full_key,
                        Target,
                        Value
                    ),
                    gleam@bool:lazy_guard(
                        not Inline_found,
                        fun() ->
                            {Updated_rest, Found} = update_existing_entries(
                                Rest,
                                Next_active_table,
                                Target,
                                Value
                            ),
                            {[Entry | Updated_rest], Found}
                        end,
                        fun() ->
                            Updated_value = {inline_table,
                                Updated_inline_entries,
                                emit_inline_table(Updated_inline_entries)},
                            {[{key_value, Leading, Key, Updated_value, Trailing} |
                                    Rest],
                                true}
                        end
                    );

                _ ->
                    {Updated_rest@1, Found@1} = update_existing_entries(
                        Rest,
                        Next_active_table,
                        Target,
                        Value
                    ),
                    {[Entry | Updated_rest@1], Found@1}
            end end
    ).

-file("src/tomlet.gleam", 1875).
-spec update_existing_entries(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    tomlet@ast:value()
) -> {list(tomlet@ast:entry()), boolean()}.
update_existing_entries(Entries, Active_table, Target, Value) ->
    case Entries of
        [] ->
            {[], false};

        [Entry | Rest] ->
            Next_active_table = case Entry of
                {table_header, Header} ->
                    header_key(Header);

                _ ->
                    Active_table
            end,
            case Entry of
                {key_value, Leading, Key, Entry_value, Trailing} ->
                    update_key_value_entry(
                        Entry,
                        Leading,
                        Key,
                        Entry_value,
                        Trailing,
                        Rest,
                        Active_table,
                        Next_active_table,
                        Target,
                        Value
                    );

                _ ->
                    {Updated_rest, Found} = update_existing_entries(
                        Rest,
                        Next_active_table,
                        Target,
                        Value
                    ),
                    {[Entry | Updated_rest], Found}
            end
    end.

-file("src/tomlet.gleam", 2281).
-spec validate_key_segments(list(binary())) -> {ok, nil} | {error, edit_error()}.
validate_key_segments(Key) ->
    case Key of
        [] ->
            {ok, nil};

        [Segment | Rest] ->
            case gleam_stdlib:contains_string(Segment, <<"\n"/utf8>>) orelse gleam_stdlib:contains_string(
                Segment,
                <<"\r"/utf8>>
            ) of
                true ->
                    {error, {invalid_key_segment, Segment}};

                false ->
                    validate_key_segments(Rest)
            end
    end.

-file("src/tomlet.gleam", 2274).
-spec validate_edit_key(list(binary())) -> {ok, nil} | {error, edit_error()}.
validate_edit_key(Key) ->
    case Key of
        [] ->
            {error, empty_key_path};

        _ ->
            validate_key_segments(Key)
    end.

-file("src/tomlet.gleam", 1801).
-spec set_value(document(), list(binary()), tomlet@ast:value()) -> {ok,
        document()} |
    {error, edit_error()}.
set_value(Doc, Key, Value) ->
    gleam@result:'try'(
        validate_edit_key(Key),
        fun(_) ->
            {table, Entries, Header} = erlang:element(2, Doc),
            {Updated_entries, Found} = update_existing_entries(
                Entries,
                [],
                Key,
                Value
            ),
            gleam@bool:guard(
                Found,
                {ok, with_root(Doc, {table, Updated_entries, Header})},
                fun() ->
                    gleam@bool:guard(
                        inline_table_blocks_key(Entries, [], Key),
                        {error, {inline_table_insert_unsupported, Key}},
                        fun() ->
                            gleam@bool:guard(
                                new_key_conflicts(Entries, Key),
                                {error, {key_conflict, Key}},
                                fun() ->
                                    gleam@result:'try'(
                                        gleam@result:replace_error(
                                            parent_and_leaf(Key),
                                            empty_key_path
                                        ),
                                        fun(_use0) ->
                                            {Parent, Leaf} = _use0,
                                            Appended_entries = insert_appended_entry(
                                                Updated_entries,
                                                Key,
                                                Parent,
                                                Leaf,
                                                Value
                                            ),
                                            {ok,
                                                with_root(
                                                    Doc,
                                                    {table,
                                                        Appended_entries,
                                                        Header}
                                                )}
                                        end
                                    )
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/tomlet.gleam", 1137).
?DOC(
    " Set a TOML string value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.set_string(tomlet.new(), [\"package\", \"name\"], \"tomlet\")\n"
    " tomlet.to_string(doc)\n"
    " // -> \"\n"
    " // [package]\n"
    " // name = \\\"tomlet\\\"\n"
    " // \"\n"
    " ```\n"
).
-spec set_string(document(), list(binary()), binary()) -> {ok, document()} |
    {error, edit_error()}.
set_string(Doc, Key, Value) ->
    set_value(Doc, Key, {string, Value, basic_string, basic_string_repr(Value)}).

-file("src/tomlet.gleam", 1153).
?DOC(
    " Set a TOML integer value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_int(document(), list(binary()), integer()) -> {ok, document()} |
    {error, edit_error()}.
set_int(Doc, Key, Value) ->
    set_value(Doc, Key, {int, Value, erlang:integer_to_binary(Value)}).

-file("src/tomlet.gleam", 1165).
?DOC(
    " Set a TOML boolean value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_bool(document(), list(binary()), boolean()) -> {ok, document()} |
    {error, edit_error()}.
set_bool(Doc, Key, Value) ->
    Repr = case Value of
        true ->
            <<"true"/utf8>>;

        false ->
            <<"false"/utf8>>
    end,
    set_value(Doc, Key, {bool, Value, Repr}).

-file("src/tomlet.gleam", 1181).
?DOC(
    " Set a TOML float value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_float(document(), list(binary()), float()) -> {ok, document()} |
    {error, edit_error()}.
set_float(Doc, Key, Value) ->
    set_value(Doc, Key, {float, Value, gleam_stdlib:float_to_string(Value)}).

-file("src/tomlet.gleam", 1193).
?DOC(
    " Set a TOML local date value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_date(document(), list(binary()), date()) -> {ok, document()} |
    {error, edit_error()}.
set_date(Doc, Key, Value) ->
    set_value(Doc, Key, {date, erlang:element(2, Value)}).

-file("src/tomlet.gleam", 1205).
?DOC(
    " Set a TOML local time value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_time(document(), list(binary()), time()) -> {ok, document()} |
    {error, edit_error()}.
set_time(Doc, Key, Value) ->
    set_value(Doc, Key, {time, erlang:element(2, Value)}).

-file("src/tomlet.gleam", 1217).
?DOC(
    " Set a TOML date-time value at a key path.\n"
    "\n"
    " Existing values are replaced in place. Missing keys are inserted, creating a\n"
    " table header when needed.\n"
).
-spec set_datetime(document(), list(binary()), date_time()) -> {ok, document()} |
    {error, edit_error()}.
set_datetime(Doc, Key, Value) ->
    set_value(Doc, Key, {date_time, erlang:element(2, Value)}).

-file("src/tomlet.gleam", 1443).
-spec emit_array_items(list(tomlet@ast:array_item())) -> binary().
emit_array_items(Items) ->
    case Items of
        [] ->
            <<"[]"/utf8>>;

        _ ->
            <<<<"["/utf8,
                    (begin
                        _pipe = Items,
                        _pipe@1 = gleam@list:map(
                            _pipe,
                            fun(Item) ->
                                {array_item, _, Value, _} = Item,
                                emit_value(Value)
                            end
                        ),
                        gleam@string:join(_pipe@1, <<", "/utf8>>)
                    end)/binary>>/binary,
                "]"/utf8>>
    end.

-file("src/tomlet.gleam", 1430).
-spec value_to_inline_entry({list(binary()), value()}) -> {ok,
        tomlet@ast:inline_table_entry()} |
    {error, edit_error()}.
value_to_inline_entry(Entry) ->
    {Path, Value} = Entry,
    gleam@result:'try'(
        value_to_ast(Value),
        fun(Ast_value) ->
            {ok,
                {inline_table_entry,
                    {trivia, <<""/utf8>>},
                    key_from_strings(Path),
                    Ast_value,
                    {trivia, <<""/utf8>>}}}
        end
    ).

-file("src/tomlet.gleam", 1359).
-spec value_to_ast(value()) -> {ok, tomlet@ast:value()} | {error, edit_error()}.
value_to_ast(Value) ->
    case Value of
        {string_value, S} ->
            {ok, {string, S, basic_string, basic_string_repr(S)}};

        {int_value, I} ->
            {ok, {int, I, erlang:integer_to_binary(I)}};

        {float_value, F} ->
            {ok, {float, F, gleam_stdlib:float_to_string(F)}};

        {special_float_value, S@1} ->
            {Internal, Source_text} = case S@1 of
                positive_infinity ->
                    {positive_infinity, <<"inf"/utf8>>};

                negative_infinity ->
                    {negative_infinity, <<"-inf"/utf8>>};

                not_a_number ->
                    {not_a_number, <<"nan"/utf8>>}
            end,
            {ok, {special_float, Internal, Source_text}};

        {bool_value, B} ->
            Repr = case B of
                true ->
                    <<"true"/utf8>>;

                false ->
                    <<"false"/utf8>>
            end,
            {ok, {bool, B, Repr}};

        {date_value, D} ->
            {ok, {date, erlang:element(2, D)}};

        {time_value, T} ->
            {ok, {time, erlang:element(2, T)}};

        {date_time_value, D@1} ->
            {ok, {date_time, erlang:element(2, D@1)}};

        {array_value, Items} ->
            gleam@result:'try'(
                gleam@list:try_map(Items, fun value_to_array_item/1),
                fun(Ast_items) ->
                    {ok, {array, Ast_items, emit_array_items(Ast_items)}}
                end
            );

        {inline_table_value, Entries} ->
            gleam@result:'try'(
                gleam@list:try_map(Entries, fun value_to_inline_entry/1),
                fun(Ast_entries) ->
                    {ok,
                        {inline_table,
                            Ast_entries,
                            emit_inline_table(Ast_entries)}}
                end
            );

        {standard_table_value, _} ->
            {error, invalid_value};

        {array_of_tables_value, _} ->
            {error, invalid_value}
    end.

-file("src/tomlet.gleam", 1394).
-spec value_to_array_item(value()) -> {ok, tomlet@ast:array_item()} |
    {error, edit_error()}.
value_to_array_item(Value) ->
    gleam@result:'try'(
        value_to_ast(Value),
        fun(Ast_value) ->
            {ok,
                {array_item,
                    {trivia, <<""/utf8>>},
                    Ast_value,
                    {trivia, <<""/utf8>>}}}
        end
    ).

-file("src/tomlet.gleam", 1488).
-spec table_entry_conflicts(list(list(binary())), list(binary())) -> boolean().
table_entry_conflicts(Seen, Path) ->
    case Seen of
        [] ->
            false;

        [Existing | Rest] ->
            key_path_conflicts(Existing, Path) orelse table_entry_conflicts(
                Rest,
                Path
            )
    end.

-file("src/tomlet.gleam", 1466).
-spec validate_table_entries_loop(
    list({list(binary()), value()}),
    list(list(binary()))
) -> {ok, nil} | {error, edit_error()}.
validate_table_entries_loop(Entries, Seen) ->
    case Entries of
        [] ->
            {ok, nil};

        [{Path, Value} | Rest] ->
            case validate_edit_key(Path) of
                {error, Error} ->
                    {error, Error};

                {ok, nil} ->
                    case table_entry_conflicts(Seen, Path) of
                        true ->
                            {error, {key_conflict, Path}};

                        false ->
                            case validate_value(Value) of
                                {error, Error@1} ->
                                    {error, Error@1};

                                {ok, nil} ->
                                    validate_table_entries_loop(
                                        Rest,
                                        [Path | Seen]
                                    )
                            end
                    end
            end
    end.

-file("src/tomlet.gleam", 1460).
-spec validate_table_entries(list({list(binary()), value()})) -> {ok, nil} |
    {error, edit_error()}.
validate_table_entries(Entries) ->
    validate_table_entries_loop(Entries, []).

-file("src/tomlet.gleam", 1414).
-spec validate_value(value()) -> {ok, nil} | {error, edit_error()}.
validate_value(Value) ->
    case Value of
        {string_value, _} ->
            {ok, nil};

        {int_value, _} ->
            {ok, nil};

        {float_value, _} ->
            {ok, nil};

        {special_float_value, _} ->
            {ok, nil};

        {bool_value, _} ->
            {ok, nil};

        {date_value, _} ->
            {ok, nil};

        {time_value, _} ->
            {ok, nil};

        {date_time_value, _} ->
            {ok, nil};

        {array_value, Items} ->
            validate_values(Items);

        {inline_table_value, Entries} ->
            validate_table_entries(Entries);

        {standard_table_value, _} ->
            {error, invalid_value};

        {array_of_tables_value, _} ->
            {error, invalid_value}
    end.

-file("src/tomlet.gleam", 1403).
-spec validate_values(list(value())) -> {ok, nil} | {error, edit_error()}.
validate_values(Values) ->
    case Values of
        [] ->
            {ok, nil};

        [Value | Rest] ->
            case validate_value(Value) of
                {error, Error} ->
                    {error, Error};

                {ok, nil} ->
                    validate_values(Rest)
            end
    end.

-file("src/tomlet.gleam", 1244).
?DOC(
    " Set a TOML array value at a key path.\n"
    "\n"
    " Items are emitted in order using a default flow-style representation\n"
    " (`[a, b, c]`). Existing values are replaced in place. Missing keys are\n"
    " inserted, creating a table header when needed.\n"
    "\n"
    " `StandardTableValue` and `ArrayOfTablesValue` items are rejected with\n"
    " `InvalidValue`; use `set_inline_table` or `append_array_of_tables` for\n"
    " table-shaped values.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.set_array(tomlet.new(), [\"ports\"], [\n"
    "     tomlet.IntValue(8000),\n"
    "     tomlet.IntValue(8001),\n"
    "   ])\n"
    " tomlet.to_string(doc)\n"
    " // -> \"ports = [8000, 8001]\\n\"\n"
    " ```\n"
).
-spec set_array(document(), list(binary()), list(value())) -> {ok, document()} |
    {error, edit_error()}.
set_array(Doc, Key, Items) ->
    case validate_values(Items) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            gleam@result:'try'(
                gleam@list:try_map(Items, fun value_to_array_item/1),
                fun(Ast_items) ->
                    set_value(
                        Doc,
                        Key,
                        {array, Ast_items, emit_array_items(Ast_items)}
                    )
                end
            )
    end.

-file("src/tomlet.gleam", 1277).
?DOC(
    " Set a TOML inline table value at a key path.\n"
    "\n"
    " Entries are emitted in order using a default flow-style representation\n"
    " (`{ a = 1, b = 2 }`). Each entry's key path is rendered as a dotted key\n"
    " when it contains more than one segment. Existing values are replaced in\n"
    " place. Missing keys are inserted, creating a table header when needed.\n"
    "\n"
    " Entry values that are `StandardTableValue` or `ArrayOfTablesValue` are\n"
    " rejected with `InvalidValue`; nest an `InlineTableValue` instead.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.set_inline_table(tomlet.new(), [\"pkg\"], [\n"
    "     #([\"name\"], tomlet.StringValue(\"tomato\")),\n"
    "     #([\"meta\", \"downloads\"], tomlet.IntValue(42)),\n"
    "   ])\n"
    " tomlet.to_string(doc)\n"
    " // -> \"pkg = { name = \\\"tomato\\\", meta.downloads = 42 }\\n\"\n"
    " ```\n"
).
-spec set_inline_table(
    document(),
    list(binary()),
    list({list(binary()), value()})
) -> {ok, document()} | {error, edit_error()}.
set_inline_table(Doc, Key, Entries) ->
    case validate_table_entries(Entries) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            gleam@result:'try'(
                gleam@list:try_map(Entries, fun value_to_inline_entry/1),
                fun(Ast_entries) ->
                    set_value(
                        Doc,
                        Key,
                        {inline_table,
                            Ast_entries,
                            emit_inline_table(Ast_entries)}
                    )
                end
            )
    end.

-file("src/tomlet.gleam", 1496).
-spec entry_conflicts_with_target(
    tomlet@ast:entry(),
    list(binary()),
    boolean(),
    list(binary())
) -> boolean().
entry_conflicts_with_target(Entry, Active_table, In_array_of_tables, Target) ->
    case Entry of
        {table_header, {header, Key, standard_table, _}} ->
            key_to_strings(Key) =:= Target;

        {table_header, {header, Key@1, array_of_tables_header, _}} ->
            Header_key = key_to_strings(Key@1),
            (Header_key /= Target) andalso key_path_conflicts(
                Header_key,
                Target
            );

        {key_value, _, Key@2, _, _} ->
            case In_array_of_tables of
                true ->
                    false;

                false ->
                    Full_key = lists:append(Active_table, key_to_strings(Key@2)),
                    key_path_conflicts(Full_key, Target)
            end;

        _ ->
            false
    end.

-file("src/tomlet.gleam", 1530).
-spec array_of_tables_key_conflicts(
    list(tomlet@ast:entry()),
    list(binary()),
    boolean(),
    list(binary())
) -> boolean().
array_of_tables_key_conflicts(Entries, Active_table, In_array_of_tables, Target) ->
    case Entries of
        [] ->
            false;

        [Entry | Rest] ->
            {Next_active_table, Next_in_aot} = case Entry of
                {table_header, {header, Key, Kind, _}} ->
                    {key_to_strings(Key), Kind =:= array_of_tables_header};

                _ ->
                    {Active_table, In_array_of_tables}
            end,
            Conflicts = entry_conflicts_with_target(
                Entry,
                Active_table,
                In_array_of_tables,
                Target
            ),
            Conflicts orelse array_of_tables_key_conflicts(
                Rest,
                Next_active_table,
                Next_in_aot,
                Target
            )
    end.

-file("src/tomlet.gleam", 1312).
?DOC(
    " Append a new table to an array of tables at a key path.\n"
    "\n"
    " A `[[key]]` header is appended to the document followed by the supplied\n"
    " entries. Works whether or not an array of tables already exists at the key\n"
    " path; if no array of tables exists yet, a new one is created.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) =\n"
    "   tomlet.append_array_of_tables(tomlet.new(), [\"packages\"], [\n"
    "     #([\"name\"], tomlet.StringValue(\"tomato\")),\n"
    "   ])\n"
    " tomlet.to_string(doc)\n"
    " // -> \"\n"
    " // [[packages]]\n"
    " // name = \\\"tomato\\\"\n"
    " // \"\n"
    " ```\n"
).
-spec append_array_of_tables(
    document(),
    list(binary()),
    list({list(binary()), value()})
) -> {ok, document()} | {error, edit_error()}.
append_array_of_tables(Doc, Key, Entries) ->
    gleam@result:'try'(
        validate_edit_key(Key),
        fun(_) ->
            gleam@result:'try'(
                validate_table_entries(Entries),
                fun(_) ->
                    {document, {table, Doc_entries, Header}, _, _, _} = Doc,
                    gleam@bool:guard(
                        array_of_tables_key_conflicts(
                            Doc_entries,
                            [],
                            false,
                            Key
                        ),
                        {error, {key_conflict, Key}},
                        fun() ->
                            gleam@result:'try'(
                                gleam@list:try_map(
                                    Entries,
                                    fun(Entry) ->
                                        {Path, Value} = Entry,
                                        gleam@result:'try'(
                                            value_to_ast(Value),
                                            fun(Ast_value) ->
                                                {ok,
                                                    {key_value,
                                                        {trivia, <<""/utf8>>},
                                                        key_from_strings(Path),
                                                        Ast_value,
                                                        {trivia, <<"\n"/utf8>>}}}
                                            end
                                        )
                                    end
                                ),
                                fun(Table_entries) ->
                                    New_entries = lists:append(
                                        [{table_header,
                                                {header,
                                                    key_from_strings(Key),
                                                    array_of_tables_header,
                                                    {trivia, <<""/utf8>>}}}],
                                        Table_entries
                                    ),
                                    {ok,
                                        with_root(
                                            Doc,
                                            {table,
                                                lists:append(
                                                    Doc_entries,
                                                    New_entries
                                                ),
                                                Header}
                                        )}
                                end
                            )
                        end
                    )
                end
            )
        end
    ).

-file("src/tomlet.gleam", 1688).
-spec remove_entries(list(tomlet@ast:entry()), list(binary()), list(binary())) -> {list(tomlet@ast:entry()),
    boolean()}.
remove_entries(Entries, Active_table, Target) ->
    case Entries of
        [] ->
            {[], false};

        [Entry | Rest] ->
            case Entry of
                {table_header, {header, Key, _, _}} ->
                    Table_key = key_to_strings(Key),
                    {Next_rest, Removed} = remove_entries(
                        Rest,
                        Table_key,
                        Target
                    ),
                    {[Entry | Next_rest], Removed};

                {key_value, _, Key@1, _, _} ->
                    Full_key = lists:append(Active_table, key_to_strings(Key@1)),
                    {Next_rest@1, Removed@1} = remove_entries(
                        Rest,
                        Active_table,
                        Target
                    ),
                    case Full_key =:= Target of
                        true ->
                            {Next_rest@1, true};

                        false ->
                            {[Entry | Next_rest@1], Removed@1}
                    end;

                _ ->
                    {Next_rest@2, Removed@2} = remove_entries(
                        Rest,
                        Active_table,
                        Target
                    ),
                    {[Entry | Next_rest@2], Removed@2}
            end
    end.

-file("src/tomlet.gleam", 1568).
?DOC(
    " Remove an existing value from a document.\n"
    "\n"
    " Returns `MissingEditKey` when the key path does not exist, and\n"
    " `EmptyKeyPath` when the key path is empty.\n"
).
-spec remove(document(), list(binary())) -> {ok, document()} |
    {error, edit_error()}.
remove(Doc, Key) ->
    case validate_edit_key(Key) of
        {error, Error} ->
            {error, Error};

        {ok, nil} ->
            {document, {table, Entries, Header}, _, _, _} = Doc,
            {Next_entries, Removed} = remove_entries(Entries, [], Key),
            case Removed of
                true ->
                    {ok, with_root(Doc, {table, Next_entries, Header})};

                false ->
                    {error, {missing_edit_key, Key}}
            end
    end.

-file("src/tomlet.gleam", 1666).
-spec normalize_comment_text(binary()) -> binary().
normalize_comment_text(Text) ->
    Trimmed = gleam@string:trim(Text),
    case Trimmed of
        <<""/utf8>> ->
            <<"#"/utf8>>;

        _ ->
            case gleam_stdlib:string_starts_with(Trimmed, <<"#"/utf8>>) of
                true ->
                    Trimmed;

                false ->
                    <<"# "/utf8, Trimmed/binary>>
            end
    end.

-file("src/tomlet.gleam", 1627).
-spec insert_comment_before_entries(
    list(tomlet@ast:entry()),
    list(binary()),
    list(binary()),
    tomlet@ast:entry()
) -> {list(tomlet@ast:entry()), boolean()}.
insert_comment_before_entries(Entries, Target, Active_table, Comment) ->
    case Entries of
        [] ->
            {[], false};

        [Entry | Rest] ->
            case Entry of
                {table_header, Header} ->
                    Table_key = header_key(Header),
                    gleam@bool:guard(
                        Table_key =:= Target,
                        {[Comment, Entry | Rest], true},
                        fun() ->
                            {Updated_rest, Inserted} = insert_comment_before_entries(
                                Rest,
                                Target,
                                Table_key,
                                Comment
                            ),
                            {[Entry | Updated_rest], Inserted}
                        end
                    );

                {key_value, _, Entry_key, _, _} ->
                    Full_key = lists:append(
                        Active_table,
                        key_to_strings(Entry_key)
                    ),
                    gleam@bool:guard(
                        Full_key =:= Target,
                        {[Comment, Entry | Rest], true},
                        fun() ->
                            {Updated_rest@1, Inserted@1} = insert_comment_before_entries(
                                Rest,
                                Target,
                                Active_table,
                                Comment
                            ),
                            {[Entry | Updated_rest@1], Inserted@1}
                        end
                    );

                _ ->
                    {Updated_rest@2, Inserted@2} = insert_comment_before_entries(
                        Rest,
                        Target,
                        Active_table,
                        Comment
                    ),
                    {[Entry | Updated_rest@2], Inserted@2}
            end
    end.

-file("src/tomlet.gleam", 2298).
-spec validate_comment_codepoints(list(integer())) -> {ok, nil} |
    {error, edit_error()}.
validate_comment_codepoints(Codepoints) ->
    case Codepoints of
        [] ->
            {ok, nil};

        [Codepoint | Rest] ->
            Value = gleam_stdlib:identity(Codepoint),
            case ((Value =< 8) orelse ((Value >= 10) andalso (Value =< 31)))
            orelse (Value =:= 127) of
                true ->
                    {error, invalid_comment_text};

                false ->
                    validate_comment_codepoints(Rest)
            end
    end.

-file("src/tomlet.gleam", 2292).
-spec validate_comment_text(binary()) -> {ok, nil} | {error, edit_error()}.
validate_comment_text(Text) ->
    _pipe = Text,
    _pipe@1 = gleam@string:to_utf_codepoints(_pipe),
    validate_comment_codepoints(_pipe@1).

-file("src/tomlet.gleam", 1600).
?DOC(
    " Insert a standalone comment before an existing key.\n"
    "\n"
    " The comment text may include a leading `#`, but must not contain TOML\n"
    " comment control characters. Returns `MissingEditKey` when the target key\n"
    " does not exist, `InvalidCommentText` when the comment is unsafe to emit, and\n"
    " `EmptyKeyPath` when the key path is empty.\n"
    "\n"
    " ```gleam\n"
    " let assert Ok(doc) = tomlet.parse(\"released = 1979-05-27\\n\")\n"
    " let assert Ok(doc) =\n"
    "   tomlet.insert_comment_before(doc, [\"released\"], \"release date\")\n"
    " tomlet.to_string(doc)\n"
    " // -> \"\n"
    " // # release date\n"
    " // released = 1979-05-27\n"
    " // \"\n"
    " ```\n"
).
-spec insert_comment_before(document(), list(binary()), binary()) -> {ok,
        document()} |
    {error, edit_error()}.
insert_comment_before(Doc, Key, Text) ->
    case {validate_edit_key(Key), validate_comment_text(Text)} of
        {{error, Error}, _} ->
            {error, Error};

        {_, {error, Error@1}} ->
            {error, Error@1};

        {{ok, nil}, {ok, nil}} ->
            {document, {table, Entries, Header}, _, _, _} = Doc,
            {Updated_entries, Inserted} = insert_comment_before_entries(
                Entries,
                Key,
                [],
                {comment, normalize_comment_text(Text)}
            ),
            case Inserted of
                true ->
                    {ok, with_root(Doc, {table, Updated_entries, Header})};

                false ->
                    {error, {missing_edit_key, Key}}
            end
    end.