Current section

Files

Jump to
tom src tom.erl
Raw

src/tom.erl

-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, <<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, {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, <<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_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.