diff --git a/src/hz_aaci.erl b/src/hz_aaci.erl index dfd158c..f9da33f 100644 --- a/src/hz_aaci.erl +++ b/src/hz_aaci.erl @@ -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 diff --git a/src/hz_sophia.erl b/src/hz_sophia.erl index d63d1be..a158ba8 100644 --- a/src/hz_sophia.erl +++ b/src/hz_sophia.erl @@ -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.