Record parsing
This commit is contained in:
parent
56e63051bc
commit
6f02d4c4e6
@ -159,7 +159,7 @@ parse_list_loop3(Inner, Tk, String, Row, Start, Acc) ->
|
|||||||
parse_record_or_map({_, _, {map, [KeyType, ValueType]}}, Tk, String, _, _) ->
|
parse_record_or_map({_, _, {map, [KeyType, ValueType]}}, Tk, String, _, _) ->
|
||||||
parse_map(KeyType, ValueType, Tk, String, #{});
|
parse_map(KeyType, ValueType, Tk, String, #{});
|
||||||
parse_record_or_map({_, _, {record, Fields}}, Tk, String, _, _) ->
|
parse_record_or_map({_, _, {record, Fields}}, Tk, String, _, _) ->
|
||||||
parse_record(Fields, Tk, String);
|
parse_record(Fields, Tk, String, #{});
|
||||||
parse_record_or_map({_, _, unknown_type}, Tk, String, _, _) ->
|
parse_record_or_map({_, _, unknown_type}, Tk, String, _, _) ->
|
||||||
case next_token(Tk, String) of
|
case next_token(Tk, String) of
|
||||||
{ok, {{character, "}", _, _, _}, NewTk, NewString}} ->
|
{ok, {{character, "}", _, _, _}, NewTk, NewString}} ->
|
||||||
@ -174,8 +174,83 @@ parse_record_or_map({_, _, unknown_type}, Tk, String, _, _) ->
|
|||||||
parse_record_or_map({O, N, _}, _, _, Row, Start) ->
|
parse_record_or_map({O, N, _}, _, _, Row, Start) ->
|
||||||
{error, {wrong_type, O, N, map, Row, Start, Start}}.
|
{error, {wrong_type, O, N, map, Row, Start, Start}}.
|
||||||
|
|
||||||
parse_record(Fields, Tk, String) ->
|
parse_record(Fields, Tk, String, Acc) ->
|
||||||
{error, not_yet_implemented}.
|
case next_token(Tk, String) of
|
||||||
|
{ok, {{alphanum, Ident, Row, Start, End}, NewTk, NewString}} ->
|
||||||
|
parse_record2(Fields, NewTk, NewString, Acc, Ident, Row, Start, End);
|
||||||
|
{ok, {{character, "}", Row, Start, End}, NewTk, NewString}} ->
|
||||||
|
parse_record_end(Fields, NewTk, NewString, Acc, Row, Start, End);
|
||||||
|
{ok, {{_, S, Row, Start, End}, _, _}} ->
|
||||||
|
{error, {unexpected_token, S, Row, Start, End}};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record2(Fields, Tk, String, Acc, Ident, Row, Start, End) ->
|
||||||
|
case lists:keyfind(Ident, 1, Fields) of
|
||||||
|
{_, Type} ->
|
||||||
|
parse_record3(Fields, Tk, String, Acc, Ident, Row, Start, End, Type);
|
||||||
|
false ->
|
||||||
|
{error, {invalid_field, Ident, Row, Start, End}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record3(Fields, Tk, String, Acc, Ident, Row, Start, End, Type) ->
|
||||||
|
case maps:is_key(Ident, Acc) of
|
||||||
|
false ->
|
||||||
|
parse_record4(Fields, Tk, String, Acc, Ident, Type);
|
||||||
|
true ->
|
||||||
|
{error, {field_already_present, Ident, Row, Start, End}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record4(Fields, Tk, String, Acc, Ident, Type) ->
|
||||||
|
case expect_tokens(["="], Tk, String) of
|
||||||
|
{ok, {NewTk, NewString}} ->
|
||||||
|
parse_record5(Fields, NewTk, NewString, Acc, Ident, Type);
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record5(Fields, Tk, String, Acc, Ident, Type) ->
|
||||||
|
case parse_expression(Type, Tk, String) of
|
||||||
|
{ok, {Result, NewTk, NewString}} ->
|
||||||
|
NewAcc = maps:put(Ident, Result, Acc),
|
||||||
|
parse_record6(Fields, NewTk, NewString, NewAcc);
|
||||||
|
{error, Reason} ->
|
||||||
|
wrap_error(Reason, {record_field, Ident})
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record6(Fields, Tk, String, Acc) ->
|
||||||
|
case next_token(Tk, String) of
|
||||||
|
{ok, {{character, ",", _, _, _}, NewTk, NewString}} ->
|
||||||
|
parse_record(Fields, NewTk, NewString, Acc);
|
||||||
|
{ok, {{character, "}", Row, Start, End}, NewTk, NewString}} ->
|
||||||
|
parse_record_end(Fields, NewTk, NewString, Acc, Row, Start, End);
|
||||||
|
{ok, {{_, S, Row, Start, End}, _, _}} ->
|
||||||
|
{error, {unexpected_token, S, Row, Start, End}};
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record_end(Fields, Tk, String, FieldValues, Row, Start, End) ->
|
||||||
|
case parse_record_final_loop(Fields, FieldValues, []) of
|
||||||
|
{ok, Result} ->
|
||||||
|
{ok, {Result, Tk, String}};
|
||||||
|
{error, {missing_field, Name}} ->
|
||||||
|
{error, {missing_field, Name, Row, Start, End}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_record_final_loop([{Name, _} | Rest], FieldValues, Acc) ->
|
||||||
|
case maps:find(Name, FieldValues) of
|
||||||
|
{ok, Value} ->
|
||||||
|
parse_record_final_loop(Rest, FieldValues, [Value | Acc]);
|
||||||
|
error ->
|
||||||
|
{error, {missing_field, Name}}
|
||||||
|
end;
|
||||||
|
parse_record_final_loop([], _, FieldsReverse) ->
|
||||||
|
Fields = lists:reverse(FieldsReverse),
|
||||||
|
Tuple = list_to_tuple(Fields),
|
||||||
|
{ok, {tuple, Tuple}}.
|
||||||
|
|
||||||
|
|
||||||
%%% Map Parsing
|
%%% Map Parsing
|
||||||
|
|
||||||
@ -238,20 +313,33 @@ check_sophia_to_fate(Type, Sophia, Fate) ->
|
|||||||
erlang:error({to_fate_failed, Fate, FateActual})
|
erlang:error({to_fate_failed, Fate, FateActual})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
check_parser(Type, Sophia, Fate) ->
|
|
||||||
check_sophia_to_fate(Type, Sophia, Fate),
|
|
||||||
check_sophia_to_fate(unknown_type(), Sophia, Fate),
|
|
||||||
|
|
||||||
% Finally, check that the FATE result is something that gmb understands.
|
|
||||||
gmb_fate_encoding:serialize(Fate),
|
|
||||||
|
|
||||||
ok.
|
|
||||||
|
|
||||||
check_parser(Sophia, Fate) ->
|
check_parser(Sophia, Fate) ->
|
||||||
|
% Compile the literal using the compiler, to check that it is valid Sophia
|
||||||
|
% syntax, and to get an AACI object to pass to the parser.
|
||||||
Source = "contract C = entrypoint f() = " ++ Sophia,
|
Source = "contract C = entrypoint f() = " ++ Sophia,
|
||||||
{ok, AACI} = hz_aaci:aaci_from_string(Source),
|
{ok, AACI} = hz_aaci:aaci_from_string(Source),
|
||||||
{ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"),
|
{ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"),
|
||||||
check_parser(Type, Sophia, Fate).
|
|
||||||
|
% Also check that the FATE term is valid, by running it through gmb.
|
||||||
|
gmb_fate_encoding:serialize(Fate),
|
||||||
|
|
||||||
|
% Now check that our parser produces that output.
|
||||||
|
check_sophia_to_fate(Type, Sophia, Fate),
|
||||||
|
% Also check that it can be parsed without type information.
|
||||||
|
check_sophia_to_fate(unknown_type(), Sophia, Fate).
|
||||||
|
|
||||||
|
check_parser_with_typedef(Typedef, Sophia, Fate) ->
|
||||||
|
% Compile the type definitions alongside the usual literal expression.
|
||||||
|
Source = "contract C =\n " ++ Typedef ++ "\n entrypoint f() = " ++ Sophia,
|
||||||
|
{ok, AACI} = hz_aaci:aaci_from_string(Source),
|
||||||
|
{ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"),
|
||||||
|
|
||||||
|
% Check the FATE term as usual.
|
||||||
|
gmb_fate_encoding:serialize(Fate),
|
||||||
|
|
||||||
|
% Do a typed parse, as usual, but there are probably record/variant
|
||||||
|
% definitions in the AACI, so untyped parses probably don't work.
|
||||||
|
check_sophia_to_fate(Type, Sophia, Fate).
|
||||||
|
|
||||||
int_test() ->
|
int_test() ->
|
||||||
check_parser("123", 123).
|
check_parser("123", 123).
|
||||||
@ -265,3 +353,12 @@ list_of_lists_test() ->
|
|||||||
maps_test() ->
|
maps_test() ->
|
||||||
check_parser("{[1] = 2, [3] = 4}", #{1 => 2, 3 => 4}).
|
check_parser("{[1] = 2, [3] = 4}", #{1 => 2, 3 => 4}).
|
||||||
|
|
||||||
|
records_test() ->
|
||||||
|
TypeDef = "record pair = {x: int, y: int}",
|
||||||
|
Sophia = "{x = 1, y = 2}",
|
||||||
|
Fate = {tuple, {1, 2}},
|
||||||
|
check_parser_with_typedef(TypeDef, Sophia, Fate),
|
||||||
|
% The above won't run an untyped parse on the expression, but we can. It
|
||||||
|
% will error, though.
|
||||||
|
{error, {unresolved_record, _, _, _}} = parse_literal(unknown_type(), Sophia).
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user