Make Hakuzaru Great Again #22

Merged
zxq9 merged 46 commits from parser into master 2026-05-10 15:26:44 +09:00
2 changed files with 40 additions and 23 deletions
Showing only changes of commit 2bf384ca82 - Show all commits
-2
View File
@@ -22,8 +22,6 @@
fate_to_erlang/2,
erlang_args_to_fate/2,
get_function_signature/2]).
% Internal stuff that is useful for writing AACI unit tests.
-export([aaci_from_string/1, annotate_type/2]).
%%% Types
+40 -21
View File
@@ -438,12 +438,33 @@ check_sophia_to_fate(Type, Sophia, Fate) ->
erlang:error({to_fate_failed, Fate, FateActual})
end.
check_parser(Sophia, Fate) ->
compile_entrypoint_code_and_type(Source, Entrypoint) ->
{ok, #{fate_code := FateCode, aci := ACI}} = so_compiler:from_string(Source, [{aci, json}]),
% Find the fcode for the correct entrypoint.
{fcode, Bodies, NamesMap, _} = FateCode,
Names = maps:to_list(NamesMap),
Name = unicode:characters_to_binary(Entrypoint),
{Hash, Name} = lists:keyfind(Name, 2, Names),
{_, _, Code} = maps:get(Hash, Bodies),
% Generate the AACI, and get the AACI type info for the correct entrypoint.
AACI = hz_aaci:prepare_aaci(ACI),
{ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"),
{Code, Type}.
extract_return_value(#{0 := [{'RETURNR', {immediate, FATE}}]}) ->
FATE;
extract_return_value(Code) ->
erlang:exit({invalid_literal_fcode, Code}).
check_parser(Sophia) ->
% 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,
{ok, AACI} = hz_aaci:aaci_from_string(Source),
{ok, {_, Type}} = hz_aaci:get_function_signature(AACI, "f"),
{Code, Type} = compile_entrypoint_code_and_type(Source, "f"),
Fate = extract_return_value(Code),
% Also check that the FATE term is valid, by running it through gmb.
gmb_fate_encoding:serialize(Fate),
@@ -453,11 +474,11 @@ check_parser(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) ->
check_parser_with_typedef(Typedef, Sophia) ->
% 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"),
{Code, Type} = compile_entrypoint_code_and_type(Source, "f"),
Fate = extract_return_value(Code),
% Check the FATE term as usual.
gmb_fate_encoding:serialize(Fate),
@@ -467,40 +488,38 @@ check_parser_with_typedef(Typedef, Sophia, Fate) ->
check_sophia_to_fate(Type, Sophia, Fate).
int_test() ->
check_parser("123", 123).
check_parser("123").
list_test() ->
check_parser("[1, 2, 3]", [1, 2, 3]).
check_parser("[1, 2, 3]").
list_of_lists_test() ->
check_parser("[[], [1], [2, 3]]", [[], [1], [2, 3]]).
check_parser("[[], [1], [2, 3]]").
tuple_test() ->
check_parser("(1, [2, 3], (4, 5))", {tuple, {1, [2, 3], {tuple, {4, 5}}}}).
check_parser("(1, [2, 3], (4, 5))").
maps_test() ->
check_parser("{[1] = 2, [3] = 4}", #{1 => 2, 3 => 4}).
check_parser("{[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),
check_parser_with_typedef(TypeDef, Sophia),
% 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).
variant_test() ->
TypeDef = "datatype multi('a) = Zero | One('a) | Two('a, 'a)",
TestFn = fun(Sophia, Fate) ->
check_parser_with_typedef(TypeDef, Sophia, Fate),
{error, {unresolved_variant, _, _, _}} = parse_literal(unknown_type(), Sophia)
end,
TestFn("Zero", {variant, [0, 1, 2], 0, {}}),
TestFn("One(0)", {variant, [0, 1, 2], 1, {0}}),
TestFn("Two(0, 1)", {variant, [0, 1, 2], 2, {0, 1}}),
TestFn("Two([], [1, 2, 3])", {variant, [0, 1, 2], 2, {[], [1, 2, 3]}}),
check_parser_with_typedef(TypeDef, "Zero"),
check_parser_with_typedef(TypeDef, "One(0)"),
check_parser_with_typedef(TypeDef, "Two(0, 1)"),
check_parser_with_typedef(TypeDef, "Two([], [1, 2, 3])"),
{error, {unresolved_variant, _, _, _}} = parse_literal(unknown_type(), "Zero"),
ok.