-module(tom). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([parse/1]). -export_type([toml/0, parse_error/0, sign/0]). -type toml() :: {int, integer()} | {float, float()} | {bool, boolean()} | {string, binary()} | {date, binary()} | {time, binary()} | {date_time, binary()} | {array, list(toml())} | {table, gleam@map:map_(binary(), toml())}. -type parse_error() :: {unexpected, binary(), binary()} | {key_already_in_use, list(binary())}. -type sign() :: positive | negative. -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 => 122}); [K] -> case gleam@map:get(Table, K) of {error, nil} -> {ok, gleam@map:insert(Table, K, Value)}; {ok, _} -> {error, [K]} 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, {table, Inner@1}} -> case insert_loop(Inner@1, Key@1, Value) of {ok, Inner@2} -> {ok, gleam@map:insert(Table, K@1, {table, Inner@2})}; {error, Path@1} -> {error, [K@1 | Path@1]} 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, {FID, list(binary())}} | {error, parse_error()}) ) -> {ok, {FID, 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, <>); [] -> {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, <>); [] -> {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, {FIO, list(binary())}} | {error, parse_error()}, fun((FIO, list(binary())) -> {ok, FIR} | {error, parse_error()}) ) -> {ok, FIR} | {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, {FIW, list(binary())}} | {error, parse_error()}) ) -> {ok, {FIW, 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) -> expect_end_of_line( Input@3, fun(Input@4) -> {ok, {Key, Input@4}} end ) end ) 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); Input@12 -> Number@1 = case Sign of positive -> Number; negative -> Number * -1.0 end, {ok, {{float, Number@1}, Input@12}} 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_float(Input@12, gleam@int:to_float(Number), Sign, 0.1); Input@13 -> Number@1 = case Sign of positive -> Number; negative -> - Number end, {ok, {{int, Number@1}, Input@13}} 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, <>); [<<"\\"/utf8>>, <<"n"/utf8>> | Input@3] -> parse_string(Input@3, <>); [<<"\\"/utf8>>, <<"r"/utf8>> | Input@4] -> parse_string(Input@4, <>); [<<"\\"/utf8>>, <<"\""/utf8>> | Input@5] -> parse_string(Input@5, <>); [<<"\\"/utf8>>, <<"\\"/utf8>> | Input@6] -> parse_string(Input@6, <>); [] -> {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, <>) 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}}; [<<"["/utf8>> | Input@3] -> parse_array(Input@3, []); [<<"{"/utf8>> | Input@4] -> parse_inline_table(Input@4, gleam@map:new()); [<<"+"/utf8>> | Input@5] -> parse_number(Input@5, 0, positive); [<<"-"/utf8>> | Input@6] -> parse_number(Input@6, 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>> | Input@7] -> parse_string(Input@7, <<""/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, {{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, {{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) -> expect_end_of_line( Input@5, fun(Input@6) -> case insert(Toml, Key, Value) of {ok, Toml@1} -> {ok, {Toml@1, Input@6}}; {error, E} -> {error, E} end 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}} -> parse_table(Input@2, Toml@1); E -> E 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>> | Input@1] -> case parse_table_and_header(Input@1) of {error, E} -> {error, E}; {ok, {{Key, Table}, Input@2}} -> case insert(Toml, Key, {table, Table}) of {ok, Toml@1} -> parse_tables(Input@2, Toml@1); {error, E@1} -> {error, E@1} 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) -> parse_tables(Input@4, Toml) 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.