Merge pull request #26 from aeternity/improved_roma_standalone

Improved roma standalone
This commit is contained in:
Hans Svensson 2019-02-12 14:52:12 +01:00 committed by GitHub
commit a1d33e93ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 267 additions and 158 deletions

View File

@ -21,8 +21,8 @@
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type option() :: pp_sophia_code | pp_ast | pp_icode | pp_assembler | -type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
pp_bytecode. pp_icode| pp_assembler | pp_bytecode.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
@ -38,34 +38,56 @@
version() -> version() ->
?COMPILER_VERSION. ?COMPILER_VERSION.
-spec file(string()) -> map(). -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
-spec file(string(), options()) -> map(). -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(Filename, Options) -> file(File, Options) ->
C = read_contract(Filename), case read_contract(File) of
from_string(C, Options). {ok, Bin} -> from_string(Bin, Options);
{error, Error} -> {error, {File, Error}}
end.
-spec from_string(string(), options()) -> map(). -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) -> from_string(ContractString, Options) ->
Ast = parse(ContractString, Options), try
ok = pp_sophia_code(Ast, Options), Ast = parse(ContractString, Options),
ok = pp_ast(Ast, Options), ok = pp_sophia_code(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options), ok = pp_ast(Ast, Options),
%% pp_types is handled inside aeso_ast_infer_types. TypedAst = aeso_ast_infer_types:infer(Ast, Options),
ok = pp_typed_ast(TypedAst, Options), %% pp_types is handled inside aeso_ast_infer_types.
ICode = to_icode(TypedAst, Options), ok = pp_typed_ast(TypedAst, Options),
TypeInfo = extract_type_info(ICode), ICode = to_icode(TypedAst, Options),
ok = pp_icode(ICode, Options), TypeInfo = extract_type_info(ICode),
Assembler = assemble(ICode, Options), ok = pp_icode(ICode, Options),
ok = pp_assembler(Assembler, Options), Assembler = assemble(ICode, Options),
ByteCodeList = to_bytecode(Assembler, Options), ok = pp_assembler(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCodeList = to_bytecode(Assembler, Options),
ok = pp_bytecode(ByteCode, Options), ByteCode = << << B:8 >> || B <- ByteCodeList >>,
#{byte_code => ByteCode, type_info => TypeInfo, ok = pp_bytecode(ByteCode, Options),
contract_source => ContractString, {ok, #{byte_code => ByteCode,
compiler_version => version()}. compiler_version => version(),
contract_source => ContractString,
type_info => TypeInfo
}}
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
@ -76,26 +98,36 @@ from_string(ContractString, Options) ->
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()} -spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
when Type :: term(). when Type :: term().
check_call(ContractString, Options) -> check_call(ContractString, Options) ->
Ast = parse(ContractString, Options), try
ok = pp_sophia_code(Ast, Options), Ast = parse(ContractString, Options),
ok = pp_ast(Ast, Options), ok = pp_sophia_code(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]), ok = pp_ast(Ast, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]),
ok = pp_typed_ast(TypedAst, Options), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
Icode = to_icode(TypedAst, Options), ok = pp_typed_ast(TypedAst, Options),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], Icode = to_icode(TypedAst, Options),
RetVMType = case RetType of ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
{id, _, "_"} -> any; RetVMType = case RetType of
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) {id, _, "_"} -> any;
end, _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
ok = pp_icode(Icode, Options), end,
#{ functions := Funs } = Icode, ok = pp_icode(Icode, Options),
ArgIcode = get_arg_icode(Funs), #{ functions := Funs } = Icode,
try [ icode_to_term(T, Arg) || {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ] of ArgIcode = get_arg_icode(Funs),
ArgTerms -> ArgTerms = [ icode_to_term(T, Arg) ||
{ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms} {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
catch throw:Err -> {ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms}
{error, Err} catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
-spec create_calldata(map(), string(), string()) -> -spec create_calldata(map(), string(), string()) ->
@ -113,7 +145,7 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
%% Function should be "foo : type", and %% Function should be "foo : type", and
%% Argument should be "Arg1, Arg2, .., ArgN" (no parens) %% Argument should be "Arg1, Arg2, .., ArgN" (no parens)
case string:lexemes(Function, ": ") of case string:lexemes(Function, ": ") of
%% If function is a single word fallback to old calldata generation %% If function is a single word fallback to old calldata generation
[FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument); [FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument);
[FunName | _] -> [FunName | _] ->
Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space
@ -247,11 +279,10 @@ parse_string(Text) ->
parse_error(Pos, ErrorString) parse_error(Pos, ErrorString)
end. end.
parse_error({Line,Pos}, ErrorString) -> parse_error({Line, Pos}, ErrorString) ->
Error = io_lib:format("line ~p, column ~p: ~s", [Line,Pos,ErrorString]), Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]),
error({parse_errors,[Error]}). error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
{ok, Bin} = file:read_file(Name), file:read_file(Name).
binary_to_list(Bin).

View File

@ -6,7 +6,13 @@
[ {src_file, undefined, undefined, string, "Sophia source code file"} [ {src_file, undefined, undefined, string, "Sophia source code file"}
, {verbose, $v, "verbose", undefined, "Verbose output"} , {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"} , {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]). , {create_calldata, $c, "create_calldata", string,
"Create calldata with respect to (compiled) contract in this file"}
, {create_calldata_fun, undefined, "calldata_fun", string,
"Deprecated calldata creation - using function + arguments - function"}
, {create_calldata_args, undefined, "calldata_args", string,
"Deprecated calldata creation - using function + arguments - arguments"}
, {outfile, $o, "out", string, "Output the result to file (experimental)"} ]).
usage() -> usage() ->
getopt:usage(?OPT_SPEC, "aesophia"). getopt:usage(?OPT_SPEC, "aesophia").
@ -14,11 +20,14 @@ usage() ->
main(Args) -> main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} -> {ok, {Opts, []}} ->
case proplists:get_value(help, Opts, false) of IsHelp = proplists:get_value(help, Opts, false),
false -> CreateCallData = proplists:get_value(create_calldata, Opts, undefined),
compile(Opts); if IsHelp ->
usage();
CreateCallData /= undefined ->
create_calldata(CreateCallData, Opts);
true -> true ->
usage() compile(Opts)
end; end;
{ok, {_, NonOpts}} -> {ok, {_, NonOpts}} ->
@ -44,28 +53,83 @@ compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false), Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined), OutFile = proplists:get_value(outfile, Opts, undefined),
try Res =
Res = aeso_compiler:file(File, [pp_ast || Verbose]), try aeso_compiler:file(File, [pp_ast || Verbose]) of
write_outfile(OutFile, Res), {ok, Map} ->
io:format("\nCompiled successfully!\n") io:format("\nCompiled successfully!\n"),
catch {ok, Map};
%% The compiler errors. {error, Reason} ->
error:{type_errors, Errors} -> io:format("\nError: ~p\n\n", [Reason]),
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]); {error, Reason}
error:{parse_errors, Errors} -> catch
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]); error:Error ->
error:{code_errors, Errors} -> Where = hd(erlang:get_stacktrace()),
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ], ErrorString = io_lib:format("Error: ~p in\n ~p", [Error, Where]),
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]); io:format("~s\n", [ErrorString]),
%% General programming errors in the compiler. {error, list_to_binary(lists:flatten(ErrorString))}
error:Error -> end,
Where = hd(erlang:get_stacktrace()), write_outfile(OutFile, Res).
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
io:format("\n~s\n", [ErrorString])
create_calldata(ContractFile, Opts) ->
case file:read_file(ContractFile) of
{ok, Bin} ->
try
Contract = binary_to_term(Bin),
create_calldata_(Contract, Opts)
catch _:_ ->
io:format("Error: Bad contract file ~s\n\n", [ContractFile]), usage()
end;
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [ContractFile]), usage()
end. end.
create_calldata_(Contract, Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined -> %% Check if old deprecated style is used
case {proplists:get_value(create_calldata_fun, Opts, undefined),
proplists:get_value(create_calldata_args, Opts, undefined)} of
{undefined, _} ->
io:format("Error: not enough create call data input\n\n"), usage();
{_, undefined} ->
io:format("Error: not enough create call data input\n\n"), usage();
{Fun, Args} ->
create_calldata(Contract, Fun, Args, Opts)
end;
CallFile ->
case file:read_file(CallFile) of
{ok, Bin} ->
create_calldata(Contract, "", binary_to_list(Bin), Opts);
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [CallFile]), usage()
end
end.
create_calldata(Contract, CallFun, CallArgs, Opts) ->
OutFile = proplists:get_value(outfile, Opts, undefined),
Res = try
case aeso_compiler:create_calldata(Contract, CallFun, CallArgs) of
{ok, CallData, _CallDataType, _OutputType} ->
io:format("Call data created successfully!\n"),
{ok, CallData};
Err = {error, Reason} ->
io:format("Error: Create calldata failed: ~p\n\n", [Reason]),
Err
end
catch
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error, Where]),
io:format("~s\n", [ErrorString]),
{error, list_to_binary(lists:flatten(ErrorString))}
end,
write_outfile(OutFile, Res).
write_outfile(undefined, _) -> ok; write_outfile(undefined, _) -> ok;
write_outfile(Out, ResMap) -> write_outfile(Out, Res) ->
%% Lazy approach %% Lazy approach
file:write_file(Out, term_to_binary(ResMap)), file:write_file(Out, term_to_binary(Res)),
io:format("Output written to: ~s\n", [Out]). io:format("Output written to: ~s\n\n", [Out]).

