aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/tom/src
diff options
context:
space:
mode:
Diffstat (limited to 'aoc2023/build/packages/tom/src')
-rw-r--r--aoc2023/build/packages/tom/src/tom.app.src8
-rw-r--r--aoc2023/build/packages/tom/src/tom.erl2140
-rw-r--r--aoc2023/build/packages/tom/src/tom.gleam1317
3 files changed, 3465 insertions, 0 deletions
diff --git a/aoc2023/build/packages/tom/src/tom.app.src b/aoc2023/build/packages/tom/src/tom.app.src
new file mode 100644
index 0000000..051649c
--- /dev/null
+++ b/aoc2023/build/packages/tom/src/tom.app.src
@@ -0,0 +1,8 @@
+{application, tom, [
+ {vsn, "0.2.1"},
+ {applications, [gleam_stdlib,
+ gleeunit]},
+ {description, "A pure Gleam TOML parser!"},
+ {modules, [tom]},
+ {registered, []}
+]}.
diff --git a/aoc2023/build/packages/tom/src/tom.erl b/aoc2023/build/packages/tom/src/tom.erl
new file mode 100644
index 0000000..4f5c071
--- /dev/null
+++ b/aoc2023/build/packages/tom/src/tom.erl
@@ -0,0 +1,2140 @@
+-module(tom).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([get/2, get_int/2, get_float/2, get_bool/2, get_string/2, get_date/2, get_time/2, get_date_time/2, get_array/2, get_table/2, get_number/2, parse/1]).
+-export_type([toml/0, date_time/0, date/0, time/0, offset/0, sign/0, parse_error/0, number_/0, get_error/0]).
+
+-type toml() :: {int, integer()} |
+ {float, float()} |
+ {infinity, sign()} |
+ {nan, sign()} |
+ {bool, boolean()} |
+ {string, binary()} |
+ {date, date()} |
+ {time, time()} |
+ {date_time, date_time()} |
+ {array, list(toml())} |
+ {array_of_tables, list(gleam@map:map_(binary(), toml()))} |
+ {table, gleam@map:map_(binary(), toml())} |
+ {inline_table, gleam@map:map_(binary(), toml())}.
+
+-type date_time() :: {date_time_value, date(), time(), offset()}.
+
+-type date() :: {date_value, integer(), integer(), integer()}.
+
+-type time() :: {time_value, integer(), integer(), integer(), integer()}.
+
+-type offset() :: local | {offset, sign(), integer(), integer()}.
+
+-type sign() :: positive | negative.
+
+-type parse_error() :: {unexpected, binary(), binary()} |
+ {key_already_in_use, list(binary())}.
+
+-type number_() :: {number_int, integer()} |
+ {number_float, float()} |
+ {number_infinity, sign()} |
+ {number_nan, sign()}.
+
+-type get_error() :: {not_found, list(binary())} |
+ {wrong_type, list(binary()), binary(), binary()}.
+
+-spec classify(toml()) -> binary().
+classify(Toml) ->
+ case Toml of
+ {int, _} ->
+ <<"Int"/utf8>>;
+
+ {float, _} ->
+ <<"Float"/utf8>>;
+
+ {nan, positive} ->
+ <<"NaN"/utf8>>;
+
+ {nan, negative} ->
+ <<"Negative NaN"/utf8>>;
+
+ {infinity, positive} ->
+ <<"Infinity"/utf8>>;
+
+ {infinity, negative} ->
+ <<"Negative Infinity"/utf8>>;
+
+ {bool, _} ->
+ <<"Bool"/utf8>>;
+
+ {string, _} ->
+ <<"String"/utf8>>;
+
+ {date, _} ->
+ <<"Date"/utf8>>;
+
+ {time, _} ->
+ <<"Time"/utf8>>;
+
+ {date_time, _} ->
+ <<"DateTime"/utf8>>;
+
+ {array, _} ->
+ <<"Array"/utf8>>;
+
+ {array_of_tables, _} ->
+ <<"Array"/utf8>>;
+
+ {table, _} ->
+ <<"Table"/utf8>>;
+
+ {inline_table, _} ->
+ <<"Table"/utf8>>
+ end.
+
+-spec push_key({ok, FIU} | {error, get_error()}, binary()) -> {ok, FIU} |
+ {error, get_error()}.
+push_key(Result, Key) ->
+ case Result of
+ {ok, T} ->
+ {ok, T};
+
+ {error, {not_found, Path}} ->
+ {error, {not_found, [Key | Path]}};
+
+ {error, {wrong_type, Path@1, Expected, Got}} ->
+ {error, {wrong_type, [Key | Path@1], Expected, Got}}
+ end.
+
+-spec get(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, toml()} |
+ {error, get_error()}.
+get(Toml, Key) ->
+ case Key of
+ [] ->
+ {error, {not_found, []}};
+
+ [K] ->
+ gleam@result:replace_error(gleam@map:get(Toml, K), {not_found, [K]});
+
+ [K@1 | Key@1] ->
+ case gleam@map:get(Toml, K@1) of
+ {ok, {table, T}} ->
+ push_key(get(T, Key@1), K@1);
+
+ {ok, Other} ->
+ {error,
+ {wrong_type, [K@1], <<"Table"/utf8>>, classify(Other)}};
+
+ {error, _} ->
+ {error, {not_found, [K@1]}}
+ end
+ end.
+
+-spec get_int(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ integer()} |
+ {error, get_error()}.
+get_int(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {int, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Int"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_float(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ float()} |
+ {error, get_error()}.
+get_float(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {float, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Float"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_bool(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ boolean()} |
+ {error, get_error()}.
+get_bool(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {bool, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Bool"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_string(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ binary()} |
+ {error, get_error()}.
+get_string(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {string, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"String"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_date(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, date()} |
+ {error, get_error()}.
+get_date(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {date, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Date"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_time(gleam@map:map_(binary(), toml()), list(binary())) -> {ok, time()} |
+ {error, get_error()}.
+get_time(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {time, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Time"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_date_time(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ date_time()} |
+ {error, get_error()}.
+get_date_time(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {date_time, I}} ->
+ {ok, I};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"DateTime"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_array(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ list(toml())} |
+ {error, get_error()}.
+get_array(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {array, I}} ->
+ {ok, I};
+
+ {ok, {array_of_tables, I@1}} ->
+ {ok, gleam@list:map(I@1, fun(Field@0) -> {table, Field@0} end)};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Array"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_table(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ gleam@map:map_(binary(), toml())} |
+ {error, get_error()}.
+get_table(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {table, I}} ->
+ {ok, I};
+
+ {ok, {inline_table, I@1}} ->
+ {ok, I@1};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Table"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec get_number(gleam@map:map_(binary(), toml()), list(binary())) -> {ok,
+ number_()} |
+ {error, get_error()}.
+get_number(Toml, Key) ->
+ case get(Toml, Key) of
+ {ok, {int, X}} ->
+ {ok, {number_int, X}};
+
+ {ok, {float, X@1}} ->
+ {ok, {number_float, X@1}};
+
+ {ok, {nan, X@2}} ->
+ {ok, {number_nan, X@2}};
+
+ {ok, {infinity, X@3}} ->
+ {ok, {number_infinity, X@3}};
+
+ {ok, Other} ->
+ {error, {wrong_type, Key, <<"Number"/utf8>>, classify(Other)}};
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec merge(gleam@map:map_(binary(), toml()), binary(), toml(), toml()) -> {ok,
+ gleam@map:map_(binary(), toml())} |
+ {error, list(binary())}.
+merge(Table, Key, Old, New) ->
+ case {Old, New} of
+ {{array_of_tables, Tables}, {array_of_tables, New@1}} ->
+ {ok,
+ gleam@map:insert(
+ Table,
+ Key,
+ {array_of_tables, gleam@list:append(New@1, Tables)}
+ )};
+
+ {_, _} ->
+ {error, [Key]}
+ end.
+
+-spec insert_loop(gleam@map:map_(binary(), toml()), list(binary()), toml()) -> {ok,
+ gleam@map:map_(binary(), toml())} |
+ {error, list(binary())}.
+insert_loop(Table, Key, Value) ->
+ case Key of
+ [] ->
+ erlang:error(#{gleam_error => panic,
+ message => <<"unreachable"/utf8>>,
+ module => <<"tom"/utf8>>,
+ function => <<"insert_loop"/utf8>>,
+ line => 511});
+
+ [K] ->
+ case gleam@map:get(Table, K) of
+ {error, nil} ->
+ {ok, gleam@map:insert(Table, K, Value)};
+
+ {ok, Old} ->
+ merge(Table, K, Old, Value)
+ end;
+
+ [K@1 | Key@1] ->
+ case gleam@map:get(Table, K@1) of
+ {error, nil} ->
+ case insert_loop(gleam@map:new(), Key@1, Value) of
+ {ok, Inner} ->
+ {ok, gleam@map:insert(Table, K@1, {table, Inner})};
+
+ {error, Path} ->
+ {error, [K@1 | Path]}
+ end;
+
+ {ok, {array_of_tables, [Inner@1 | Rest]}} ->
+ case insert_loop(Inner@1, Key@1, Value) of
+ {ok, Inner@2} ->
+ {ok,
+ gleam@map:insert(
+ Table,
+ K@1,
+ {array_of_tables, [Inner@2 | Rest]}
+ )};
+
+ {error, Path@1} ->
+ {error, [K@1 | Path@1]}
+ end;
+
+ {ok, {table, Inner@3}} ->
+ case insert_loop(Inner@3, Key@1, Value) of
+ {ok, Inner@4} ->
+ {ok, gleam@map:insert(Table, K@1, {table, Inner@4})};
+
+ {error, Path@2} ->
+ {error, [K@1 | Path@2]}
+ end;
+
+ {ok, _} ->
+ {error, [K@1]}
+ end
+ end.
+
+-spec insert(gleam@map:map_(binary(), toml()), list(binary()), toml()) -> {ok,
+ gleam@map:map_(binary(), toml())} |
+ {error, parse_error()}.
+insert(Table, Key, Value) ->
+ case insert_loop(Table, Key, Value) of
+ {ok, Table@1} ->
+ {ok, Table@1};
+
+ {error, Path} ->
+ {error, {key_already_in_use, Path}}
+ end.
+
+-spec expect_end_of_line(
+ list(binary()),
+ fun((list(binary())) -> {ok, {FKZ, list(binary())}} | {error, parse_error()})
+) -> {ok, {FKZ, list(binary())}} | {error, parse_error()}.
+expect_end_of_line(Input, Next) ->
+ case Input of
+ [<<"\n"/utf8>> | Input@1] ->
+ Next(Input@1);
+
+ [<<"\r\n"/utf8>> | Input@2] ->
+ Next(Input@2);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"\n"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"\n"/utf8>>}}
+ end.
+
+-spec parse_key_quoted(list(binary()), binary(), binary()) -> {ok,
+ {binary(), list(binary())}} |
+ {error, parse_error()}.
+parse_key_quoted(Input, Close, Name) ->
+ case Input of
+ [G | Input@1] when G =:= Close ->
+ {ok, {Name, Input@1}};
+
+ [G@1 | Input@2] ->
+ parse_key_quoted(Input@2, Close, <<Name/binary, G@1/binary>>);
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, Close}}
+ end.
+
+-spec parse_key_bare(list(binary()), binary()) -> {ok,
+ {binary(), list(binary())}} |
+ {error, parse_error()}.
+parse_key_bare(Input, Name) ->
+ case Input of
+ [<<" "/utf8>> | Input@1] when Name =/= <<""/utf8>> ->
+ {ok, {Name, Input@1}};
+
+ [<<"="/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {ok, {Name, Input}};
+
+ [<<"."/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {ok, {Name, Input}};
+
+ [<<"]"/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {ok, {Name, Input}};
+
+ [<<","/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {error, {unexpected, <<","/utf8>>, <<"="/utf8>>}};
+
+ [<<"\n"/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {error, {unexpected, <<"\n"/utf8>>, <<"="/utf8>>}};
+
+ [<<"\r\n"/utf8>> | _] when Name =/= <<""/utf8>> ->
+ {error, {unexpected, <<"\r\n"/utf8>>, <<"="/utf8>>}};
+
+ [<<"\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\n"/utf8>>, <<"key"/utf8>>}};
+
+ [<<"\r\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\r\n"/utf8>>, <<"key"/utf8>>}};
+
+ [<<"]"/utf8>> | _] ->
+ {error, {unexpected, <<"]"/utf8>>, <<"key"/utf8>>}};
+
+ [<<","/utf8>> | _] ->
+ {error, {unexpected, <<","/utf8>>, <<"key"/utf8>>}};
+
+ [G | Input@2] ->
+ parse_key_bare(Input@2, <<Name/binary, G/binary>>);
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"key"/utf8>>}}
+ end.
+
+-spec skip_line_whitespace(list(binary())) -> list(binary()).
+skip_line_whitespace(Input) ->
+ gleam@list:drop_while(
+ Input,
+ fun(G) -> (G =:= <<" "/utf8>>) orelse (G =:= <<"\t"/utf8>>) end
+ ).
+
+-spec parse_key_segment(list(binary())) -> {ok, {binary(), list(binary())}} |
+ {error, parse_error()}.
+parse_key_segment(Input) ->
+ Input@1 = skip_line_whitespace(Input),
+ case Input@1 of
+ [<<"="/utf8>> | _] ->
+ {error, {unexpected, <<"="/utf8>>, <<"Key"/utf8>>}};
+
+ [<<"\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\n"/utf8>>, <<"Key"/utf8>>}};
+
+ [<<"\r\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\r\n"/utf8>>, <<"Key"/utf8>>}};
+
+ [<<"["/utf8>> | _] ->
+ {error, {unexpected, <<"["/utf8>>, <<"Key"/utf8>>}};
+
+ [<<"\""/utf8>> | Input@2] ->
+ parse_key_quoted(Input@2, <<"\""/utf8>>, <<""/utf8>>);
+
+ [<<"'"/utf8>> | Input@3] ->
+ parse_key_quoted(Input@3, <<"'"/utf8>>, <<""/utf8>>);
+
+ _ ->
+ parse_key_bare(Input@1, <<""/utf8>>)
+ end.
+
+-spec skip_whitespace(list(binary())) -> list(binary()).
+skip_whitespace(Input) ->
+ case Input of
+ [<<" "/utf8>> | Input@1] ->
+ skip_whitespace(Input@1);
+
+ [<<"\t"/utf8>> | Input@2] ->
+ skip_whitespace(Input@2);
+
+ [<<"\n"/utf8>> | Input@3] ->
+ skip_whitespace(Input@3);
+
+ [<<"\r\n"/utf8>> | Input@4] ->
+ skip_whitespace(Input@4);
+
+ Input@5 ->
+ Input@5
+ end.
+
+-spec drop_comments(list(binary()), list(binary())) -> list(binary()).
+drop_comments(Input, Acc) ->
+ case Input of
+ [<<"#"/utf8>> | Input@1] ->
+ _pipe = Input@1,
+ _pipe@1 = gleam@list:drop_while(
+ _pipe,
+ fun(G) -> G /= <<"\n"/utf8>> end
+ ),
+ drop_comments(_pipe@1, Acc);
+
+ [G@1 | Input@2] ->
+ drop_comments(Input@2, [G@1 | Acc]);
+
+ [] ->
+ gleam@list:reverse(Acc)
+ end.
+
+-spec do(
+ {ok, {FLK, list(binary())}} | {error, parse_error()},
+ fun((FLK, list(binary())) -> {ok, FLN} | {error, parse_error()})
+) -> {ok, FLN} | {error, parse_error()}.
+do(Result, Next) ->
+ case Result of
+ {ok, {A, Input}} ->
+ Next(A, Input);
+
+ {error, E} ->
+ {error, E}
+ end.
+
+-spec parse_key(list(binary()), list(binary())) -> {ok,
+ {list(binary()), list(binary())}} |
+ {error, parse_error()}.
+parse_key(Input, Segments) ->
+ do(
+ parse_key_segment(Input),
+ fun(Segment, Input@1) ->
+ Segments@1 = [Segment | Segments],
+ Input@2 = skip_line_whitespace(Input@1),
+ case Input@2 of
+ [<<"."/utf8>> | Input@3] ->
+ parse_key(Input@3, Segments@1);
+
+ _ ->
+ {ok, {gleam@list:reverse(Segments@1), Input@2}}
+ end
+ end
+ ).
+
+-spec expect(
+ list(binary()),
+ binary(),
+ fun((list(binary())) -> {ok, {FLS, list(binary())}} | {error, parse_error()})
+) -> {ok, {FLS, list(binary())}} | {error, parse_error()}.
+expect(Input, Expected, Next) ->
+ case Input of
+ [G | Input@1] when G =:= Expected ->
+ Next(Input@1);
+
+ [G@1 | _] ->
+ {error, {unexpected, G@1, Expected}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, Expected}}
+ end.
+
+-spec parse_table_header(list(binary())) -> {ok,
+ {list(binary()), list(binary())}} |
+ {error, parse_error()}.
+parse_table_header(Input) ->
+ Input@1 = skip_line_whitespace(Input),
+ do(
+ parse_key(Input@1, []),
+ fun(Key, Input@2) ->
+ expect(
+ Input@2,
+ <<"]"/utf8>>,
+ fun(Input@3) ->
+ Input@4 = skip_line_whitespace(Input@3),
+ expect_end_of_line(
+ Input@4,
+ fun(Input@5) -> {ok, {Key, Input@5}} end
+ )
+ end
+ )
+ end
+ ).
+
+-spec parse_hex(list(binary()), integer(), sign()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_hex(Input, Number, Sign) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_hex(Input@1, Number, Sign);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_hex(Input@2, (Number * 16) + 0, Sign);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_hex(Input@3, (Number * 16) + 1, Sign);
+
+ [<<"2"/utf8>> | Input@4] ->
+ parse_hex(Input@4, (Number * 16) + 2, Sign);
+
+ [<<"3"/utf8>> | Input@5] ->
+ parse_hex(Input@5, (Number * 16) + 3, Sign);
+
+ [<<"4"/utf8>> | Input@6] ->
+ parse_hex(Input@6, (Number * 16) + 4, Sign);
+
+ [<<"5"/utf8>> | Input@7] ->
+ parse_hex(Input@7, (Number * 16) + 5, Sign);
+
+ [<<"6"/utf8>> | Input@8] ->
+ parse_hex(Input@8, (Number * 16) + 6, Sign);
+
+ [<<"7"/utf8>> | Input@9] ->
+ parse_hex(Input@9, (Number * 16) + 7, Sign);
+
+ [<<"8"/utf8>> | Input@10] ->
+ parse_hex(Input@10, (Number * 16) + 8, Sign);
+
+ [<<"9"/utf8>> | Input@11] ->
+ parse_hex(Input@11, (Number * 16) + 9, Sign);
+
+ [<<"a"/utf8>> | Input@12] ->
+ parse_hex(Input@12, (Number * 16) + 10, Sign);
+
+ [<<"b"/utf8>> | Input@13] ->
+ parse_hex(Input@13, (Number * 16) + 11, Sign);
+
+ [<<"c"/utf8>> | Input@14] ->
+ parse_hex(Input@14, (Number * 16) + 12, Sign);
+
+ [<<"d"/utf8>> | Input@15] ->
+ parse_hex(Input@15, (Number * 16) + 13, Sign);
+
+ [<<"e"/utf8>> | Input@16] ->
+ parse_hex(Input@16, (Number * 16) + 14, Sign);
+
+ [<<"f"/utf8>> | Input@17] ->
+ parse_hex(Input@17, (Number * 16) + 15, Sign);
+
+ [<<"A"/utf8>> | Input@18] ->
+ parse_hex(Input@18, (Number * 16) + 10, Sign);
+
+ [<<"B"/utf8>> | Input@19] ->
+ parse_hex(Input@19, (Number * 16) + 11, Sign);
+
+ [<<"C"/utf8>> | Input@20] ->
+ parse_hex(Input@20, (Number * 16) + 12, Sign);
+
+ [<<"D"/utf8>> | Input@21] ->
+ parse_hex(Input@21, (Number * 16) + 13, Sign);
+
+ [<<"E"/utf8>> | Input@22] ->
+ parse_hex(Input@22, (Number * 16) + 14, Sign);
+
+ [<<"F"/utf8>> | Input@23] ->
+ parse_hex(Input@23, (Number * 16) + 15, Sign);
+
+ Input@24 ->
+ Number@1 = case Sign of
+ positive ->
+ Number;
+
+ negative ->
+ - Number
+ end,
+ {ok, {{int, Number@1}, Input@24}}
+ end.
+
+-spec parse_octal(list(binary()), integer(), sign()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_octal(Input, Number, Sign) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_octal(Input@1, Number, Sign);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_octal(Input@2, (Number * 8) + 0, Sign);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_octal(Input@3, (Number * 8) + 1, Sign);
+
+ [<<"2"/utf8>> | Input@4] ->
+ parse_octal(Input@4, (Number * 8) + 2, Sign);
+
+ [<<"3"/utf8>> | Input@5] ->
+ parse_octal(Input@5, (Number * 8) + 3, Sign);
+
+ [<<"4"/utf8>> | Input@6] ->
+ parse_octal(Input@6, (Number * 8) + 4, Sign);
+
+ [<<"5"/utf8>> | Input@7] ->
+ parse_octal(Input@7, (Number * 8) + 5, Sign);
+
+ [<<"6"/utf8>> | Input@8] ->
+ parse_octal(Input@8, (Number * 8) + 6, Sign);
+
+ [<<"7"/utf8>> | Input@9] ->
+ parse_octal(Input@9, (Number * 8) + 7, Sign);
+
+ Input@10 ->
+ Number@1 = case Sign of
+ positive ->
+ Number;
+
+ negative ->
+ - Number
+ end,
+ {ok, {{int, Number@1}, Input@10}}
+ end.
+
+-spec parse_binary(list(binary()), integer(), sign()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_binary(Input, Number, Sign) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_binary(Input@1, Number, Sign);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_binary(Input@2, (Number * 2) + 0, Sign);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_binary(Input@3, (Number * 2) + 1, Sign);
+
+ Input@4 ->
+ Number@1 = case Sign of
+ positive ->
+ Number;
+
+ negative ->
+ - Number
+ end,
+ {ok, {{int, Number@1}, Input@4}}
+ end.
+
+-spec parse_exponent(list(binary()), float(), sign(), integer(), sign()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_exponent(Input, N, N_sign, Ex, Ex_sign) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_exponent(Input@1, N, N_sign, Ex, Ex_sign);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_exponent(Input@2, N, N_sign, Ex * 10, Ex_sign);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_exponent(Input@3, N, N_sign, (Ex * 10) + 1, Ex_sign);
+
+ [<<"2"/utf8>> | Input@4] ->
+ parse_exponent(Input@4, N, N_sign, (Ex * 10) + 2, Ex_sign);
+
+ [<<"3"/utf8>> | Input@5] ->
+ parse_exponent(Input@5, N, N_sign, (Ex * 10) + 3, Ex_sign);
+
+ [<<"4"/utf8>> | Input@6] ->
+ parse_exponent(Input@6, N, N_sign, (Ex * 10) + 4, Ex_sign);
+
+ [<<"5"/utf8>> | Input@7] ->
+ parse_exponent(Input@7, N, N_sign, (Ex * 10) + 5, Ex_sign);
+
+ [<<"6"/utf8>> | Input@8] ->
+ parse_exponent(Input@8, N, N_sign, (Ex * 10) + 6, Ex_sign);
+
+ [<<"7"/utf8>> | Input@9] ->
+ parse_exponent(Input@9, N, N_sign, (Ex * 10) + 7, Ex_sign);
+
+ [<<"8"/utf8>> | Input@10] ->
+ parse_exponent(Input@10, N, N_sign, (Ex * 10) + 8, Ex_sign);
+
+ [<<"9"/utf8>> | Input@11] ->
+ parse_exponent(Input@11, N, N_sign, (Ex * 10) + 9, Ex_sign);
+
+ Input@12 ->
+ Number = case N_sign of
+ positive ->
+ N;
+
+ negative ->
+ N * -1.0
+ end,
+ Exponent = gleam@int:to_float(case Ex_sign of
+ positive ->
+ Ex;
+
+ negative ->
+ - Ex
+ end),
+ Multiplier@1 = case gleam@float:power(10.0, Exponent) of
+ {ok, Multiplier} ->
+ Multiplier;
+
+ {error, _} ->
+ 1.0
+ end,
+ {ok, {{float, Number * Multiplier@1}, Input@12}}
+ end.
+
+-spec parse_float(list(binary()), float(), sign(), float()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_float(Input, Number, Sign, Unit) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_float(Input@1, Number, Sign, Unit);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_float(Input@2, Number, Sign, Unit * 0.1);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_float(Input@3, Number + (1.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"2"/utf8>> | Input@4] ->
+ parse_float(Input@4, Number + (2.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"3"/utf8>> | Input@5] ->
+ parse_float(Input@5, Number + (3.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"4"/utf8>> | Input@6] ->
+ parse_float(Input@6, Number + (4.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"5"/utf8>> | Input@7] ->
+ parse_float(Input@7, Number + (5.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"6"/utf8>> | Input@8] ->
+ parse_float(Input@8, Number + (6.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"7"/utf8>> | Input@9] ->
+ parse_float(Input@9, Number + (7.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"8"/utf8>> | Input@10] ->
+ parse_float(Input@10, Number + (8.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"9"/utf8>> | Input@11] ->
+ parse_float(Input@11, Number + (9.0 * Unit), Sign, Unit * 0.1);
+
+ [<<"e"/utf8>>, <<"+"/utf8>> | Input@12] ->
+ parse_exponent(Input@12, Number, Sign, 0, positive);
+
+ [<<"e"/utf8>>, <<"-"/utf8>> | Input@13] ->
+ parse_exponent(Input@13, Number, Sign, 0, negative);
+
+ [<<"e"/utf8>> | Input@14] ->
+ parse_exponent(Input@14, Number, Sign, 0, positive);
+
+ [<<"E"/utf8>>, <<"+"/utf8>> | Input@15] ->
+ parse_exponent(Input@15, Number, Sign, 0, positive);
+
+ [<<"E"/utf8>>, <<"-"/utf8>> | Input@16] ->
+ parse_exponent(Input@16, Number, Sign, 0, negative);
+
+ [<<"E"/utf8>> | Input@17] ->
+ parse_exponent(Input@17, Number, Sign, 0, positive);
+
+ Input@18 ->
+ Number@1 = case Sign of
+ positive ->
+ Number;
+
+ negative ->
+ Number * -1.0
+ end,
+ {ok, {{float, Number@1}, Input@18}}
+ end.
+
+-spec parse_string(list(binary()), binary()) -> {ok, {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_string(Input, String) ->
+ case Input of
+ [<<"\""/utf8>> | Input@1] ->
+ {ok, {{string, String}, Input@1}};
+
+ [<<"\\"/utf8>>, <<"t"/utf8>> | Input@2] ->
+ parse_string(Input@2, <<String/binary, "\t"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"n"/utf8>> | Input@3] ->
+ parse_string(Input@3, <<String/binary, "\n"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"r"/utf8>> | Input@4] ->
+ parse_string(Input@4, <<String/binary, "\r"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"\""/utf8>> | Input@5] ->
+ parse_string(Input@5, <<String/binary, "\""/utf8>>);
+
+ [<<"\\"/utf8>>, <<"\\"/utf8>> | Input@6] ->
+ parse_string(Input@6, <<String/binary, "\\"/utf8>>);
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}};
+
+ [<<"\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\n"/utf8>>, <<"\""/utf8>>}};
+
+ [<<"\r\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\r\n"/utf8>>, <<"\""/utf8>>}};
+
+ [G | Input@7] ->
+ parse_string(Input@7, <<String/binary, G/binary>>)
+ end.
+
+-spec parse_multi_line_string(list(binary()), binary()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_multi_line_string(Input, String) ->
+ case Input of
+ [<<"\""/utf8>>, <<"\""/utf8>>, <<"\""/utf8>> | Input@1] ->
+ {ok, {{string, String}, Input@1}};
+
+ [<<"\\"/utf8>>, <<"\n"/utf8>> | Input@2] ->
+ parse_multi_line_string(skip_whitespace(Input@2), String);
+
+ [<<"\\"/utf8>>, <<"\r\n"/utf8>> | Input@3] ->
+ parse_multi_line_string(skip_whitespace(Input@3), String);
+
+ [<<"\r\n"/utf8>> | Input@4] when String =:= <<""/utf8>> ->
+ parse_multi_line_string(Input@4, String);
+
+ [<<"\n"/utf8>> | Input@5] when String =:= <<""/utf8>> ->
+ parse_multi_line_string(Input@5, String);
+
+ [<<"\r\n"/utf8>> | Input@6] when String =:= <<""/utf8>> ->
+ parse_multi_line_string(Input@6, String);
+
+ [<<"\\"/utf8>>, <<"t"/utf8>> | Input@7] ->
+ parse_multi_line_string(Input@7, <<String/binary, "\t"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"n"/utf8>> | Input@8] ->
+ parse_multi_line_string(Input@8, <<String/binary, "\n"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"r"/utf8>> | Input@9] ->
+ parse_multi_line_string(Input@9, <<String/binary, "\r"/utf8>>);
+
+ [<<"\\"/utf8>>, <<"\""/utf8>> | Input@10] ->
+ parse_multi_line_string(Input@10, <<String/binary, "\""/utf8>>);
+
+ [<<"\\"/utf8>>, <<"\\"/utf8>> | Input@11] ->
+ parse_multi_line_string(Input@11, <<String/binary, "\\"/utf8>>);
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}};
+
+ [G | Input@12] ->
+ parse_multi_line_string(Input@12, <<String/binary, G/binary>>)
+ end.
+
+-spec parse_multi_line_literal_string(list(binary()), binary()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_multi_line_literal_string(Input, String) ->
+ case Input of
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}};
+
+ [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | _] ->
+ {error, {unexpected, <<"''''"/utf8>>, <<"'''"/utf8>>}};
+
+ [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | Input@1] ->
+ {ok, {{string, String}, Input@1}};
+
+ [<<"\n"/utf8>> | Input@2] when String =:= <<""/utf8>> ->
+ parse_multi_line_literal_string(Input@2, String);
+
+ [<<"\r\n"/utf8>> | Input@3] when String =:= <<""/utf8>> ->
+ parse_multi_line_literal_string(Input@3, String);
+
+ [G | Input@4] ->
+ parse_multi_line_literal_string(
+ Input@4,
+ <<String/binary, G/binary>>
+ )
+ end.
+
+-spec parse_literal_string(list(binary()), binary()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_literal_string(Input, String) ->
+ case Input of
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"\""/utf8>>}};
+
+ [<<"\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\n"/utf8>>, <<"'"/utf8>>}};
+
+ [<<"\r\n"/utf8>> | _] ->
+ {error, {unexpected, <<"\r\n"/utf8>>, <<"'"/utf8>>}};
+
+ [<<"'"/utf8>> | Input@1] ->
+ {ok, {{string, String}, Input@1}};
+
+ [G | Input@2] ->
+ parse_literal_string(Input@2, <<String/binary, G/binary>>)
+ end.
+
+-spec parse_time_ms(list(binary()), integer(), integer()) -> {ok,
+ {{integer(), integer()}, list(binary())}} |
+ {error, parse_error()}.
+parse_time_ms(Input, Seconds, Ms) ->
+ case Input of
+ [<<"0"/utf8>> | Input@1] when Ms < 100000 ->
+ parse_time_ms(Input@1, Seconds, (Ms * 10) + 0);
+
+ [<<"1"/utf8>> | Input@2] when Ms < 100000 ->
+ parse_time_ms(Input@2, Seconds, (Ms * 10) + 1);
+
+ [<<"2"/utf8>> | Input@3] when Ms < 100000 ->
+ parse_time_ms(Input@3, Seconds, (Ms * 10) + 2);
+
+ [<<"3"/utf8>> | Input@4] when Ms < 100000 ->
+ parse_time_ms(Input@4, Seconds, (Ms * 10) + 3);
+
+ [<<"4"/utf8>> | Input@5] when Ms < 100000 ->
+ parse_time_ms(Input@5, Seconds, (Ms * 10) + 4);
+
+ [<<"5"/utf8>> | Input@6] when Ms < 100000 ->
+ parse_time_ms(Input@6, Seconds, (Ms * 10) + 5);
+
+ [<<"6"/utf8>> | Input@7] when Ms < 100000 ->
+ parse_time_ms(Input@7, Seconds, (Ms * 10) + 6);
+
+ [<<"7"/utf8>> | Input@8] when Ms < 100000 ->
+ parse_time_ms(Input@8, Seconds, (Ms * 10) + 7);
+
+ [<<"8"/utf8>> | Input@9] when Ms < 100000 ->
+ parse_time_ms(Input@9, Seconds, (Ms * 10) + 8);
+
+ [<<"9"/utf8>> | Input@10] when Ms < 100000 ->
+ parse_time_ms(Input@10, Seconds, (Ms * 10) + 9);
+
+ _ ->
+ {ok, {{Seconds, Ms}, Input}}
+ end.
+
+-spec parse_number_under_60(list(binary()), binary()) -> {ok,
+ {integer(), list(binary())}} |
+ {error, parse_error()}.
+parse_number_under_60(Input, Expected) ->
+ case Input of
+ [<<"0"/utf8>>, <<"0"/utf8>> | Input@1] ->
+ {ok, {0, Input@1}};
+
+ [<<"0"/utf8>>, <<"1"/utf8>> | Input@2] ->
+ {ok, {1, Input@2}};
+
+ [<<"0"/utf8>>, <<"2"/utf8>> | Input@3] ->
+ {ok, {2, Input@3}};
+
+ [<<"0"/utf8>>, <<"3"/utf8>> | Input@4] ->
+ {ok, {3, Input@4}};
+
+ [<<"0"/utf8>>, <<"4"/utf8>> | Input@5] ->
+ {ok, {4, Input@5}};
+
+ [<<"0"/utf8>>, <<"5"/utf8>> | Input@6] ->
+ {ok, {5, Input@6}};
+
+ [<<"0"/utf8>>, <<"6"/utf8>> | Input@7] ->
+ {ok, {6, Input@7}};
+
+ [<<"0"/utf8>>, <<"7"/utf8>> | Input@8] ->
+ {ok, {7, Input@8}};
+
+ [<<"0"/utf8>>, <<"8"/utf8>> | Input@9] ->
+ {ok, {8, Input@9}};
+
+ [<<"0"/utf8>>, <<"9"/utf8>> | Input@10] ->
+ {ok, {9, Input@10}};
+
+ [<<"1"/utf8>>, <<"0"/utf8>> | Input@11] ->
+ {ok, {10, Input@11}};
+
+ [<<"1"/utf8>>, <<"1"/utf8>> | Input@12] ->
+ {ok, {11, Input@12}};
+
+ [<<"1"/utf8>>, <<"2"/utf8>> | Input@13] ->
+ {ok, {12, Input@13}};
+
+ [<<"1"/utf8>>, <<"3"/utf8>> | Input@14] ->
+ {ok, {13, Input@14}};
+
+ [<<"1"/utf8>>, <<"4"/utf8>> | Input@15] ->
+ {ok, {14, Input@15}};
+
+ [<<"1"/utf8>>, <<"5"/utf8>> | Input@16] ->
+ {ok, {15, Input@16}};
+
+ [<<"1"/utf8>>, <<"6"/utf8>> | Input@17] ->
+ {ok, {16, Input@17}};
+
+ [<<"1"/utf8>>, <<"7"/utf8>> | Input@18] ->
+ {ok, {17, Input@18}};
+
+ [<<"1"/utf8>>, <<"8"/utf8>> | Input@19] ->
+ {ok, {18, Input@19}};
+
+ [<<"1"/utf8>>, <<"9"/utf8>> | Input@20] ->
+ {ok, {19, Input@20}};
+
+ [<<"2"/utf8>>, <<"0"/utf8>> | Input@21] ->
+ {ok, {20, Input@21}};
+
+ [<<"2"/utf8>>, <<"1"/utf8>> | Input@22] ->
+ {ok, {21, Input@22}};
+
+ [<<"2"/utf8>>, <<"2"/utf8>> | Input@23] ->
+ {ok, {22, Input@23}};
+
+ [<<"2"/utf8>>, <<"3"/utf8>> | Input@24] ->
+ {ok, {23, Input@24}};
+
+ [<<"2"/utf8>>, <<"4"/utf8>> | Input@25] ->
+ {ok, {24, Input@25}};
+
+ [<<"2"/utf8>>, <<"5"/utf8>> | Input@26] ->
+ {ok, {25, Input@26}};
+
+ [<<"2"/utf8>>, <<"6"/utf8>> | Input@27] ->
+ {ok, {26, Input@27}};
+
+ [<<"2"/utf8>>, <<"7"/utf8>> | Input@28] ->
+ {ok, {27, Input@28}};
+
+ [<<"2"/utf8>>, <<"8"/utf8>> | Input@29] ->
+ {ok, {28, Input@29}};
+
+ [<<"2"/utf8>>, <<"9"/utf8>> | Input@30] ->
+ {ok, {29, Input@30}};
+
+ [<<"3"/utf8>>, <<"0"/utf8>> | Input@31] ->
+ {ok, {30, Input@31}};
+
+ [<<"3"/utf8>>, <<"1"/utf8>> | Input@32] ->
+ {ok, {31, Input@32}};
+
+ [<<"3"/utf8>>, <<"2"/utf8>> | Input@33] ->
+ {ok, {32, Input@33}};
+
+ [<<"3"/utf8>>, <<"3"/utf8>> | Input@34] ->
+ {ok, {33, Input@34}};
+
+ [<<"3"/utf8>>, <<"4"/utf8>> | Input@35] ->
+ {ok, {34, Input@35}};
+
+ [<<"3"/utf8>>, <<"5"/utf8>> | Input@36] ->
+ {ok, {35, Input@36}};
+
+ [<<"3"/utf8>>, <<"6"/utf8>> | Input@37] ->
+ {ok, {36, Input@37}};
+
+ [<<"3"/utf8>>, <<"7"/utf8>> | Input@38] ->
+ {ok, {37, Input@38}};
+
+ [<<"3"/utf8>>, <<"8"/utf8>> | Input@39] ->
+ {ok, {38, Input@39}};
+
+ [<<"3"/utf8>>, <<"9"/utf8>> | Input@40] ->
+ {ok, {39, Input@40}};
+
+ [<<"4"/utf8>>, <<"0"/utf8>> | Input@41] ->
+ {ok, {40, Input@41}};
+
+ [<<"4"/utf8>>, <<"1"/utf8>> | Input@42] ->
+ {ok, {41, Input@42}};
+
+ [<<"4"/utf8>>, <<"2"/utf8>> | Input@43] ->
+ {ok, {42, Input@43}};
+
+ [<<"4"/utf8>>, <<"3"/utf8>> | Input@44] ->
+ {ok, {43, Input@44}};
+
+ [<<"4"/utf8>>, <<"4"/utf8>> | Input@45] ->
+ {ok, {44, Input@45}};
+
+ [<<"4"/utf8>>, <<"5"/utf8>> | Input@46] ->
+ {ok, {45, Input@46}};
+
+ [<<"4"/utf8>>, <<"6"/utf8>> | Input@47] ->
+ {ok, {46, Input@47}};
+
+ [<<"4"/utf8>>, <<"7"/utf8>> | Input@48] ->
+ {ok, {47, Input@48}};
+
+ [<<"4"/utf8>>, <<"8"/utf8>> | Input@49] ->
+ {ok, {48, Input@49}};
+
+ [<<"4"/utf8>>, <<"9"/utf8>> | Input@50] ->
+ {ok, {49, Input@50}};
+
+ [<<"5"/utf8>>, <<"0"/utf8>> | Input@51] ->
+ {ok, {50, Input@51}};
+
+ [<<"5"/utf8>>, <<"1"/utf8>> | Input@52] ->
+ {ok, {51, Input@52}};
+
+ [<<"5"/utf8>>, <<"2"/utf8>> | Input@53] ->
+ {ok, {52, Input@53}};
+
+ [<<"5"/utf8>>, <<"3"/utf8>> | Input@54] ->
+ {ok, {53, Input@54}};
+
+ [<<"5"/utf8>>, <<"4"/utf8>> | Input@55] ->
+ {ok, {54, Input@55}};
+
+ [<<"5"/utf8>>, <<"5"/utf8>> | Input@56] ->
+ {ok, {55, Input@56}};
+
+ [<<"5"/utf8>>, <<"6"/utf8>> | Input@57] ->
+ {ok, {56, Input@57}};
+
+ [<<"5"/utf8>>, <<"7"/utf8>> | Input@58] ->
+ {ok, {57, Input@58}};
+
+ [<<"5"/utf8>>, <<"8"/utf8>> | Input@59] ->
+ {ok, {58, Input@59}};
+
+ [<<"5"/utf8>>, <<"9"/utf8>> | Input@60] ->
+ {ok, {59, Input@60}};
+
+ [G | _] ->
+ {error, {unexpected, G, Expected}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, Expected}}
+ end.
+
+-spec parse_hour_minute(list(binary())) -> {ok,
+ {{integer(), integer()}, list(binary())}} |
+ {error, parse_error()}.
+parse_hour_minute(Input) ->
+ do(case Input of
+ [<<"0"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@1] ->
+ {ok, {0, Input@1}};
+
+ [<<"0"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@2] ->
+ {ok, {1, Input@2}};
+
+ [<<"0"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@3] ->
+ {ok, {2, Input@3}};
+
+ [<<"0"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@4] ->
+ {ok, {3, Input@4}};
+
+ [<<"0"/utf8>>, <<"4"/utf8>>, <<":"/utf8>> | Input@5] ->
+ {ok, {4, Input@5}};
+
+ [<<"0"/utf8>>, <<"5"/utf8>>, <<":"/utf8>> | Input@6] ->
+ {ok, {5, Input@6}};
+
+ [<<"0"/utf8>>, <<"6"/utf8>>, <<":"/utf8>> | Input@7] ->
+ {ok, {6, Input@7}};
+
+ [<<"0"/utf8>>, <<"7"/utf8>>, <<":"/utf8>> | Input@8] ->
+ {ok, {7, Input@8}};
+
+ [<<"0"/utf8>>, <<"8"/utf8>>, <<":"/utf8>> | Input@9] ->
+ {ok, {8, Input@9}};
+
+ [<<"0"/utf8>>, <<"9"/utf8>>, <<":"/utf8>> | Input@10] ->
+ {ok, {9, Input@10}};
+
+ [<<"1"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@11] ->
+ {ok, {10, Input@11}};
+
+ [<<"1"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@12] ->
+ {ok, {11, Input@12}};
+
+ [<<"1"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@13] ->
+ {ok, {12, Input@13}};
+
+ [<<"1"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@14] ->
+ {ok, {13, Input@14}};
+
+ [<<"1"/utf8>>, <<"4"/utf8>>, <<":"/utf8>> | Input@15] ->
+ {ok, {14, Input@15}};
+
+ [<<"1"/utf8>>, <<"5"/utf8>>, <<":"/utf8>> | Input@16] ->
+ {ok, {15, Input@16}};
+
+ [<<"1"/utf8>>, <<"6"/utf8>>, <<":"/utf8>> | Input@17] ->
+ {ok, {16, Input@17}};
+
+ [<<"1"/utf8>>, <<"7"/utf8>>, <<":"/utf8>> | Input@18] ->
+ {ok, {17, Input@18}};
+
+ [<<"1"/utf8>>, <<"8"/utf8>>, <<":"/utf8>> | Input@19] ->
+ {ok, {18, Input@19}};
+
+ [<<"1"/utf8>>, <<"9"/utf8>>, <<":"/utf8>> | Input@20] ->
+ {ok, {19, Input@20}};
+
+ [<<"2"/utf8>>, <<"0"/utf8>>, <<":"/utf8>> | Input@21] ->
+ {ok, {20, Input@21}};
+
+ [<<"2"/utf8>>, <<"1"/utf8>>, <<":"/utf8>> | Input@22] ->
+ {ok, {21, Input@22}};
+
+ [<<"2"/utf8>>, <<"2"/utf8>>, <<":"/utf8>> | Input@23] ->
+ {ok, {22, Input@23}};
+
+ [<<"2"/utf8>>, <<"3"/utf8>>, <<":"/utf8>> | Input@24] ->
+ {ok, {23, Input@24}};
+
+ [G | _] ->
+ {error, {unexpected, G, <<"time"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"time"/utf8>>}}
+ end, fun(Hours, Input@25) ->
+ do(
+ parse_number_under_60(Input@25, <<"minutes"/utf8>>),
+ fun(Minutes, Input@26) -> {ok, {{Hours, Minutes}, Input@26}} end
+ )
+ end).
+
+-spec parse_time_s_ms(list(binary())) -> {ok,
+ {{integer(), integer()}, list(binary())}} |
+ {error, parse_error()}.
+parse_time_s_ms(Input) ->
+ case Input of
+ [<<":"/utf8>> | Input@1] ->
+ do(
+ parse_number_under_60(Input@1, <<"seconds"/utf8>>),
+ fun(Seconds, Input@2) -> case Input@2 of
+ [<<"."/utf8>> | Input@3] ->
+ parse_time_ms(Input@3, Seconds, 0);
+
+ _ ->
+ {ok, {{Seconds, 0}, Input@2}}
+ end end
+ );
+
+ _ ->
+ {ok, {{0, 0}, Input}}
+ end.
+
+-spec parse_time_minute(list(binary()), integer()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_time_minute(Input, Hours) ->
+ do(
+ parse_number_under_60(Input, <<"minutes"/utf8>>),
+ fun(Minutes, Input@1) ->
+ do(
+ parse_time_s_ms(Input@1),
+ fun(_use0, Input@2) ->
+ {Seconds, Ms} = _use0,
+ Time = {time_value, Hours, Minutes, Seconds, Ms},
+ {ok, {{time, Time}, Input@2}}
+ end
+ )
+ end
+ ).
+
+-spec parse_time_value(list(binary())) -> {ok, {time(), list(binary())}} |
+ {error, parse_error()}.
+parse_time_value(Input) ->
+ do(
+ parse_hour_minute(Input),
+ fun(_use0, Input@1) ->
+ {Hours, Minutes} = _use0,
+ do(
+ parse_time_s_ms(Input@1),
+ fun(_use0@1, Input@2) ->
+ {Seconds, Ms} = _use0@1,
+ Time = {time_value, Hours, Minutes, Seconds, Ms},
+ {ok, {Time, Input@2}}
+ end
+ )
+ end
+ ).
+
+-spec parse_offset_hours(list(binary()), sign()) -> {ok,
+ {offset(), list(binary())}} |
+ {error, parse_error()}.
+parse_offset_hours(Input, Sign) ->
+ do(
+ parse_hour_minute(Input),
+ fun(_use0, Input@1) ->
+ {Hours, Minutes} = _use0,
+ {ok, {{offset, Sign, Hours, Minutes}, Input@1}}
+ end
+ ).
+
+-spec parse_offset(list(binary())) -> {ok, {offset(), list(binary())}} |
+ {error, parse_error()}.
+parse_offset(Input) ->
+ case Input of
+ [<<"Z"/utf8>> | Input@1] ->
+ {ok, {{offset, positive, 0, 0}, Input@1}};
+
+ [<<"+"/utf8>> | Input@2] ->
+ parse_offset_hours(Input@2, positive);
+
+ [<<"-"/utf8>> | Input@3] ->
+ parse_offset_hours(Input@3, negative);
+
+ _ ->
+ {ok, {local, Input}}
+ end.
+
+-spec parse_date_end(list(binary()), integer(), integer(), integer()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_date_end(Input, Year, Month, Day) ->
+ Date = {date_value, Year, Month, Day},
+ case Input of
+ [<<" "/utf8>> | Input@1] ->
+ do(
+ parse_time_value(Input@1),
+ fun(Time, Input@2) ->
+ do(
+ parse_offset(Input@2),
+ fun(Offset, Input@3) ->
+ {ok,
+ {{date_time,
+ {date_time_value, Date, Time, Offset}},
+ Input@3}}
+ end
+ )
+ end
+ );
+
+ [<<"T"/utf8>> | Input@1] ->
+ do(
+ parse_time_value(Input@1),
+ fun(Time, Input@2) ->
+ do(
+ parse_offset(Input@2),
+ fun(Offset, Input@3) ->
+ {ok,
+ {{date_time,
+ {date_time_value, Date, Time, Offset}},
+ Input@3}}
+ end
+ )
+ end
+ );
+
+ _ ->
+ {ok, {{date, Date}, Input}}
+ end.
+
+-spec parse_date_day(list(binary()), integer(), integer()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_date_day(Input, Year, Month) ->
+ case Input of
+ [<<"0"/utf8>>, <<"1"/utf8>> | Input@1] ->
+ parse_date_end(Input@1, Year, Month, 1);
+
+ [<<"0"/utf8>>, <<"2"/utf8>> | Input@2] ->
+ parse_date_end(Input@2, Year, Month, 2);
+
+ [<<"0"/utf8>>, <<"3"/utf8>> | Input@3] ->
+ parse_date_end(Input@3, Year, Month, 3);
+
+ [<<"0"/utf8>>, <<"4"/utf8>> | Input@4] ->
+ parse_date_end(Input@4, Year, Month, 4);
+
+ [<<"0"/utf8>>, <<"5"/utf8>> | Input@5] ->
+ parse_date_end(Input@5, Year, Month, 5);
+
+ [<<"0"/utf8>>, <<"6"/utf8>> | Input@6] ->
+ parse_date_end(Input@6, Year, Month, 6);
+
+ [<<"0"/utf8>>, <<"7"/utf8>> | Input@7] ->
+ parse_date_end(Input@7, Year, Month, 7);
+
+ [<<"0"/utf8>>, <<"8"/utf8>> | Input@8] ->
+ parse_date_end(Input@8, Year, Month, 8);
+
+ [<<"0"/utf8>>, <<"9"/utf8>> | Input@9] ->
+ parse_date_end(Input@9, Year, Month, 9);
+
+ [<<"1"/utf8>>, <<"0"/utf8>> | Input@10] ->
+ parse_date_end(Input@10, Year, Month, 10);
+
+ [<<"1"/utf8>>, <<"1"/utf8>> | Input@11] ->
+ parse_date_end(Input@11, Year, Month, 11);
+
+ [<<"1"/utf8>>, <<"2"/utf8>> | Input@12] ->
+ parse_date_end(Input@12, Year, Month, 12);
+
+ [<<"1"/utf8>>, <<"3"/utf8>> | Input@13] ->
+ parse_date_end(Input@13, Year, Month, 13);
+
+ [<<"1"/utf8>>, <<"4"/utf8>> | Input@14] ->
+ parse_date_end(Input@14, Year, Month, 14);
+
+ [<<"1"/utf8>>, <<"5"/utf8>> | Input@15] ->
+ parse_date_end(Input@15, Year, Month, 15);
+
+ [<<"1"/utf8>>, <<"6"/utf8>> | Input@16] ->
+ parse_date_end(Input@16, Year, Month, 16);
+
+ [<<"1"/utf8>>, <<"7"/utf8>> | Input@17] ->
+ parse_date_end(Input@17, Year, Month, 17);
+
+ [<<"1"/utf8>>, <<"8"/utf8>> | Input@18] ->
+ parse_date_end(Input@18, Year, Month, 18);
+
+ [<<"1"/utf8>>, <<"9"/utf8>> | Input@19] ->
+ parse_date_end(Input@19, Year, Month, 19);
+
+ [<<"2"/utf8>>, <<"0"/utf8>> | Input@20] ->
+ parse_date_end(Input@20, Year, Month, 20);
+
+ [<<"2"/utf8>>, <<"1"/utf8>> | Input@21] ->
+ parse_date_end(Input@21, Year, Month, 21);
+
+ [<<"2"/utf8>>, <<"2"/utf8>> | Input@22] ->
+ parse_date_end(Input@22, Year, Month, 22);
+
+ [<<"2"/utf8>>, <<"3"/utf8>> | Input@23] ->
+ parse_date_end(Input@23, Year, Month, 23);
+
+ [<<"2"/utf8>>, <<"4"/utf8>> | Input@24] ->
+ parse_date_end(Input@24, Year, Month, 24);
+
+ [<<"2"/utf8>>, <<"5"/utf8>> | Input@25] ->
+ parse_date_end(Input@25, Year, Month, 25);
+
+ [<<"2"/utf8>>, <<"6"/utf8>> | Input@26] ->
+ parse_date_end(Input@26, Year, Month, 26);
+
+ [<<"2"/utf8>>, <<"7"/utf8>> | Input@27] ->
+ parse_date_end(Input@27, Year, Month, 27);
+
+ [<<"2"/utf8>>, <<"8"/utf8>> | Input@28] ->
+ parse_date_end(Input@28, Year, Month, 28);
+
+ [<<"2"/utf8>>, <<"9"/utf8>> | Input@29] ->
+ parse_date_end(Input@29, Year, Month, 29);
+
+ [<<"3"/utf8>>, <<"0"/utf8>> | Input@30] ->
+ parse_date_end(Input@30, Year, Month, 30);
+
+ [<<"3"/utf8>>, <<"1"/utf8>> | Input@31] ->
+ parse_date_end(Input@31, Year, Month, 31);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"date day"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"date day"/utf8>>}}
+ end.
+
+-spec parse_date(list(binary()), integer()) -> {ok, {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_date(Input, Year) ->
+ case Input of
+ [<<"0"/utf8>>, <<"1"/utf8>>, <<"-"/utf8>> | Input@1] ->
+ parse_date_day(Input@1, Year, 1);
+
+ [<<"0"/utf8>>, <<"2"/utf8>>, <<"-"/utf8>> | Input@2] ->
+ parse_date_day(Input@2, Year, 2);
+
+ [<<"0"/utf8>>, <<"3"/utf8>>, <<"-"/utf8>> | Input@3] ->
+ parse_date_day(Input@3, Year, 3);
+
+ [<<"0"/utf8>>, <<"4"/utf8>>, <<"-"/utf8>> | Input@4] ->
+ parse_date_day(Input@4, Year, 4);
+
+ [<<"0"/utf8>>, <<"5"/utf8>>, <<"-"/utf8>> | Input@5] ->
+ parse_date_day(Input@5, Year, 5);
+
+ [<<"0"/utf8>>, <<"6"/utf8>>, <<"-"/utf8>> | Input@6] ->
+ parse_date_day(Input@6, Year, 6);
+
+ [<<"0"/utf8>>, <<"7"/utf8>>, <<"-"/utf8>> | Input@7] ->
+ parse_date_day(Input@7, Year, 7);
+
+ [<<"0"/utf8>>, <<"8"/utf8>>, <<"-"/utf8>> | Input@8] ->
+ parse_date_day(Input@8, Year, 8);
+
+ [<<"0"/utf8>>, <<"9"/utf8>>, <<"-"/utf8>> | Input@9] ->
+ parse_date_day(Input@9, Year, 9);
+
+ [<<"1"/utf8>>, <<"0"/utf8>>, <<"-"/utf8>> | Input@10] ->
+ parse_date_day(Input@10, Year, 10);
+
+ [<<"1"/utf8>>, <<"1"/utf8>>, <<"-"/utf8>> | Input@11] ->
+ parse_date_day(Input@11, Year, 11);
+
+ [<<"1"/utf8>>, <<"2"/utf8>>, <<"-"/utf8>> | Input@12] ->
+ parse_date_day(Input@12, Year, 12);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"date month"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"date month"/utf8>>}}
+ end.
+
+-spec parse_number(list(binary()), integer(), sign()) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_number(Input, Number, Sign) ->
+ case Input of
+ [<<"_"/utf8>> | Input@1] ->
+ parse_number(Input@1, Number, Sign);
+
+ [<<"0"/utf8>> | Input@2] ->
+ parse_number(Input@2, (Number * 10) + 0, Sign);
+
+ [<<"1"/utf8>> | Input@3] ->
+ parse_number(Input@3, (Number * 10) + 1, Sign);
+
+ [<<"2"/utf8>> | Input@4] ->
+ parse_number(Input@4, (Number * 10) + 2, Sign);
+
+ [<<"3"/utf8>> | Input@5] ->
+ parse_number(Input@5, (Number * 10) + 3, Sign);
+
+ [<<"4"/utf8>> | Input@6] ->
+ parse_number(Input@6, (Number * 10) + 4, Sign);
+
+ [<<"5"/utf8>> | Input@7] ->
+ parse_number(Input@7, (Number * 10) + 5, Sign);
+
+ [<<"6"/utf8>> | Input@8] ->
+ parse_number(Input@8, (Number * 10) + 6, Sign);
+
+ [<<"7"/utf8>> | Input@9] ->
+ parse_number(Input@9, (Number * 10) + 7, Sign);
+
+ [<<"8"/utf8>> | Input@10] ->
+ parse_number(Input@10, (Number * 10) + 8, Sign);
+
+ [<<"9"/utf8>> | Input@11] ->
+ parse_number(Input@11, (Number * 10) + 9, Sign);
+
+ [<<"-"/utf8>> | Input@12] ->
+ parse_date(Input@12, Number);
+
+ [<<":"/utf8>> | Input@13] when Number < 24 ->
+ parse_time_minute(Input@13, Number);
+
+ [<<"."/utf8>> | Input@14] ->
+ parse_float(Input@14, gleam@int:to_float(Number), Sign, 0.1);
+
+ [<<"e"/utf8>>, <<"+"/utf8>> | Input@15] ->
+ parse_exponent(
+ Input@15,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ positive
+ );
+
+ [<<"e"/utf8>>, <<"-"/utf8>> | Input@16] ->
+ parse_exponent(
+ Input@16,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ negative
+ );
+
+ [<<"e"/utf8>> | Input@17] ->
+ parse_exponent(
+ Input@17,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ positive
+ );
+
+ [<<"E"/utf8>>, <<"+"/utf8>> | Input@18] ->
+ parse_exponent(
+ Input@18,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ positive
+ );
+
+ [<<"E"/utf8>>, <<"-"/utf8>> | Input@19] ->
+ parse_exponent(
+ Input@19,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ negative
+ );
+
+ [<<"E"/utf8>> | Input@20] ->
+ parse_exponent(
+ Input@20,
+ gleam@int:to_float(Number),
+ Sign,
+ 0,
+ positive
+ );
+
+ Input@21 ->
+ Number@1 = case Sign of
+ positive ->
+ Number;
+
+ negative ->
+ - Number
+ end,
+ {ok, {{int, Number@1}, Input@21}}
+ end.
+
+-spec reverse_arrays_of_tables(toml()) -> toml().
+reverse_arrays_of_tables(Toml) ->
+ case Toml of
+ {array_of_tables, Tables} ->
+ {array_of_tables, reverse_arrays_of_tables_array(Tables, [])};
+
+ {table, Table} ->
+ {table, reverse_arrays_of_tables_table(Table)};
+
+ _ ->
+ Toml
+ end.
+
+-spec reverse_arrays_of_tables_table(gleam@map:map_(binary(), toml())) -> gleam@map:map_(binary(), toml()).
+reverse_arrays_of_tables_table(Table) ->
+ gleam@map:map_values(Table, fun(_, V) -> reverse_arrays_of_tables(V) end).
+
+-spec reverse_arrays_of_tables_array(
+ list(gleam@map:map_(binary(), toml())),
+ list(gleam@map:map_(binary(), toml()))
+) -> list(gleam@map:map_(binary(), toml())).
+reverse_arrays_of_tables_array(Array, Acc) ->
+ case Array of
+ [] ->
+ Acc;
+
+ [First | Rest] ->
+ First@1 = reverse_arrays_of_tables_table(First),
+ reverse_arrays_of_tables_array(Rest, [First@1 | Acc])
+ end.
+
+-spec parse_inline_table_property(
+ list(binary()),
+ gleam@map:map_(binary(), toml())
+) -> {ok, {gleam@map:map_(binary(), toml()), list(binary())}} |
+ {error, parse_error()}.
+parse_inline_table_property(Input, Properties) ->
+ Input@1 = skip_whitespace(Input),
+ do(
+ parse_key(Input@1, []),
+ fun(Key, Input@2) ->
+ Input@3 = skip_line_whitespace(Input@2),
+ expect(
+ Input@3,
+ <<"="/utf8>>,
+ fun(Input@4) ->
+ Input@5 = skip_line_whitespace(Input@4),
+ do(
+ parse_value(Input@5),
+ fun(Value, Input@6) ->
+ case insert(Properties, Key, Value) of
+ {ok, Properties@1} ->
+ {ok, {Properties@1, Input@6}};
+
+ {error, E} ->
+ {error, E}
+ end
+ end
+ )
+ end
+ )
+ end
+ ).
+
+-spec parse_value(list(binary())) -> {ok, {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_value(Input) ->
+ case Input of
+ [<<"t"/utf8>>, <<"r"/utf8>>, <<"u"/utf8>>, <<"e"/utf8>> | Input@1] ->
+ {ok, {{bool, true}, Input@1}};
+
+ [<<"f"/utf8>>,
+ <<"a"/utf8>>,
+ <<"l"/utf8>>,
+ <<"s"/utf8>>,
+ <<"e"/utf8>> |
+ Input@2] ->
+ {ok, {{bool, false}, Input@2}};
+
+ [<<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@3] ->
+ {ok, {{nan, positive}, Input@3}};
+
+ [<<"+"/utf8>>, <<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@4] ->
+ {ok, {{nan, positive}, Input@4}};
+
+ [<<"-"/utf8>>, <<"n"/utf8>>, <<"a"/utf8>>, <<"n"/utf8>> | Input@5] ->
+ {ok, {{nan, negative}, Input@5}};
+
+ [<<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@6] ->
+ {ok, {{infinity, positive}, Input@6}};
+
+ [<<"+"/utf8>>, <<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@7] ->
+ {ok, {{infinity, positive}, Input@7}};
+
+ [<<"-"/utf8>>, <<"i"/utf8>>, <<"n"/utf8>>, <<"f"/utf8>> | Input@8] ->
+ {ok, {{infinity, negative}, Input@8}};
+
+ [<<"["/utf8>> | Input@9] ->
+ parse_array(Input@9, []);
+
+ [<<"{"/utf8>> | Input@10] ->
+ parse_inline_table(Input@10, gleam@map:new());
+
+ [<<"0"/utf8>>, <<"x"/utf8>> | Input@11] ->
+ parse_hex(Input@11, 0, positive);
+
+ [<<"+"/utf8>>, <<"0"/utf8>>, <<"x"/utf8>> | Input@12] ->
+ parse_hex(Input@12, 0, positive);
+
+ [<<"-"/utf8>>, <<"0"/utf8>>, <<"x"/utf8>> | Input@13] ->
+ parse_hex(Input@13, 0, negative);
+
+ [<<"0"/utf8>>, <<"o"/utf8>> | Input@14] ->
+ parse_octal(Input@14, 0, positive);
+
+ [<<"+"/utf8>>, <<"0"/utf8>>, <<"o"/utf8>> | Input@15] ->
+ parse_octal(Input@15, 0, positive);
+
+ [<<"-"/utf8>>, <<"0"/utf8>>, <<"o"/utf8>> | Input@16] ->
+ parse_octal(Input@16, 0, negative);
+
+ [<<"0"/utf8>>, <<"b"/utf8>> | Input@17] ->
+ parse_binary(Input@17, 0, positive);
+
+ [<<"+"/utf8>>, <<"0"/utf8>>, <<"b"/utf8>> | Input@18] ->
+ parse_binary(Input@18, 0, positive);
+
+ [<<"-"/utf8>>, <<"0"/utf8>>, <<"b"/utf8>> | Input@19] ->
+ parse_binary(Input@19, 0, negative);
+
+ [<<"+"/utf8>> | Input@20] ->
+ parse_number(Input@20, 0, positive);
+
+ [<<"-"/utf8>> | Input@21] ->
+ parse_number(Input@21, 0, negative);
+
+ [<<"0"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"1"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"2"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"3"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"4"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"5"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"6"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"7"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"8"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"9"/utf8>> | _] ->
+ parse_number(Input, 0, positive);
+
+ [<<"\""/utf8>>, <<"\""/utf8>>, <<"\""/utf8>> | Input@22] ->
+ parse_multi_line_string(Input@22, <<""/utf8>>);
+
+ [<<"\""/utf8>> | Input@23] ->
+ parse_string(Input@23, <<""/utf8>>);
+
+ [<<"'"/utf8>>, <<"'"/utf8>>, <<"'"/utf8>> | Input@24] ->
+ parse_multi_line_literal_string(Input@24, <<""/utf8>>);
+
+ [<<"'"/utf8>> | Input@25] ->
+ parse_literal_string(Input@25, <<""/utf8>>);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"value"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"value"/utf8>>}}
+ end.
+
+-spec parse_inline_table(list(binary()), gleam@map:map_(binary(), toml())) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_inline_table(Input, Properties) ->
+ Input@1 = skip_whitespace(Input),
+ case Input@1 of
+ [<<"}"/utf8>> | Input@2] ->
+ {ok, {{inline_table, Properties}, Input@2}};
+
+ _ ->
+ case parse_inline_table_property(Input@1, Properties) of
+ {ok, {Properties@1, Input@3}} ->
+ Input@4 = skip_whitespace(Input@3),
+ case Input@4 of
+ [<<"}"/utf8>> | Input@5] ->
+ {ok, {{inline_table, Properties@1}, Input@5}};
+
+ [<<","/utf8>> | Input@6] ->
+ Input@7 = skip_whitespace(Input@6),
+ parse_inline_table(Input@7, Properties@1);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"}"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"}"/utf8>>}}
+ end;
+
+ {error, E} ->
+ {error, E}
+ end
+ end.
+
+-spec parse_key_value(list(binary()), gleam@map:map_(binary(), toml())) -> {ok,
+ {gleam@map:map_(binary(), toml()), list(binary())}} |
+ {error, parse_error()}.
+parse_key_value(Input, Toml) ->
+ do(
+ parse_key(Input, []),
+ fun(Key, Input@1) ->
+ Input@2 = skip_line_whitespace(Input@1),
+ expect(
+ Input@2,
+ <<"="/utf8>>,
+ fun(Input@3) ->
+ Input@4 = skip_line_whitespace(Input@3),
+ do(
+ parse_value(Input@4),
+ fun(Value, Input@5) -> case insert(Toml, Key, Value) of
+ {ok, Toml@1} ->
+ {ok, {Toml@1, Input@5}};
+
+ {error, E} ->
+ {error, E}
+ end end
+ )
+ end
+ )
+ end
+ ).
+
+-spec parse_table(list(binary()), gleam@map:map_(binary(), toml())) -> {ok,
+ {gleam@map:map_(binary(), toml()), list(binary())}} |
+ {error, parse_error()}.
+parse_table(Input, Toml) ->
+ Input@1 = skip_whitespace(Input),
+ case Input@1 of
+ [<<"["/utf8>> | _] ->
+ {ok, {Toml, Input@1}};
+
+ [] ->
+ {ok, {Toml, Input@1}};
+
+ _ ->
+ case parse_key_value(Input@1, Toml) of
+ {ok, {Toml@1, Input@2}} ->
+ case skip_line_whitespace(Input@2) of
+ [] ->
+ {ok, {Toml@1, []}};
+
+ [<<"\n"/utf8>> | In] ->
+ parse_table(In, Toml@1);
+
+ [<<"\r\n"/utf8>> | In] ->
+ parse_table(In, Toml@1);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"\n"/utf8>>}}
+ end;
+
+ E ->
+ E
+ end
+ end.
+
+-spec parse_array_of_tables(list(binary())) -> {ok,
+ {{list(binary()), gleam@map:map_(binary(), toml())}, list(binary())}} |
+ {error, parse_error()}.
+parse_array_of_tables(Input) ->
+ Input@1 = skip_line_whitespace(Input),
+ do(
+ parse_key(Input@1, []),
+ fun(Key, Input@2) ->
+ expect(
+ Input@2,
+ <<"]"/utf8>>,
+ fun(Input@3) ->
+ expect(
+ Input@3,
+ <<"]"/utf8>>,
+ fun(Input@4) ->
+ do(
+ parse_table(Input@4, gleam@map:new()),
+ fun(Table, Input@5) ->
+ {ok, {{Key, Table}, Input@5}}
+ end
+ )
+ end
+ )
+ end
+ )
+ end
+ ).
+
+-spec parse_table_and_header(list(binary())) -> {ok,
+ {{list(binary()), gleam@map:map_(binary(), toml())}, list(binary())}} |
+ {error, parse_error()}.
+parse_table_and_header(Input) ->
+ do(
+ parse_table_header(Input),
+ fun(Key, Input@1) ->
+ do(
+ parse_table(Input@1, gleam@map:new()),
+ fun(Table, Input@2) -> {ok, {{Key, Table}, Input@2}} end
+ )
+ end
+ ).
+
+-spec parse_tables(list(binary()), gleam@map:map_(binary(), toml())) -> {ok,
+ gleam@map:map_(binary(), toml())} |
+ {error, parse_error()}.
+parse_tables(Input, Toml) ->
+ case Input of
+ [<<"["/utf8>>, <<"["/utf8>> | Input@1] ->
+ case parse_array_of_tables(Input@1) of
+ {error, E} ->
+ {error, E};
+
+ {ok, {{Key, Table}, Input@2}} ->
+ case insert(Toml, Key, {array_of_tables, [Table]}) of
+ {ok, Toml@1} ->
+ parse_tables(Input@2, Toml@1);
+
+ {error, E@1} ->
+ {error, E@1}
+ end
+ end;
+
+ [<<"["/utf8>> | Input@3] ->
+ case parse_table_and_header(Input@3) of
+ {error, E@2} ->
+ {error, E@2};
+
+ {ok, {{Key@1, Table@1}, Input@4}} ->
+ case insert(Toml, Key@1, {table, Table@1}) of
+ {ok, Toml@2} ->
+ parse_tables(Input@4, Toml@2);
+
+ {error, E@3} ->
+ {error, E@3}
+ end
+ end;
+
+ [G | _] ->
+ {error, {unexpected, G, <<"["/utf8>>}};
+
+ [] ->
+ {ok, Toml}
+ end.
+
+-spec parse(binary()) -> {ok, gleam@map:map_(binary(), toml())} |
+ {error, parse_error()}.
+parse(Input) ->
+ Input@1 = gleam@string:to_graphemes(Input),
+ Input@2 = drop_comments(Input@1, []),
+ Input@3 = skip_whitespace(Input@2),
+ do(
+ parse_table(Input@3, gleam@map:new()),
+ fun(Toml, Input@4) -> case parse_tables(Input@4, Toml) of
+ {ok, Toml@1} ->
+ {ok, reverse_arrays_of_tables_table(Toml@1)};
+
+ {error, E} ->
+ {error, E}
+ end end
+ ).
+
+-spec parse_array(list(binary()), list(toml())) -> {ok,
+ {toml(), list(binary())}} |
+ {error, parse_error()}.
+parse_array(Input, Elements) ->
+ Input@1 = skip_whitespace(Input),
+ case Input@1 of
+ [<<"]"/utf8>> | Input@2] ->
+ {ok, {{array, gleam@list:reverse(Elements)}, Input@2}};
+
+ _ ->
+ do(
+ parse_value(Input@1),
+ fun(Element, Input@3) ->
+ Elements@1 = [Element | Elements],
+ Input@4 = skip_whitespace(Input@3),
+ case Input@4 of
+ [<<"]"/utf8>> | Input@5] ->
+ {ok,
+ {{array, gleam@list:reverse(Elements@1)},
+ Input@5}};
+
+ [<<","/utf8>> | Input@6] ->
+ Input@7 = skip_whitespace(Input@6),
+ parse_array(Input@7, Elements@1);
+
+ [G | _] ->
+ {error, {unexpected, G, <<"]"/utf8>>}};
+
+ [] ->
+ {error, {unexpected, <<"EOF"/utf8>>, <<"]"/utf8>>}}
+ end
+ end
+ )
+ end.
diff --git a/aoc2023/build/packages/tom/src/tom.gleam b/aoc2023/build/packages/tom/src/tom.gleam
new file mode 100644
index 0000000..e19ce3e
--- /dev/null
+++ b/aoc2023/build/packages/tom/src/tom.gleam
@@ -0,0 +1,1317 @@
+//// A pure Gleam TOML parser!
+////
+//// ```gleam
+//// import tom
+////
+//// const config = "
+//// [person]
+//// name = \"Lucy\"
+//// is_cool = true
+//// "
+////
+//// pub fn main() {
+//// // Parse a string of TOML
+//// let assert Ok(parsed) = tom.parse(config)
+////
+//// // Now you can work with the data directly, or you can use the `get_*`
+//// // functions to retrieve values.
+////
+//// tom.get_string(parsed, ["person", "name"])
+//// // -> Ok("Lucy")
+////
+//// let is_cool = tom.get_bool(parsed, ["person", "is_cool"])
+//// // -> Ok(True)
+//// }
+//// ```
+
+import gleam/int
+import gleam/list
+import gleam/float
+import gleam/string
+import gleam/result
+import gleam/map.{type Map}
+
+/// A TOML document.
+pub type Toml {
+ Int(Int)
+ Float(Float)
+ /// Infinity is a valid number in TOML but Gleam does not support it, so this
+ /// variant represents the infinity values.
+ Infinity(Sign)
+ /// NaN is a valid number in TOML but Gleam does not support it, so this
+ /// variant represents the NaN values.
+ Nan(Sign)
+ Bool(Bool)
+ String(String)
+ Date(Date)
+ Time(Time)
+ DateTime(DateTime)
+ Array(List(Toml))
+ ArrayOfTables(List(Map(String, Toml)))
+ Table(Map(String, Toml))
+ InlineTable(Map(String, Toml))
+}
+
+pub type DateTime {
+ DateTimeValue(date: Date, time: Time, offset: Offset)
+}
+
+pub type Date {
+ DateValue(year: Int, month: Int, day: Int)
+}
+
+pub type Time {
+ TimeValue(hour: Int, minute: Int, second: Int, millisecond: Int)
+}
+
+pub type Offset {
+ Local
+ Offset(direction: Sign, hours: Int, minutes: Int)
+}
+
+pub type Sign {
+ Positive
+ Negative
+}
+
+/// An error that can occur when parsing a TOML document.
+pub type ParseError {
+ /// An unexpected character was encountered when parsing the document.
+ Unexpected(got: String, expected: String)
+ /// More than one items have the same key in the document.
+ KeyAlreadyInUse(key: List(String))
+}
+
+type Tokens =
+ List(String)
+
+type Parsed(a) =
+ Result(#(a, Tokens), ParseError)
+
+/// A number of any kind, returned by the `get_number` function.
+pub type Number {
+ NumberInt(Int)
+ NumberFloat(Float)
+ NumberInfinity(Sign)
+ NumberNan(Sign)
+}
+
+/// An error that can occur when retrieving a value from a TOML document with
+/// one of the `get_*` functions.
+pub type GetError {
+ /// There was no value at the given key.
+ NotFound(key: List(String))
+ /// The value at the given key was not of the expected type.
+ WrongType(key: List(String), expected: String, got: String)
+}
+
+// TODO: test
+/// Get a value of any type from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 1")
+/// get(parsed, ["a", "b", "c"])
+/// // -> Ok(Int(1))
+/// ```
+///
+pub fn get(toml: Map(String, Toml), key: List(String)) -> Result(Toml, GetError) {
+ case key {
+ [] -> Error(NotFound([]))
+ [k] -> result.replace_error(map.get(toml, k), NotFound([k]))
+ [k, ..key] -> {
+ case map.get(toml, k) {
+ Ok(Table(t)) -> push_key(get(t, key), k)
+ Ok(other) -> Error(WrongType([k], "Table", classify(other)))
+ Error(_) -> Error(NotFound([k]))
+ }
+ }
+ }
+}
+
+// TODO: test
+/// Get an int from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 1")
+/// get_int(parsed, ["a", "b", "c"])
+/// // -> Ok(1)
+/// ```
+///
+pub fn get_int(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Int, GetError) {
+ case get(toml, key) {
+ Ok(Int(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Int", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a float from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 1.1")
+/// get_float(parsed, ["a", "b", "c"])
+/// // -> Ok(1.1)
+/// ```
+///
+pub fn get_float(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Float, GetError) {
+ case get(toml, key) {
+ Ok(Float(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Float", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a bool from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = true")
+/// get_bool(parsed, ["a", "b", "c"])
+/// // -> Ok(True)
+/// ```
+///
+pub fn get_bool(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Bool, GetError) {
+ case get(toml, key) {
+ Ok(Bool(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Bool", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a string from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = \"ok\"")
+/// get_string(parsed, ["a", "b", "c"])
+/// // -> Ok("ok")
+/// ```
+///
+pub fn get_string(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(String, GetError) {
+ case get(toml, key) {
+ Ok(String(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "String", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a date from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27")
+/// get_date(parsed, ["a", "b", "c"])
+/// // -> Ok("1979-05-27")
+/// ```
+///
+pub fn get_date(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Date, GetError) {
+ case get(toml, key) {
+ Ok(Date(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Date", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a time from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 07:32:00")
+/// get_time(parsed, ["a", "b", "c"])
+/// // -> Ok("07:32:00")
+/// ```
+///
+pub fn get_time(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Time, GetError) {
+ case get(toml, key) {
+ Ok(Time(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Time", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a date-time from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = 1979-05-27T07:32:00")
+/// get_date_time(parsed, ["a", "b", "c"])
+/// // -> Ok("1979-05-27T07:32:00")
+/// ```
+///
+pub fn get_date_time(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(DateTime, GetError) {
+ case get(toml, key) {
+ Ok(DateTime(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "DateTime", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get an array from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = [1, 2]")
+/// get_array(parsed, ["a", "b", "c"])
+/// // -> Ok([Int(1), Int(2)])
+/// ```
+///
+pub fn get_array(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(List(Toml), GetError) {
+ case get(toml, key) {
+ Ok(Array(i)) -> Ok(i)
+ Ok(ArrayOfTables(i)) -> Ok(list.map(i, Table))
+ Ok(other) -> Error(WrongType(key, "Array", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a table from a TOML document.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = { d = 1 }")
+/// get_table(parsed, ["a", "b", "c"])
+/// // -> Ok(map.from_list([#("d", Int(1))]))
+/// ```
+///
+pub fn get_table(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Map(String, Toml), GetError) {
+ case get(toml, key) {
+ Ok(Table(i)) -> Ok(i)
+ Ok(InlineTable(i)) -> Ok(i)
+ Ok(other) -> Error(WrongType(key, "Table", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+// TODO: test
+/// Get a number of any kind from a TOML document.
+/// This could be an int, a float, a NaN, or an infinity.
+///
+/// ## Examples
+///
+/// ```gleam
+/// let assert Ok(parsed) = parse("a.b.c = { d = inf }")
+/// get_number(parsed, ["a", "b", "c"])
+/// // -> Ok(NumberInfinity(Positive)))
+/// ```
+///
+pub fn get_number(
+ toml: Map(String, Toml),
+ key: List(String),
+) -> Result(Number, GetError) {
+ case get(toml, key) {
+ Ok(Int(x)) -> Ok(NumberInt(x))
+ Ok(Float(x)) -> Ok(NumberFloat(x))
+ Ok(Nan(x)) -> Ok(NumberNan(x))
+ Ok(Infinity(x)) -> Ok(NumberInfinity(x))
+ Ok(other) -> Error(WrongType(key, "Number", classify(other)))
+ Error(e) -> Error(e)
+ }
+}
+
+fn classify(toml: Toml) -> String {
+ case toml {
+ Int(_) -> "Int"
+ Float(_) -> "Float"
+ Nan(Positive) -> "NaN"
+ Nan(Negative) -> "Negative NaN"
+ Infinity(Positive) -> "Infinity"
+ Infinity(Negative) -> "Negative Infinity"
+ Bool(_) -> "Bool"
+ String(_) -> "String"
+ Date(_) -> "Date"
+ Time(_) -> "Time"
+ DateTime(_) -> "DateTime"
+ Array(_) -> "Array"
+ ArrayOfTables(_) -> "Array"
+ Table(_) -> "Table"
+ InlineTable(_) -> "Table"
+ }
+}
+
+fn push_key(result: Result(t, GetError), key: String) -> Result(t, GetError) {
+ case result {
+ Ok(t) -> Ok(t)
+ Error(NotFound(path)) -> Error(NotFound([key, ..path]))
+ Error(WrongType(path, expected, got)) ->
+ Error(WrongType([key, ..path], expected, got))
+ }
+}
+
+pub fn parse(input: String) -> Result(Map(String, Toml), ParseError) {
+ let input = string.to_graphemes(input)
+ let input = drop_comments(input, [])
+ let input = skip_whitespace(input)
+ use toml, input <- do(parse_table(input, map.new()))
+ case parse_tables(input, toml) {
+ Ok(toml) -> Ok(reverse_arrays_of_tables_table(toml))
+ Error(e) -> Error(e)
+ }
+}
+
+fn parse_tables(
+ input: Tokens,
+ toml: Map(String, Toml),
+) -> Result(Map(String, Toml), ParseError) {
+ case input {
+ ["[", "[", ..input] -> {
+ case parse_array_of_tables(input) {
+ Error(e) -> Error(e)
+ Ok(#(#(key, table), input)) -> {
+ case insert(toml, key, ArrayOfTables([table])) {
+ Ok(toml) -> parse_tables(input, toml)
+ Error(e) -> Error(e)
+ }
+ }
+ }
+ }
+ ["[", ..input] -> {
+ case parse_table_and_header(input) {
+ Error(e) -> Error(e)
+ Ok(#(#(key, table), input)) -> {
+ case insert(toml, key, Table(table)) {
+ Ok(toml) -> parse_tables(input, toml)
+ Error(e) -> Error(e)
+ }
+ }
+ }
+ }
+ [g, ..] -> Error(Unexpected(g, "["))
+ [] -> Ok(toml)
+ }
+}
+
+fn parse_array_of_tables(
+ input: Tokens,
+) -> Parsed(#(List(String), Map(String, Toml))) {
+ let input = skip_line_whitespace(input)
+ use key, input <- do(parse_key(input, []))
+ use input <- expect(input, "]")
+ use input <- expect(input, "]")
+ use table, input <- do(parse_table(input, map.new()))
+ Ok(#(#(key, table), input))
+}
+
+fn parse_table_header(input: Tokens) -> Parsed(List(String)) {
+ let input = skip_line_whitespace(input)
+ use key, input <- do(parse_key(input, []))
+ use input <- expect(input, "]")
+ let input = skip_line_whitespace(input)
+ use input <- expect_end_of_line(input)
+ Ok(#(key, input))
+}
+
+fn parse_table_and_header(
+ input: Tokens,
+) -> Parsed(#(List(String), Map(String, Toml))) {
+ use key, input <- do(parse_table_header(input))
+ use table, input <- do(parse_table(input, map.new()))
+ Ok(#(#(key, table), input))
+}
+
+fn parse_table(
+ input: Tokens,
+ toml: Map(String, Toml),
+) -> Parsed(Map(String, Toml)) {
+ let input = skip_whitespace(input)
+ case input {
+ ["[", ..] | [] -> Ok(#(toml, input))
+ _ ->
+ case parse_key_value(input, toml) {
+ Ok(#(toml, input)) ->
+ case skip_line_whitespace(input) {
+ [] -> Ok(#(toml, []))
+ ["\n", ..in] | ["\r\n", ..in] -> parse_table(in, toml)
+ [g, ..] -> Error(Unexpected(g, "\n"))
+ }
+ e -> e
+ }
+ }
+}
+
+fn parse_key_value(
+ input: Tokens,
+ toml: Map(String, Toml),
+) -> Parsed(Map(String, Toml)) {
+ use key, input <- do(parse_key(input, []))
+ let input = skip_line_whitespace(input)
+ use input <- expect(input, "=")
+ let input = skip_line_whitespace(input)
+ use value, input <- do(parse_value(input))
+ case insert(toml, key, value) {
+ Ok(toml) -> Ok(#(toml, input))
+ Error(e) -> Error(e)
+ }
+}
+
+fn insert(
+ table: Map(String, Toml),
+ key: List(String),
+ value: Toml,
+) -> Result(Map(String, Toml), ParseError) {
+ case insert_loop(table, key, value) {
+ Ok(table) -> Ok(table)
+ Error(path) -> Error(KeyAlreadyInUse(path))
+ }
+}
+
+fn insert_loop(
+ table: Map(String, Toml),
+ key: List(String),
+ value: Toml,
+) -> Result(Map(String, Toml), List(String)) {
+ case key {
+ [] -> panic as "unreachable"
+ [k] -> {
+ case map.get(table, k) {
+ Error(Nil) -> Ok(map.insert(table, k, value))
+ Ok(old) -> merge(table, k, old, value)
+ }
+ }
+ [k, ..key] -> {
+ case map.get(table, k) {
+ Error(Nil) -> {
+ case insert_loop(map.new(), key, value) {
+ Ok(inner) -> Ok(map.insert(table, k, Table(inner)))
+ Error(path) -> Error([k, ..path])
+ }
+ }
+ Ok(ArrayOfTables([inner, ..rest])) -> {
+ case insert_loop(inner, key, value) {
+ Ok(inner) ->
+ Ok(map.insert(table, k, ArrayOfTables([inner, ..rest])))
+ Error(path) -> Error([k, ..path])
+ }
+ }
+ Ok(Table(inner)) -> {
+ case insert_loop(inner, key, value) {
+ Ok(inner) -> Ok(map.insert(table, k, Table(inner)))
+ Error(path) -> Error([k, ..path])
+ }
+ }
+ Ok(_) -> Error([k])
+ }
+ }
+ }
+}
+
+fn merge(
+ table: Map(String, Toml),
+ key: String,
+ old: Toml,
+ new: Toml,
+) -> Result(Map(String, Toml), List(String)) {
+ case old, new {
+ // When both are arrays of tables then they are merged together
+ ArrayOfTables(tables), ArrayOfTables(new) ->
+ Ok(map.insert(table, key, ArrayOfTables(list.append(new, tables))))
+
+ _, _ -> Error([key])
+ }
+}
+
+fn expect_end_of_line(input: Tokens, next: fn(Tokens) -> Parsed(a)) -> Parsed(a) {
+ case input {
+ ["\n", ..input] -> next(input)
+ ["\r\n", ..input] -> next(input)
+ [g, ..] -> Error(Unexpected(g, "\n"))
+ [] -> Error(Unexpected("EOF", "\n"))
+ }
+}
+
+fn parse_value(input) -> Parsed(Toml) {
+ case input {
+ ["t", "r", "u", "e", ..input] -> Ok(#(Bool(True), input))
+ ["f", "a", "l", "s", "e", ..input] -> Ok(#(Bool(False), input))
+
+ ["n", "a", "n", ..input] -> Ok(#(Nan(Positive), input))
+ ["+", "n", "a", "n", ..input] -> Ok(#(Nan(Positive), input))
+ ["-", "n", "a", "n", ..input] -> Ok(#(Nan(Negative), input))
+
+ ["i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input))
+ ["+", "i", "n", "f", ..input] -> Ok(#(Infinity(Positive), input))
+ ["-", "i", "n", "f", ..input] -> Ok(#(Infinity(Negative), input))
+
+ ["[", ..input] -> parse_array(input, [])
+ ["{", ..input] -> parse_inline_table(input, map.new())
+
+ ["0", "x", ..input] -> parse_hex(input, 0, Positive)
+ ["+", "0", "x", ..input] -> parse_hex(input, 0, Positive)
+ ["-", "0", "x", ..input] -> parse_hex(input, 0, Negative)
+
+ ["0", "o", ..input] -> parse_octal(input, 0, Positive)
+ ["+", "0", "o", ..input] -> parse_octal(input, 0, Positive)
+ ["-", "0", "o", ..input] -> parse_octal(input, 0, Negative)
+
+ ["0", "b", ..input] -> parse_binary(input, 0, Positive)
+ ["+", "0", "b", ..input] -> parse_binary(input, 0, Positive)
+ ["-", "0", "b", ..input] -> parse_binary(input, 0, Negative)
+
+ ["+", ..input] -> parse_number(input, 0, Positive)
+ ["-", ..input] -> parse_number(input, 0, Negative)
+ ["0", ..]
+ | ["1", ..]
+ | ["2", ..]
+ | ["3", ..]
+ | ["4", ..]
+ | ["5", ..]
+ | ["6", ..]
+ | ["7", ..]
+ | ["8", ..]
+ | ["9", ..] -> parse_number(input, 0, Positive)
+
+ ["\"", "\"", "\"", ..input] -> parse_multi_line_string(input, "")
+ ["\"", ..input] -> parse_string(input, "")
+
+ ["'", "'", "'", ..input] -> parse_multi_line_literal_string(input, "")
+ ["'", ..input] -> parse_literal_string(input, "")
+
+ [g, ..] -> Error(Unexpected(g, "value"))
+ [] -> Error(Unexpected("EOF", "value"))
+ }
+}
+
+fn parse_key(input: Tokens, segments: List(String)) -> Parsed(List(String)) {
+ use segment, input <- do(parse_key_segment(input))
+ let segments = [segment, ..segments]
+ let input = skip_line_whitespace(input)
+
+ case input {
+ [".", ..input] -> parse_key(input, segments)
+ _ -> Ok(#(list.reverse(segments), input))
+ }
+}
+
+fn parse_key_segment(input: Tokens) -> Parsed(String) {
+ let input = skip_line_whitespace(input)
+ case input {
+ ["=", ..] -> Error(Unexpected("=", "Key"))
+ ["\n", ..] -> Error(Unexpected("\n", "Key"))
+ ["\r\n", ..] -> Error(Unexpected("\r\n", "Key"))
+ ["[", ..] -> Error(Unexpected("[", "Key"))
+ ["\"", ..input] -> parse_key_quoted(input, "\"", "")
+ ["'", ..input] -> parse_key_quoted(input, "'", "")
+ _ -> parse_key_bare(input, "")
+ }
+}
+
+fn parse_key_quoted(
+ input: Tokens,
+ close: String,
+ name: String,
+) -> Parsed(String) {
+ case input {
+ [g, ..input] if g == close -> Ok(#(name, input))
+ [g, ..input] -> parse_key_quoted(input, close, name <> g)
+ [] -> Error(Unexpected("EOF", close))
+ }
+}
+
+fn parse_key_bare(input: Tokens, name: String) -> Parsed(String) {
+ case input {
+ [" ", ..input] if name != "" -> Ok(#(name, input))
+ ["=", ..] if name != "" -> Ok(#(name, input))
+ [".", ..] if name != "" -> Ok(#(name, input))
+ ["]", ..] if name != "" -> Ok(#(name, input))
+ [",", ..] if name != "" -> Error(Unexpected(",", "="))
+ ["\n", ..] if name != "" -> Error(Unexpected("\n", "="))
+ ["\r\n", ..] if name != "" -> Error(Unexpected("\r\n", "="))
+ ["\n", ..] -> Error(Unexpected("\n", "key"))
+ ["\r\n", ..] -> Error(Unexpected("\r\n", "key"))
+ ["]", ..] -> Error(Unexpected("]", "key"))
+ [",", ..] -> Error(Unexpected(",", "key"))
+ [g, ..input] -> parse_key_bare(input, name <> g)
+ [] -> Error(Unexpected("EOF", "key"))
+ }
+}
+
+fn skip_line_whitespace(input: Tokens) -> Tokens {
+ list.drop_while(input, fn(g) { g == " " || g == "\t" })
+}
+
+fn skip_whitespace(input: Tokens) -> Tokens {
+ case input {
+ [" ", ..input] -> skip_whitespace(input)
+ ["\t", ..input] -> skip_whitespace(input)
+ ["\n", ..input] -> skip_whitespace(input)
+ ["\r\n", ..input] -> skip_whitespace(input)
+ input -> input
+ }
+}
+
+fn drop_comments(input: Tokens, acc: Tokens) -> Tokens {
+ case input {
+ ["#", ..input] ->
+ input
+ |> list.drop_while(fn(g) { g != "\n" })
+ |> drop_comments(acc)
+ [g, ..input] -> drop_comments(input, [g, ..acc])
+ [] -> list.reverse(acc)
+ }
+}
+
+fn do(
+ result: Result(#(a, Tokens), ParseError),
+ next: fn(a, Tokens) -> Result(b, ParseError),
+) -> Result(b, ParseError) {
+ case result {
+ Ok(#(a, input)) -> next(a, input)
+ Error(e) -> Error(e)
+ }
+}
+
+fn expect(
+ input: Tokens,
+ expected: String,
+ next: fn(Tokens) -> Parsed(a),
+) -> Parsed(a) {
+ case input {
+ [g, ..input] if g == expected -> next(input)
+ [g, ..] -> Error(Unexpected(g, expected))
+ [] -> Error(Unexpected("EOF", expected))
+ }
+}
+
+fn parse_inline_table(
+ input: Tokens,
+ properties: Map(String, Toml),
+) -> Parsed(Toml) {
+ let input = skip_whitespace(input)
+ case input {
+ ["}", ..input] -> Ok(#(InlineTable(properties), input))
+ _ ->
+ case parse_inline_table_property(input, properties) {
+ Ok(#(properties, input)) -> {
+ let input = skip_whitespace(input)
+ case input {
+ ["}", ..input] -> Ok(#(InlineTable(properties), input))
+ [",", ..input] -> {
+ let input = skip_whitespace(input)
+ parse_inline_table(input, properties)
+ }
+ [g, ..] -> Error(Unexpected(g, "}"))
+ [] -> Error(Unexpected("EOF", "}"))
+ }
+ }
+ Error(e) -> Error(e)
+ }
+ }
+}
+
+fn parse_inline_table_property(
+ input: Tokens,
+ properties: Map(String, Toml),
+) -> Parsed(Map(String, Toml)) {
+ let input = skip_whitespace(input)
+ use key, input <- do(parse_key(input, []))
+ let input = skip_line_whitespace(input)
+ use input <- expect(input, "=")
+ let input = skip_line_whitespace(input)
+ use value, input <- do(parse_value(input))
+ case insert(properties, key, value) {
+ Ok(properties) -> Ok(#(properties, input))
+ Error(e) -> Error(e)
+ }
+}
+
+fn parse_array(input: Tokens, elements: List(Toml)) -> Parsed(Toml) {
+ let input = skip_whitespace(input)
+ case input {
+ ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input))
+ _ -> {
+ use element, input <- do(parse_value(input))
+ let elements = [element, ..elements]
+ let input = skip_whitespace(input)
+ case input {
+ ["]", ..input] -> Ok(#(Array(list.reverse(elements)), input))
+ [",", ..input] -> {
+ let input = skip_whitespace(input)
+ parse_array(input, elements)
+ }
+ [g, ..] -> Error(Unexpected(g, "]"))
+ [] -> Error(Unexpected("EOF", "]"))
+ }
+ }
+ }
+}
+
+fn parse_hex(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_hex(input, number, sign)
+ ["0", ..input] -> parse_hex(input, number * 16 + 0, sign)
+ ["1", ..input] -> parse_hex(input, number * 16 + 1, sign)
+ ["2", ..input] -> parse_hex(input, number * 16 + 2, sign)
+ ["3", ..input] -> parse_hex(input, number * 16 + 3, sign)
+ ["4", ..input] -> parse_hex(input, number * 16 + 4, sign)
+ ["5", ..input] -> parse_hex(input, number * 16 + 5, sign)
+ ["6", ..input] -> parse_hex(input, number * 16 + 6, sign)
+ ["7", ..input] -> parse_hex(input, number * 16 + 7, sign)
+ ["8", ..input] -> parse_hex(input, number * 16 + 8, sign)
+ ["9", ..input] -> parse_hex(input, number * 16 + 9, sign)
+ ["a", ..input] -> parse_hex(input, number * 16 + 10, sign)
+ ["b", ..input] -> parse_hex(input, number * 16 + 11, sign)
+ ["c", ..input] -> parse_hex(input, number * 16 + 12, sign)
+ ["d", ..input] -> parse_hex(input, number * 16 + 13, sign)
+ ["e", ..input] -> parse_hex(input, number * 16 + 14, sign)
+ ["f", ..input] -> parse_hex(input, number * 16 + 15, sign)
+ ["A", ..input] -> parse_hex(input, number * 16 + 10, sign)
+ ["B", ..input] -> parse_hex(input, number * 16 + 11, sign)
+ ["C", ..input] -> parse_hex(input, number * 16 + 12, sign)
+ ["D", ..input] -> parse_hex(input, number * 16 + 13, sign)
+ ["E", ..input] -> parse_hex(input, number * 16 + 14, sign)
+ ["F", ..input] -> parse_hex(input, number * 16 + 15, sign)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case sign {
+ Positive -> number
+ Negative -> -number
+ }
+ Ok(#(Int(number), input))
+ }
+ }
+}
+
+fn parse_octal(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_octal(input, number, sign)
+ ["0", ..input] -> parse_octal(input, number * 8 + 0, sign)
+ ["1", ..input] -> parse_octal(input, number * 8 + 1, sign)
+ ["2", ..input] -> parse_octal(input, number * 8 + 2, sign)
+ ["3", ..input] -> parse_octal(input, number * 8 + 3, sign)
+ ["4", ..input] -> parse_octal(input, number * 8 + 4, sign)
+ ["5", ..input] -> parse_octal(input, number * 8 + 5, sign)
+ ["6", ..input] -> parse_octal(input, number * 8 + 6, sign)
+ ["7", ..input] -> parse_octal(input, number * 8 + 7, sign)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case sign {
+ Positive -> number
+ Negative -> -number
+ }
+ Ok(#(Int(number), input))
+ }
+ }
+}
+
+fn parse_binary(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_binary(input, number, sign)
+ ["0", ..input] -> parse_binary(input, number * 2 + 0, sign)
+ ["1", ..input] -> parse_binary(input, number * 2 + 1, sign)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case sign {
+ Positive -> number
+ Negative -> -number
+ }
+ Ok(#(Int(number), input))
+ }
+ }
+}
+
+fn parse_number(input: Tokens, number: Int, sign: Sign) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_number(input, number, sign)
+ ["0", ..input] -> parse_number(input, number * 10 + 0, sign)
+ ["1", ..input] -> parse_number(input, number * 10 + 1, sign)
+ ["2", ..input] -> parse_number(input, number * 10 + 2, sign)
+ ["3", ..input] -> parse_number(input, number * 10 + 3, sign)
+ ["4", ..input] -> parse_number(input, number * 10 + 4, sign)
+ ["5", ..input] -> parse_number(input, number * 10 + 5, sign)
+ ["6", ..input] -> parse_number(input, number * 10 + 6, sign)
+ ["7", ..input] -> parse_number(input, number * 10 + 7, sign)
+ ["8", ..input] -> parse_number(input, number * 10 + 8, sign)
+ ["9", ..input] -> parse_number(input, number * 10 + 9, sign)
+
+ ["-", ..input] -> parse_date(input, number)
+ [":", ..input] if number < 24 -> parse_time_minute(input, number)
+
+ [".", ..input] -> parse_float(input, int.to_float(number), sign, 0.1)
+
+ ["e", "+", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Positive)
+ ["e", "-", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Negative)
+ ["e", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Positive)
+ ["E", "+", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Positive)
+ ["E", "-", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Negative)
+ ["E", ..input] ->
+ parse_exponent(input, int.to_float(number), sign, 0, Positive)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case sign {
+ Positive -> number
+ Negative -> -number
+ }
+ Ok(#(Int(number), input))
+ }
+ }
+}
+
+fn parse_exponent(
+ input: Tokens,
+ n: Float,
+ n_sign: Sign,
+ ex: Int,
+ ex_sign: Sign,
+) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_exponent(input, n, n_sign, ex, ex_sign)
+ ["0", ..input] -> parse_exponent(input, n, n_sign, ex * 10, ex_sign)
+ ["1", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 1, ex_sign)
+ ["2", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 2, ex_sign)
+ ["3", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 3, ex_sign)
+ ["4", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 4, ex_sign)
+ ["5", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 5, ex_sign)
+ ["6", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 6, ex_sign)
+ ["7", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 7, ex_sign)
+ ["8", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 8, ex_sign)
+ ["9", ..input] -> parse_exponent(input, n, n_sign, ex * 10 + 9, ex_sign)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case n_sign {
+ Positive -> n
+ Negative -> n *. -1.0
+ }
+ let exponent =
+ int.to_float(case ex_sign {
+ Positive -> ex
+ Negative -> -ex
+ })
+ let multiplier = case float.power(10.0, exponent) {
+ Ok(multiplier) -> multiplier
+ Error(_) -> 1.0
+ }
+ Ok(#(Float(number *. multiplier), input))
+ }
+ }
+}
+
+fn parse_float(
+ input: Tokens,
+ number: Float,
+ sign: Sign,
+ unit: Float,
+) -> Parsed(Toml) {
+ case input {
+ ["_", ..input] -> parse_float(input, number, sign, unit)
+ ["0", ..input] -> parse_float(input, number, sign, unit *. 0.1)
+ ["1", ..input] ->
+ parse_float(input, number +. 1.0 *. unit, sign, unit *. 0.1)
+ ["2", ..input] ->
+ parse_float(input, number +. 2.0 *. unit, sign, unit *. 0.1)
+ ["3", ..input] ->
+ parse_float(input, number +. 3.0 *. unit, sign, unit *. 0.1)
+ ["4", ..input] ->
+ parse_float(input, number +. 4.0 *. unit, sign, unit *. 0.1)
+ ["5", ..input] ->
+ parse_float(input, number +. 5.0 *. unit, sign, unit *. 0.1)
+ ["6", ..input] ->
+ parse_float(input, number +. 6.0 *. unit, sign, unit *. 0.1)
+ ["7", ..input] ->
+ parse_float(input, number +. 7.0 *. unit, sign, unit *. 0.1)
+ ["8", ..input] ->
+ parse_float(input, number +. 8.0 *. unit, sign, unit *. 0.1)
+ ["9", ..input] ->
+ parse_float(input, number +. 9.0 *. unit, sign, unit *. 0.1)
+
+ ["e", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive)
+ ["e", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative)
+ ["e", ..input] -> parse_exponent(input, number, sign, 0, Positive)
+ ["E", "+", ..input] -> parse_exponent(input, number, sign, 0, Positive)
+ ["E", "-", ..input] -> parse_exponent(input, number, sign, 0, Negative)
+ ["E", ..input] -> parse_exponent(input, number, sign, 0, Positive)
+
+ // Anything else and the number is terminated
+ input -> {
+ let number = case sign {
+ Positive -> number
+ Negative -> number *. -1.0
+ }
+ Ok(#(Float(number), input))
+ }
+ }
+}
+
+fn parse_string(input: Tokens, string: String) -> Parsed(Toml) {
+ case input {
+ ["\"", ..input] -> Ok(#(String(string), input))
+ ["\\", "t", ..input] -> parse_string(input, string <> "\t")
+ ["\\", "n", ..input] -> parse_string(input, string <> "\n")
+ ["\\", "r", ..input] -> parse_string(input, string <> "\r")
+ ["\\", "\"", ..input] -> parse_string(input, string <> "\"")
+ ["\\", "\\", ..input] -> parse_string(input, string <> "\\")
+ [] -> Error(Unexpected("EOF", "\""))
+ ["\n", ..] -> Error(Unexpected("\n", "\""))
+ ["\r\n", ..] -> Error(Unexpected("\r\n", "\""))
+ [g, ..input] -> parse_string(input, string <> g)
+ }
+}
+
+fn parse_multi_line_string(input: Tokens, string: String) -> Parsed(Toml) {
+ case input {
+ ["\"", "\"", "\"", ..input] -> Ok(#(String(string), input))
+ ["\\", "\n", ..input] ->
+ parse_multi_line_string(skip_whitespace(input), string)
+ ["\\", "\r\n", ..input] ->
+ parse_multi_line_string(skip_whitespace(input), string)
+ ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string)
+ ["\n", ..input] if string == "" -> parse_multi_line_string(input, string)
+ ["\r\n", ..input] if string == "" -> parse_multi_line_string(input, string)
+ ["\\", "t", ..input] -> parse_multi_line_string(input, string <> "\t")
+ ["\\", "n", ..input] -> parse_multi_line_string(input, string <> "\n")
+ ["\\", "r", ..input] -> parse_multi_line_string(input, string <> "\r")
+ ["\\", "\"", ..input] -> parse_multi_line_string(input, string <> "\"")
+ ["\\", "\\", ..input] -> parse_multi_line_string(input, string <> "\\")
+ [] -> Error(Unexpected("EOF", "\""))
+ [g, ..input] -> parse_multi_line_string(input, string <> g)
+ }
+}
+
+fn parse_multi_line_literal_string(
+ input: Tokens,
+ string: String,
+) -> Parsed(Toml) {
+ case input {
+ [] -> Error(Unexpected("EOF", "\""))
+ ["'", "'", "'", "'", ..] -> Error(Unexpected("''''", "'''"))
+ ["'", "'", "'", ..input] -> Ok(#(String(string), input))
+ ["\n", ..input] if string == "" ->
+ parse_multi_line_literal_string(input, string)
+ ["\r\n", ..input] if string == "" ->
+ parse_multi_line_literal_string(input, string)
+ [g, ..input] -> parse_multi_line_literal_string(input, string <> g)
+ }
+}
+
+fn parse_literal_string(input: Tokens, string: String) -> Parsed(Toml) {
+ case input {
+ [] -> Error(Unexpected("EOF", "\""))
+ ["\n", ..] -> Error(Unexpected("\n", "'"))
+ ["\r\n", ..] -> Error(Unexpected("\r\n", "'"))
+ ["'", ..input] -> Ok(#(String(string), input))
+ [g, ..input] -> parse_literal_string(input, string <> g)
+ }
+}
+
+fn reverse_arrays_of_tables(toml: Toml) -> Toml {
+ case toml {
+ ArrayOfTables(tables) ->
+ ArrayOfTables(reverse_arrays_of_tables_array(tables, []))
+
+ Table(table) -> Table(reverse_arrays_of_tables_table(table))
+
+ _ -> toml
+ }
+}
+
+fn reverse_arrays_of_tables_table(table: Map(String, Toml)) -> Map(String, Toml) {
+ map.map_values(table, fn(_, v) { reverse_arrays_of_tables(v) })
+}
+
+fn reverse_arrays_of_tables_array(
+ array: List(Map(String, Toml)),
+ acc: List(Map(String, Toml)),
+) -> List(Map(String, Toml)) {
+ case array {
+ [] -> acc
+ [first, ..rest] -> {
+ let first = reverse_arrays_of_tables_table(first)
+ reverse_arrays_of_tables_array(rest, [first, ..acc])
+ }
+ }
+}
+
+fn parse_time_minute(input: Tokens, hours: Int) -> Parsed(Toml) {
+ use minutes, input <- do(parse_number_under_60(input, "minutes"))
+ use #(seconds, ms), input <- do(parse_time_s_ms(input))
+ let time = TimeValue(hours, minutes, seconds, ms)
+ Ok(#(Time(time), input))
+}
+
+fn parse_hour_minute(input: Tokens) -> Parsed(#(Int, Int)) {
+ use hours, input <- do(case input {
+ ["0", "0", ":", ..input] -> Ok(#(0, input))
+ ["0", "1", ":", ..input] -> Ok(#(1, input))
+ ["0", "2", ":", ..input] -> Ok(#(2, input))
+ ["0", "3", ":", ..input] -> Ok(#(3, input))
+ ["0", "4", ":", ..input] -> Ok(#(4, input))
+ ["0", "5", ":", ..input] -> Ok(#(5, input))
+ ["0", "6", ":", ..input] -> Ok(#(6, input))
+ ["0", "7", ":", ..input] -> Ok(#(7, input))
+ ["0", "8", ":", ..input] -> Ok(#(8, input))
+ ["0", "9", ":", ..input] -> Ok(#(9, input))
+ ["1", "0", ":", ..input] -> Ok(#(10, input))
+ ["1", "1", ":", ..input] -> Ok(#(11, input))
+ ["1", "2", ":", ..input] -> Ok(#(12, input))
+ ["1", "3", ":", ..input] -> Ok(#(13, input))
+ ["1", "4", ":", ..input] -> Ok(#(14, input))
+ ["1", "5", ":", ..input] -> Ok(#(15, input))
+ ["1", "6", ":", ..input] -> Ok(#(16, input))
+ ["1", "7", ":", ..input] -> Ok(#(17, input))
+ ["1", "8", ":", ..input] -> Ok(#(18, input))
+ ["1", "9", ":", ..input] -> Ok(#(19, input))
+ ["2", "0", ":", ..input] -> Ok(#(20, input))
+ ["2", "1", ":", ..input] -> Ok(#(21, input))
+ ["2", "2", ":", ..input] -> Ok(#(22, input))
+ ["2", "3", ":", ..input] -> Ok(#(23, input))
+ [g, ..] -> Error(Unexpected(g, "time"))
+ [] -> Error(Unexpected("EOF", "time"))
+ })
+
+ use minutes, input <- do(parse_number_under_60(input, "minutes"))
+ Ok(#(#(hours, minutes), input))
+}
+
+fn parse_time_value(input: Tokens) -> Parsed(Time) {
+ use #(hours, minutes), input <- do(parse_hour_minute(input))
+ use #(seconds, ms), input <- do(parse_time_s_ms(input))
+ let time = TimeValue(hours, minutes, seconds, ms)
+ Ok(#(time, input))
+}
+
+fn parse_time_s_ms(input: Tokens) -> Parsed(#(Int, Int)) {
+ case input {
+ [":", ..input] -> {
+ use seconds, input <- do(parse_number_under_60(input, "seconds"))
+ case input {
+ [".", ..input] -> parse_time_ms(input, seconds, 0)
+ _ -> Ok(#(#(seconds, 0), input))
+ }
+ }
+
+ _ -> Ok(#(#(0, 0), input))
+ }
+}
+
+fn parse_time_ms(input: Tokens, seconds: Int, ms: Int) -> Parsed(#(Int, Int)) {
+ case input {
+ ["0", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 0)
+ ["1", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 1)
+ ["2", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 2)
+ ["3", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 3)
+ ["4", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 4)
+ ["5", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 5)
+ ["6", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 6)
+ ["7", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 7)
+ ["8", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 8)
+ ["9", ..input] if ms < 100_000 -> parse_time_ms(input, seconds, ms * 10 + 9)
+
+ // Anything else and the number is terminated
+ _ -> Ok(#(#(seconds, ms), input))
+ }
+}
+
+fn parse_number_under_60(input: Tokens, expected: String) -> Parsed(Int) {
+ case input {
+ ["0", "0", ..input] -> Ok(#(0, input))
+ ["0", "1", ..input] -> Ok(#(1, input))
+ ["0", "2", ..input] -> Ok(#(2, input))
+ ["0", "3", ..input] -> Ok(#(3, input))
+ ["0", "4", ..input] -> Ok(#(4, input))
+ ["0", "5", ..input] -> Ok(#(5, input))
+ ["0", "6", ..input] -> Ok(#(6, input))
+ ["0", "7", ..input] -> Ok(#(7, input))
+ ["0", "8", ..input] -> Ok(#(8, input))
+ ["0", "9", ..input] -> Ok(#(9, input))
+ ["1", "0", ..input] -> Ok(#(10, input))
+ ["1", "1", ..input] -> Ok(#(11, input))
+ ["1", "2", ..input] -> Ok(#(12, input))
+ ["1", "3", ..input] -> Ok(#(13, input))
+ ["1", "4", ..input] -> Ok(#(14, input))
+ ["1", "5", ..input] -> Ok(#(15, input))
+ ["1", "6", ..input] -> Ok(#(16, input))
+ ["1", "7", ..input] -> Ok(#(17, input))
+ ["1", "8", ..input] -> Ok(#(18, input))
+ ["1", "9", ..input] -> Ok(#(19, input))
+ ["2", "0", ..input] -> Ok(#(20, input))
+ ["2", "1", ..input] -> Ok(#(21, input))
+ ["2", "2", ..input] -> Ok(#(22, input))
+ ["2", "3", ..input] -> Ok(#(23, input))
+ ["2", "4", ..input] -> Ok(#(24, input))
+ ["2", "5", ..input] -> Ok(#(25, input))
+ ["2", "6", ..input] -> Ok(#(26, input))
+ ["2", "7", ..input] -> Ok(#(27, input))
+ ["2", "8", ..input] -> Ok(#(28, input))
+ ["2", "9", ..input] -> Ok(#(29, input))
+ ["3", "0", ..input] -> Ok(#(30, input))
+ ["3", "1", ..input] -> Ok(#(31, input))
+ ["3", "2", ..input] -> Ok(#(32, input))
+ ["3", "3", ..input] -> Ok(#(33, input))
+ ["3", "4", ..input] -> Ok(#(34, input))
+ ["3", "5", ..input] -> Ok(#(35, input))
+ ["3", "6", ..input] -> Ok(#(36, input))
+ ["3", "7", ..input] -> Ok(#(37, input))
+ ["3", "8", ..input] -> Ok(#(38, input))
+ ["3", "9", ..input] -> Ok(#(39, input))
+ ["4", "0", ..input] -> Ok(#(40, input))
+ ["4", "1", ..input] -> Ok(#(41, input))
+ ["4", "2", ..input] -> Ok(#(42, input))
+ ["4", "3", ..input] -> Ok(#(43, input))
+ ["4", "4", ..input] -> Ok(#(44, input))
+ ["4", "5", ..input] -> Ok(#(45, input))
+ ["4", "6", ..input] -> Ok(#(46, input))
+ ["4", "7", ..input] -> Ok(#(47, input))
+ ["4", "8", ..input] -> Ok(#(48, input))
+ ["4", "9", ..input] -> Ok(#(49, input))
+ ["5", "0", ..input] -> Ok(#(50, input))
+ ["5", "1", ..input] -> Ok(#(51, input))
+ ["5", "2", ..input] -> Ok(#(52, input))
+ ["5", "3", ..input] -> Ok(#(53, input))
+ ["5", "4", ..input] -> Ok(#(54, input))
+ ["5", "5", ..input] -> Ok(#(55, input))
+ ["5", "6", ..input] -> Ok(#(56, input))
+ ["5", "7", ..input] -> Ok(#(57, input))
+ ["5", "8", ..input] -> Ok(#(58, input))
+ ["5", "9", ..input] -> Ok(#(59, input))
+
+ [g, ..] -> Error(Unexpected(g, expected))
+ [] -> Error(Unexpected("EOF", expected))
+ }
+}
+
+fn parse_date(input: Tokens, year: Int) -> Parsed(Toml) {
+ case input {
+ ["0", "1", "-", ..input] -> parse_date_day(input, year, 1)
+ ["0", "2", "-", ..input] -> parse_date_day(input, year, 2)
+ ["0", "3", "-", ..input] -> parse_date_day(input, year, 3)
+ ["0", "4", "-", ..input] -> parse_date_day(input, year, 4)
+ ["0", "5", "-", ..input] -> parse_date_day(input, year, 5)
+ ["0", "6", "-", ..input] -> parse_date_day(input, year, 6)
+ ["0", "7", "-", ..input] -> parse_date_day(input, year, 7)
+ ["0", "8", "-", ..input] -> parse_date_day(input, year, 8)
+ ["0", "9", "-", ..input] -> parse_date_day(input, year, 9)
+ ["1", "0", "-", ..input] -> parse_date_day(input, year, 10)
+ ["1", "1", "-", ..input] -> parse_date_day(input, year, 11)
+ ["1", "2", "-", ..input] -> parse_date_day(input, year, 12)
+
+ [g, ..] -> Error(Unexpected(g, "date month"))
+ [] -> Error(Unexpected("EOF", "date month"))
+ }
+}
+
+fn parse_date_day(input: Tokens, year: Int, month: Int) -> Parsed(Toml) {
+ case input {
+ ["0", "1", ..input] -> parse_date_end(input, year, month, 1)
+ ["0", "2", ..input] -> parse_date_end(input, year, month, 2)
+ ["0", "3", ..input] -> parse_date_end(input, year, month, 3)
+ ["0", "4", ..input] -> parse_date_end(input, year, month, 4)
+ ["0", "5", ..input] -> parse_date_end(input, year, month, 5)
+ ["0", "6", ..input] -> parse_date_end(input, year, month, 6)
+ ["0", "7", ..input] -> parse_date_end(input, year, month, 7)
+ ["0", "8", ..input] -> parse_date_end(input, year, month, 8)
+ ["0", "9", ..input] -> parse_date_end(input, year, month, 9)
+ ["1", "0", ..input] -> parse_date_end(input, year, month, 10)
+ ["1", "1", ..input] -> parse_date_end(input, year, month, 11)
+ ["1", "2", ..input] -> parse_date_end(input, year, month, 12)
+ ["1", "3", ..input] -> parse_date_end(input, year, month, 13)
+ ["1", "4", ..input] -> parse_date_end(input, year, month, 14)
+ ["1", "5", ..input] -> parse_date_end(input, year, month, 15)
+ ["1", "6", ..input] -> parse_date_end(input, year, month, 16)
+ ["1", "7", ..input] -> parse_date_end(input, year, month, 17)
+ ["1", "8", ..input] -> parse_date_end(input, year, month, 18)
+ ["1", "9", ..input] -> parse_date_end(input, year, month, 19)
+ ["2", "0", ..input] -> parse_date_end(input, year, month, 20)
+ ["2", "1", ..input] -> parse_date_end(input, year, month, 21)
+ ["2", "2", ..input] -> parse_date_end(input, year, month, 22)
+ ["2", "3", ..input] -> parse_date_end(input, year, month, 23)
+ ["2", "4", ..input] -> parse_date_end(input, year, month, 24)
+ ["2", "5", ..input] -> parse_date_end(input, year, month, 25)
+ ["2", "6", ..input] -> parse_date_end(input, year, month, 26)
+ ["2", "7", ..input] -> parse_date_end(input, year, month, 27)
+ ["2", "8", ..input] -> parse_date_end(input, year, month, 28)
+ ["2", "9", ..input] -> parse_date_end(input, year, month, 29)
+ ["3", "0", ..input] -> parse_date_end(input, year, month, 30)
+ ["3", "1", ..input] -> parse_date_end(input, year, month, 31)
+
+ [g, ..] -> Error(Unexpected(g, "date day"))
+ [] -> Error(Unexpected("EOF", "date day"))
+ }
+}
+
+fn parse_date_end(
+ input: Tokens,
+ year: Int,
+ month: Int,
+ day: Int,
+) -> Parsed(Toml) {
+ let date = DateValue(year, month, day)
+ case input {
+ [" ", ..input] | ["T", ..input] -> {
+ use time, input <- do(parse_time_value(input))
+ use offset, input <- do(parse_offset(input))
+ Ok(#(DateTime(DateTimeValue(date, time, offset)), input))
+ }
+
+ _ -> Ok(#(Date(date), input))
+ }
+}
+
+fn parse_offset(input: Tokens) -> Parsed(Offset) {
+ case input {
+ ["Z", ..input] -> Ok(#(Offset(Positive, 0, 0), input))
+ ["+", ..input] -> parse_offset_hours(input, Positive)
+ ["-", ..input] -> parse_offset_hours(input, Negative)
+ _ -> Ok(#(Local, input))
+ }
+}
+
+fn parse_offset_hours(input: Tokens, sign: Sign) -> Parsed(Offset) {
+ use #(hours, minutes), input <- do(parse_hour_minute(input))
+ Ok(#(Offset(sign, hours, minutes), input))
+}