diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 26372a8..233cbec 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -14,6 +14,8 @@ , contract_interface/2 , contract_interface/3 + , from_typed_ast/2 + , render_aci_json/1 , json_encode_expr/1 @@ -23,6 +25,8 @@ -type json() :: jsx:json_term(). -type json_text() :: binary(). +-export_type([aci_type/0]). + %% External API -spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}. file(Type, File) -> @@ -65,18 +69,20 @@ do_contract_interface(Type, ContractString, Options) -> try Ast = aeso_compiler:parse(ContractString, Options), %% io:format("~p\n", [Ast]), - TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), + {TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold]), %% io:format("~p\n", [TypedAst]), - JArray = [ encode_contract(C) || C <- TypedAst ], - - case Type of - json -> {ok, JArray}; - string -> do_render_aci_json(JArray) - end + from_typed_ast(Type, TypedAst) catch throw:{error, Errors} -> {error, Errors} end. +from_typed_ast(Type, TypedAst) -> + JArray = [ encode_contract(C) || C <- TypedAst ], + case Type of + json -> {ok, JArray}; + string -> do_render_aci_json(JArray) + end. + encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> C0 = #{name => encode_name(Name)}, diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index d85b3f6..bb09980 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -553,7 +553,7 @@ global_env() -> option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. --spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. +-spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}. infer(Contracts) -> infer(Contracts, []). @@ -563,7 +563,7 @@ infer(Contracts) -> init_env(_Options) -> global_env(). -spec infer(aeso_syntax:ast(), list(option())) -> - aeso_syntax:ast() | {env(), aeso_syntax:ast()}. + {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}. infer([], Options) -> create_type_errors(), type_error({no_decls, proplists:get_value(src_file, Options, no_file)}), @@ -576,15 +576,15 @@ infer(Contracts, Options) -> ets_new(type_vars, [set]), check_modifiers(Env, Contracts), {Env1, Decls} = infer1(Env, Contracts, [], Options), - {Env2, Decls2} = + {Env2, DeclsFolded, DeclsUnfolded} = case proplists:get_value(dont_unfold, Options, false) of - true -> {Env1, Decls}; + true -> {Env1, Decls, Decls}; false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), - {E, unfold_record_types(E, Decls)} + {E, Decls, unfold_record_types(E, Decls)} end, case proplists:get_value(return_env, Options, false) of - false -> Decls2; - true -> {Env2, Decls2} + false -> {DeclsFolded, DeclsUnfolded}; + true -> {Env2, DeclsFolded, DeclsUnfolded} end after clean_up_ets() diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 64f3f98..5f3a2cb 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -42,7 +42,8 @@ | {backend, aevm | fate} | {include, {file_system, [string()]} | {explicit_files, #{string() => binary()}}} - | {src_file, string()}. + | {src_file, string()} + | {aci, aeso_aci:aci_type()}. -type options() :: [option()]. -export_type([ option/0 @@ -116,7 +117,8 @@ from_string(Backend, ContractString, Options) -> end. from_string1(aevm, ContractString, Options) -> - #{icode := Icode} = string_to_code(ContractString, Options), + #{ icode := Icode + , folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options), TypeInfo = extract_type_info(Icode), Assembler = assemble(Icode, Options), pp_assembler(aevm, Assembler, Options), @@ -124,47 +126,61 @@ from_string1(aevm, ContractString, Options) -> ByteCode = << << B:8 >> || B <- ByteCodeList >>, pp_bytecode(ByteCode, Options), {ok, Version} = version(), - {ok, #{byte_code => ByteCode, - compiler_version => Version, - contract_source => ContractString, - type_info => TypeInfo, - abi_version => aeb_aevm_abi:abi_version(), - payable => maps:get(payable, Icode) - }}; + Res = #{byte_code => ByteCode, + compiler_version => Version, + contract_source => ContractString, + type_info => TypeInfo, + abi_version => aeb_aevm_abi:abi_version(), + payable => maps:get(payable, Icode) + }, + {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}; from_string1(fate, ContractString, Options) -> - #{fcode := FCode} = string_to_code(ContractString, Options), + #{ fcode := FCode + , folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options), FateCode = aeso_fcode_to_fate:compile(FCode, Options), pp_assembler(fate, FateCode, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = version(), - {ok, #{byte_code => ByteCode, - compiler_version => Version, - contract_source => ContractString, - type_info => [], - fate_code => FateCode, - abi_version => aeb_fate_abi:abi_version(), - payable => maps:get(payable, FCode) - }}. + Res = #{byte_code => ByteCode, + compiler_version => Version, + contract_source => ContractString, + type_info => [], + fate_code => FateCode, + abi_version => aeb_fate_abi:abi_version(), + payable => maps:get(payable, FCode) + }, + {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}. + +maybe_generate_aci(Result, FoldedTypedAst, Options) -> + case proplists:get_value(aci, Options) of + undefined -> + Result; + Type -> + {ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst), + maps:put(aci, Aci, Result) + end. -spec string_to_code(string(), options()) -> map(). string_to_code(ContractString, Options) -> Ast = parse(ContractString, Options), pp_sophia_code(Ast, Options), pp_ast(Ast, Options), - {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]), - pp_typed_ast(TypedAst, Options), + {TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]), + pp_typed_ast(UnfoldedTypedAst, Options), case proplists:get_value(backend, Options, aevm) of aevm -> - Icode = ast_to_icode(TypedAst, Options), + Icode = ast_to_icode(UnfoldedTypedAst, Options), pp_icode(Icode, Options), #{ icode => Icode - , typed_ast => TypedAst + , unfolded_typed_ast => UnfoldedTypedAst + , folded_typed_ast => FoldedTypedAst , type_env => TypeEnv , ast => Ast }; fate -> - Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options), + Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options), #{ fcode => Fcode - , typed_ast => TypedAst + , unfolded_typed_ast => UnfoldedTypedAst + , folded_typed_ast => FoldedTypedAst , type_env => TypeEnv , ast => Ast } end. @@ -203,7 +219,7 @@ check_call1(ContractString0, FunName, Args, Options) -> %% First check the contract without the __call function #{ast := Ast} = string_to_code(ContractString0, Options), ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args), - #{typed_ast := TypedAst, + #{unfolded_typed_ast := TypedAst, icode := Icode} = string_to_code(ContractString, Options), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], @@ -313,7 +329,7 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, _, Type0} = get_decode_type(FunName, TypedAst), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), @@ -389,7 +405,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) -> Options = [no_code | Options0], try Code = string_to_code(ContractString, Options), - #{ typed_ast := TypedAst, type_env := TypeEnv} = Code, + #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code, {ok, Args, _} = get_decode_type(FunName, TypedAst), GetType = fun({typed, _, _, T}) -> T; (T) -> T end, diff --git a/test/aeso_aci_tests.erl b/test/aeso_aci_tests.erl index d5010e3..c851afb 100644 --- a/test/aeso_aci_tests.erl +++ b/test/aeso_aci_tests.erl @@ -11,7 +11,10 @@ test_contract(N) -> {Contract,MapACI,DecACI} = test_cases(N), {ok,JSON} = aeso_aci:contract_interface(json, Contract), ?assertEqual([MapACI], JSON), - ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). + ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)), + %% Check if the compiler provides correct aci + {ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]), + ?assertEqual(JSON, JSON2). test_cases(1) -> Contract = <<"payable contract C =\n" @@ -84,7 +87,7 @@ test_cases(3) -> " entrypoint a : (C.bert(string)) => int\n">>, {Contract,MapACI,DecACI}. -%% Rounttrip +%% Roundtrip aci_test_() -> [{"Testing ACI generation for " ++ ContractName, fun() -> aci_test_contract(ContractName) end} @@ -96,6 +99,8 @@ aci_test_contract(Name) -> String = aeso_test_utils:read_contract(Name), Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}], {ok, JSON} = aeso_aci:contract_interface(json, String, Opts), + {ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]), + ?assertEqual(JSON, JSON1), io:format("JSON:\n~p\n", [JSON]), {ok, ContractStub} = aeso_aci:render_aci_json(JSON), @@ -122,4 +127,3 @@ check_stub(Stub, Options) -> _ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ], error({parse_errors, Errs}) end. -