View File

@ -28,13 +28,15 @@ simple_compile_test_() ->
end} || ContractName <- compilable_contracts() ] ++ end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
{type_errors, Errors} = compile(ContractName), <<"Type errors\n",ErrorString/binary>> = compile(ContractName),
check_errors(lists:sort(ExpectedErrors), lists:sort(Errors)) check_errors(lists:sort(ExpectedErrors), ErrorString)
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] {ContractName, ExpectedErrors} <- failing_contracts() ]
}. }.
check_errors(Expect, Actual) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@ -42,10 +44,10 @@ check_errors(Expect, Actual) ->
end. end.
compile(Name) -> compile(Name) ->
try String = aeso_test_utils:read_contract(Name),
aeso_compiler:from_string(aeso_test_utils:read_contract(Name), []) case aeso_compiler:from_string(String, []) of
catch _:{type_errors, _} = E -> {ok,Map} -> Map;
E {error,ErrorString} -> ErrorString
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@ -75,82 +77,94 @@ compilable_contracts() ->
failing_contracts() -> failing_contracts() ->
[ {"name_clash", [ {"name_clash",
["Duplicate definitions of abort at\n - (builtin location)\n - line 14, column 3\n", [<<"Duplicate definitions of abort at\n"
"Duplicate definitions of double_def at\n - line 10, column 3\n - line 11, column 3\n", " - (builtin location)\n"
"Duplicate definitions of double_proto at\n - line 4, column 3\n - line 5, column 3\n", " - line 14, column 3">>,
"Duplicate definitions of proto_and_def at\n - line 7, column 3\n - line 8, column 3\n", <<"Duplicate definitions of double_def at\n"
"Duplicate definitions of put at\n - (builtin location)\n - line 15, column 3\n", " - line 10, column 3\n"
"Duplicate definitions of state at\n - (builtin location)\n - line 16, column 3\n"]} " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<"Duplicate definitions of put at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of state at\n"
" - (builtin location)\n"
" - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
["Unbound variable zz at line 17, column 21\n", [<<"Unbound variable zz at line 17, column 21">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int\n", " x : int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 46)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)\n", " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 45\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string\n", " string">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 50\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 56\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)\n", " - z : string (at line 39, column 10)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 38)\n", "arising from the projection of the field y (at line 22, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 42)\n", "arising from an assignment of the field y (at line 21, column 42)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 38)\n", "arising from an assignment of the field y (at line 20, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 35)\n", "arising from an assignment of the field y (at line 19, column 35)">>,
"Ambiguous record type with field y (at line 13, column 25) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)\n", " - r' (at line 5, column 10)">>,
"Record type r2 does not have field y (at line 15, column 22)\n", <<"Record type r2 does not have field y (at line 15, column 22)">>,
"The field z is missing when constructing an element of type r2 (at line 15, column 24)\n", <<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
"Repeated name x in pattern\n" <<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)\n", " x :: x (at line 26, column 7)">>,
"No record type with fields y, z (at line 14, column 22)\n"]} <<"No record type with fields y, z (at line 14, column 22)">>]}
, {"init_type_error", , {"init_type_error",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and ()\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
["The field x is missing when constructing an element of type r('a) (at line 7, column 40)\n", [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
"The field y is missing when constructing an element of type r(int) (at line 8, column 40)\n", <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)\n"]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
]. ].