Merge pull request #25 from aeternity/PT-156466783-namespaces
PT-156466783 namespaces
This commit is contained in:
commit
202a06a580
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ _build
|
|||||||
rebar3.crashdump
|
rebar3.crashdump
|
||||||
*.erl~
|
*.erl~
|
||||||
*.aes~
|
*.aes~
|
||||||
|
aesophia
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -17,11 +17,19 @@
|
|||||||
|
|
||||||
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
||||||
convert_typed(TypedTree, Options) ->
|
convert_typed(TypedTree, Options) ->
|
||||||
code(TypedTree, aeso_icode:new(Options)).
|
Name = case lists:last(TypedTree) of
|
||||||
|
{contract, _, {con, _, Con}, _} -> Con;
|
||||||
|
_ -> gen_error(last_declaration_must_be_contract)
|
||||||
|
end,
|
||||||
|
Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))),
|
||||||
|
deadcode_elimination(Icode).
|
||||||
|
|
||||||
code([{contract, _Attribs, {con, _, Name}, Code}|Rest], Icode) ->
|
code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
|
||||||
NewIcode = contract_to_icode(Code,
|
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
|
||||||
aeso_icode:set_name(Name, Icode)),
|
code(Rest, NewIcode);
|
||||||
|
code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
|
||||||
|
%% TODO: nested namespaces
|
||||||
|
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)),
|
||||||
code(Rest, NewIcode);
|
code(Rest, NewIcode);
|
||||||
code([], Icode) ->
|
code([], Icode) ->
|
||||||
add_default_init_function(add_builtins(Icode)).
|
add_default_init_function(add_builtins(Icode)).
|
||||||
@ -33,30 +41,38 @@ gen_error(Error) ->
|
|||||||
|
|
||||||
%% Create default init function (only if state is unit).
|
%% Create default init function (only if state is unit).
|
||||||
add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
|
add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
|
||||||
case lists:keymember("init", 1, Funs) of
|
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
|
||||||
|
case lists:keymember(QInit, 1, Funs) of
|
||||||
true -> Icode;
|
true -> Icode;
|
||||||
false when State /= {tuple, []} -> gen_error(missing_init_function);
|
false when State /= {tuple, []} ->
|
||||||
|
gen_error(missing_init_function);
|
||||||
false ->
|
false ->
|
||||||
Type = {tuple, [typerep, {tuple, []}]},
|
Type = {tuple, [typerep, {tuple, []}]},
|
||||||
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
|
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
|
||||||
DefaultInit = {"init", [], [], Value, Type},
|
DefaultInit = {QInit, [], [], Value, Type},
|
||||||
Icode#{ functions => [DefaultInit | Funs] }
|
Icode#{ functions => [DefaultInit | Funs] }
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
|
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
|
||||||
aeso_icode:icode().
|
aeso_icode:icode().
|
||||||
contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
|
contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
|
||||||
|
NS = aeso_icode:get_namespace(Icode),
|
||||||
|
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
|
||||||
|
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
|
||||||
|
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
|
||||||
Icode = #{ types := Types, constructors := Constructors }) ->
|
Icode = #{ types := Types, constructors := Constructors }) ->
|
||||||
TypeDef = make_type_def(Args, Def, Icode),
|
TypeDef = make_type_def(Args, Def, Icode),
|
||||||
NewConstructors =
|
NewConstructors =
|
||||||
case Def of
|
case Def of
|
||||||
{variant_t, Cons} ->
|
{variant_t, Cons} ->
|
||||||
Tags = lists:seq(0, length(Cons) - 1),
|
Tags = lists:seq(0, length(Cons) - 1),
|
||||||
GetName = fun({constr_t, _, {con, _, C}, _}) -> C end,
|
GetName = fun({constr_t, _, C, _}) -> C end,
|
||||||
maps:from_list([ {GetName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
|
QName = fun(Con) -> {_, _, Xs} = aeso_icode:qualify(GetName(Con), Icode), Xs end,
|
||||||
|
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
|
||||||
_ -> #{}
|
_ -> #{}
|
||||||
end,
|
end,
|
||||||
Icode1 = Icode#{ types := Types#{ Name => TypeDef },
|
{_, _, TName} = aeso_icode:qualify(Id, Icode),
|
||||||
|
Icode1 = Icode#{ types := Types#{ TName => TypeDef },
|
||||||
constructors := maps:merge(Constructors, NewConstructors) },
|
constructors := maps:merge(Constructors, NewConstructors) },
|
||||||
Icode2 = case Name of
|
Icode2 = case Name of
|
||||||
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
|
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
|
||||||
@ -68,8 +84,7 @@ contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
|
|||||||
contract_to_icode(Rest, Icode2);
|
contract_to_icode(Rest, Icode2);
|
||||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
||||||
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
||||||
[ private || proplists:get_value(private, Attrib, false) orelse
|
[ private || is_private(Attrib, Icode) ],
|
||||||
proplists:get_value(internal, Attrib, false) ],
|
|
||||||
%% TODO: Handle types
|
%% TODO: Handle types
|
||||||
FunName = ast_id(Name),
|
FunName = ast_id(Name),
|
||||||
%% TODO: push funname to env
|
%% TODO: push funname to env
|
||||||
@ -84,7 +99,8 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
|
|||||||
{tuple, [typerep, ast_typerep(T, Icode)]}};
|
{tuple, [typerep, ast_typerep(T, Icode)]}};
|
||||||
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
|
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
|
||||||
end,
|
end,
|
||||||
NewIcode = ast_fun_to_icode(FunName, FunAttrs, FunArgs, FunBody, TypeRep, Icode),
|
QName = aeso_icode:qualify(Name, Icode),
|
||||||
|
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
|
||||||
contract_to_icode(Rest, NewIcode);
|
contract_to_icode(Rest, NewIcode);
|
||||||
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
|
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
|
||||||
%% OBS! This code ignores the letrec structure of the source,
|
%% OBS! This code ignores the letrec structure of the source,
|
||||||
@ -94,11 +110,14 @@ contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
|
|||||||
%% just to parse a list of (mutually recursive) definitions.
|
%% just to parse a list of (mutually recursive) definitions.
|
||||||
contract_to_icode(Defs++Rest, Icode);
|
contract_to_icode(Defs++Rest, Icode);
|
||||||
contract_to_icode([], Icode) -> Icode;
|
contract_to_icode([], Icode) -> Icode;
|
||||||
contract_to_icode(_Code, Icode) ->
|
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
|
||||||
%% TODO debug output for debug("Unhandled code ~p~n",[Code]),
|
contract_to_icode(Code, Icode);
|
||||||
Icode.
|
contract_to_icode([Decl | Code], Icode) ->
|
||||||
|
io:format("Unhandled declaration: ~p\n", [Decl]),
|
||||||
|
contract_to_icode(Code, Icode).
|
||||||
|
|
||||||
ast_id({id, _, Id}) -> Id.
|
ast_id({id, _, Id}) -> Id;
|
||||||
|
ast_id({qid, _, Id}) -> Id.
|
||||||
|
|
||||||
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
|
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
|
||||||
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
|
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
|
||||||
@ -121,7 +140,7 @@ ast_type(T, Icode) ->
|
|||||||
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
|
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
|
||||||
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
|
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
|
||||||
|
|
||||||
ast_body(?qid_app(["Chain","event"], [Event], _, _), Icode) ->
|
ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) ->
|
||||||
aeso_builtins:check_event_type(Icode),
|
aeso_builtins:check_event_type(Icode),
|
||||||
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
|
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
|
||||||
|
|
||||||
@ -152,10 +171,10 @@ ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
|
|||||||
gen_error({underapplied_primitive, 'Chain.spend'});
|
gen_error({underapplied_primitive, 'Chain.spend'});
|
||||||
|
|
||||||
%% State
|
%% State
|
||||||
ast_body({id, _, "state"}, _Icode) -> prim_state;
|
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
|
||||||
ast_body(?id_app("put", [NewState], _, _), Icode) ->
|
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
|
||||||
#prim_put{ state = ast_body(NewState, Icode) };
|
#prim_put{ state = ast_body(NewState, Icode) };
|
||||||
ast_body({id, _, "put"}, _Icode) ->
|
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
|
||||||
gen_error({underapplied_primitive, put}); %% TODO: eta
|
gen_error({underapplied_primitive, put}); %% TODO: eta
|
||||||
|
|
||||||
%% Abort
|
%% Abort
|
||||||
@ -384,7 +403,8 @@ ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
|
|||||||
|
|
||||||
%% Other terms
|
%% Other terms
|
||||||
ast_body({id, _, Name}, _Icode) ->
|
ast_body({id, _, Name}, _Icode) ->
|
||||||
%% TODO Look up id in env
|
#var_ref{name = Name};
|
||||||
|
ast_body({qid, _, Name}, _Icode) ->
|
||||||
#var_ref{name = Name};
|
#var_ref{name = Name};
|
||||||
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
|
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
|
||||||
Value = if Bool -> 1 ; true -> 0 end,
|
Value = if Bool -> 1 ; true -> 0 end,
|
||||||
@ -441,9 +461,15 @@ ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode)
|
|||||||
string:join([Contract, FunName], ".")});
|
string:join([Contract, FunName], ".")});
|
||||||
|
|
||||||
ast_body({con, _, Name}, Icode) ->
|
ast_body({con, _, Name}, Icode) ->
|
||||||
|
Tag = aeso_icode:get_constructor_tag([Name], Icode),
|
||||||
|
#tuple{cpts = [#integer{value = Tag}]};
|
||||||
|
ast_body({qcon, _, Name}, Icode) ->
|
||||||
Tag = aeso_icode:get_constructor_tag(Name, Icode),
|
Tag = aeso_icode:get_constructor_tag(Name, Icode),
|
||||||
#tuple{cpts = [#integer{value = Tag}]};
|
#tuple{cpts = [#integer{value = Tag}]};
|
||||||
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
|
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
|
||||||
|
Tag = aeso_icode:get_constructor_tag([Name], Icode),
|
||||||
|
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
|
||||||
|
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
|
||||||
Tag = aeso_icode:get_constructor_tag(Name, Icode),
|
Tag = aeso_icode:get_constructor_tag(Name, Icode),
|
||||||
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
|
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
|
||||||
ast_body({app,As,Fun,Args}, Icode) ->
|
ast_body({app,As,Fun,Args}, Icode) ->
|
||||||
@ -745,6 +771,15 @@ has_maps({list, T}) -> has_maps(T);
|
|||||||
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
|
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
|
||||||
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
|
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
|
||||||
|
|
||||||
|
%% A function is private if marked 'private' or 'internal', or if it's not
|
||||||
|
%% defined in the main contract name space. (NOTE: changes when we introduce
|
||||||
|
%% inheritance).
|
||||||
|
is_private(Ann, #{ contract_name := MainContract } = Icode) ->
|
||||||
|
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
|
||||||
|
proplists:get_value(private, Ann, false) orelse
|
||||||
|
proplists:get_value(internal, Ann, false) orelse
|
||||||
|
MainContract /= CurrentNamespace.
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
%% Builtins
|
%% Builtins
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
@ -756,3 +791,39 @@ builtin_call(Builtin, Args) ->
|
|||||||
add_builtins(Icode = #{functions := Funs}) ->
|
add_builtins(Icode = #{functions := Funs}) ->
|
||||||
Builtins = aeso_builtins:used_builtins(Funs),
|
Builtins = aeso_builtins:used_builtins(Funs),
|
||||||
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}.
|
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}.
|
||||||
|
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% Deadcode elimination
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
|
deadcode_elimination(Icode = #{ functions := Funs }) ->
|
||||||
|
PublicNames = [ Name || {Name, Ann, _, _, _} <- Funs, not lists:member(private, Ann) ],
|
||||||
|
ArgsToPat = fun(Args) -> [ #var_ref{ name = X } || {X, _} <- Args ] end,
|
||||||
|
Defs = maps:from_list([ {Name, {binder, ArgsToPat(Args), Body}} || {Name, _, Args, Body, _} <- Funs ]),
|
||||||
|
UsedNames = chase_names(Defs, PublicNames, #{}),
|
||||||
|
UsedFuns = [ Def || Def = {Name, _, _, _, _} <- Funs, maps:is_key(Name, UsedNames) ],
|
||||||
|
Icode#{ functions := UsedFuns }.
|
||||||
|
|
||||||
|
chase_names(_Defs, [], Used) -> Used;
|
||||||
|
chase_names(Defs, [X | Xs], Used) ->
|
||||||
|
%% can happen when compiling __call contracts
|
||||||
|
case maps:is_key(X, Used) orelse not maps:is_key(X, Defs) of
|
||||||
|
true -> chase_names(Defs, Xs, Used); %% already chased
|
||||||
|
false ->
|
||||||
|
Def = maps:get(X, Defs),
|
||||||
|
Vars = maps:keys(free_vars(Def)),
|
||||||
|
chase_names(Defs, Vars ++ Xs, Used#{ X => true })
|
||||||
|
end.
|
||||||
|
|
||||||
|
free_vars(#var_ref{ name = X }) -> #{ X => true };
|
||||||
|
free_vars(#arg{ name = X }) -> #{ X => true };
|
||||||
|
free_vars({binder, Pat, Body}) ->
|
||||||
|
maps:without(maps:keys(free_vars(Pat)), free_vars(Body));
|
||||||
|
free_vars(#switch{ expr = E, cases = Cases }) ->
|
||||||
|
free_vars([E | [{binder, P, B} || {P, B} <- Cases]]);
|
||||||
|
free_vars(#lambda{ args = Xs, body = E }) ->
|
||||||
|
free_vars({binder, Xs, E});
|
||||||
|
free_vars(T) when is_tuple(T) -> free_vars(tuple_to_list(T));
|
||||||
|
free_vars([H | T]) -> maps:merge(free_vars(H), free_vars(T));
|
||||||
|
free_vars(_) -> #{}.
|
||||||
|
@ -106,21 +106,22 @@ check_event_type(Icode) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
check_event_type(Evts, Icode) ->
|
check_event_type(Evts, Icode) ->
|
||||||
[ check_event_type(Name, T, Icode)
|
[ check_event_type(Name, Ix, T, Icode)
|
||||||
|| {constr_t, _, {con, _, Name}, Types} <- Evts, T <- Types ].
|
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
|
||||||
|
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
|
||||||
|
|
||||||
check_event_type(EvtName, Type, Icode) ->
|
check_event_type(EvtName, Ix, Type, Icode) ->
|
||||||
VMType =
|
VMType =
|
||||||
try
|
try
|
||||||
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
||||||
catch _:_ ->
|
catch _:_ ->
|
||||||
error({EvtName, could_not_resolve_type, Type})
|
error({EvtName, could_not_resolve_type, Type})
|
||||||
end,
|
end,
|
||||||
case aeso_syntax:get_ann(indexed, Type, false) of
|
case {Ix, VMType} of
|
||||||
true when VMType == word -> ok;
|
{indexed, word} -> ok;
|
||||||
false when VMType == string -> ok;
|
{notindexed, string} -> ok;
|
||||||
true -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
{indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
||||||
false -> error({EvtName, payload_should_be_string, is, VMType})
|
{notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
bfun(B, {IArgs, IExpr, IRet}) ->
|
bfun(B, {IArgs, IExpr, IRet}) ->
|
||||||
@ -169,16 +170,15 @@ builtin_event(EventT) ->
|
|||||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||||
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
||||||
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
||||||
IsIndexed = fun(T) -> aeso_syntax:get_ann(indexed, T, false) end,
|
|
||||||
Payload = %% Should put data ptr, length on stack.
|
Payload = %% Should put data ptr, length on stack.
|
||||||
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
||||||
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
||||||
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
|
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
|
||||||
end,
|
end,
|
||||||
Clause =
|
Clause =
|
||||||
fun(_Tag, {con, _, Con}, Types) ->
|
fun(_Tag, {con, _, Con}, IxTypes) ->
|
||||||
Indexed = [ Var || {Var, Type} <- lists:zip(ArgPats(Types), Types),
|
Types = [ T || {_Ix, T} <- IxTypes ],
|
||||||
IsIndexed(Type) ],
|
Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||||
EvtIndex = {unop, 'sha3', str_to_icode(Con)},
|
EvtIndex = {unop, 'sha3', str_to_icode(Con)},
|
||||||
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
|
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
|
||||||
end,
|
end,
|
||||||
@ -189,8 +189,8 @@ builtin_event(EventT) ->
|
|||||||
|
|
||||||
{[{"e", event}],
|
{[{"e", event}],
|
||||||
{switch, v(e),
|
{switch, v(e),
|
||||||
[{Pat(Tag, Types), Clause(Tag, Con, Types)}
|
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|
||||||
|| {Tag, {constr_t, _, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
||||||
{tuple, []}}.
|
{tuple, []}}.
|
||||||
|
|
||||||
%% Abort primitive.
|
%% Abort primitive.
|
||||||
|
@ -21,8 +21,16 @@
|
|||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
|
|
||||||
|
|
||||||
-type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
|
-type option() :: pp_sophia_code
|
||||||
pp_icode| pp_assembler | pp_bytecode.
|
| pp_ast
|
||||||
|
| pp_types
|
||||||
|
| pp_typed_ast
|
||||||
|
| pp_icode
|
||||||
|
| pp_assembler
|
||||||
|
| pp_bytecode
|
||||||
|
| {include, {file_system, [string()]} |
|
||||||
|
{explicit_files, #{string() => binary()}}}
|
||||||
|
| {src_file, string()}.
|
||||||
-type options() :: [option()].
|
-type options() :: [option()].
|
||||||
|
|
||||||
-export_type([ option/0
|
-export_type([ option/0
|
||||||
@ -40,12 +48,14 @@ version() ->
|
|||||||
|
|
||||||
-spec file(string()) -> {ok, map()} | {error, binary()}.
|
-spec file(string()) -> {ok, map()} | {error, binary()}.
|
||||||
file(Filename) ->
|
file(Filename) ->
|
||||||
file(Filename, []).
|
Dir = filename:dirname(Filename),
|
||||||
|
{ok, Cwd} = file:get_cwd(),
|
||||||
|
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
|
||||||
|
|
||||||
-spec file(string(), options()) -> {ok, map()} | {error, binary()}.
|
-spec file(string(), options()) -> {ok, map()} | {error, binary()}.
|
||||||
file(File, Options) ->
|
file(File, Options) ->
|
||||||
case read_contract(File) of
|
case read_contract(File) of
|
||||||
{ok, Bin} -> from_string(Bin, Options);
|
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
ErrorString = [File,": ",file:format_error(Error)],
|
ErrorString = [File,": ",file:format_error(Error)],
|
||||||
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
|
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
|
||||||
@ -152,7 +162,7 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
|
|||||||
[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
|
||||||
CallContract = lists:flatten(
|
CallContract = lists:flatten(
|
||||||
[ "contract Call =\n"
|
[ "contract MakeCall =\n"
|
||||||
, " function ", Function, "\n"
|
, " function ", Function, "\n"
|
||||||
, " function __call() = ", FunName, "(", Args, ")"
|
, " function __call() = ", FunName, "(", Args, ")"
|
||||||
]),
|
]),
|
||||||
@ -161,15 +171,17 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) ->
|
|||||||
|
|
||||||
|
|
||||||
get_arg_icode(Funs) ->
|
get_arg_icode(Funs) ->
|
||||||
[Args] = [ Args || {?CALL_NAME, _, _, {funcall, _, Args}, _} <- Funs ],
|
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||||
Args.
|
[Args] -> Args;
|
||||||
|
[] -> error({missing_call_function, Funs})
|
||||||
|
end.
|
||||||
|
|
||||||
get_call_type([{contract, _, _, Defs}]) ->
|
get_call_type([{contract, _, _, Defs}]) ->
|
||||||
case [ {FunName, FunType}
|
case [ {lists:last(QFunName), FunType}
|
||||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||||
{typed, _,
|
{typed, _,
|
||||||
{app, _,
|
{app, _,
|
||||||
{typed, _, {id, _, FunName}, FunType}, _}, _}} <- Defs ] of
|
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
|
||||||
[Call] -> {ok, Call};
|
[Call] -> {ok, Call};
|
||||||
[] -> {error, missing_call_function}
|
[] -> {error, missing_call_function}
|
||||||
end;
|
end;
|
||||||
@ -211,9 +223,6 @@ icode_to_term(T, V) ->
|
|||||||
icodes_to_terms(Ts, Vs) ->
|
icodes_to_terms(Ts, Vs) ->
|
||||||
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
|
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
|
||||||
|
|
||||||
parse(C,_Options) ->
|
|
||||||
parse_string(C).
|
|
||||||
|
|
||||||
to_icode(TypedAst, Options) ->
|
to_icode(TypedAst, Options) ->
|
||||||
aeso_ast_to_icode:convert_typed(TypedAst, Options).
|
aeso_ast_to_icode:convert_typed(TypedAst, Options).
|
||||||
|
|
||||||
@ -228,7 +237,7 @@ to_bytecode([Op|Rest], Options) ->
|
|||||||
to_bytecode([], _) -> [].
|
to_bytecode([], _) -> [].
|
||||||
|
|
||||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||||
TypeInfo = [aeso_abi:function_type_info(list_to_binary(Name), Args, TypeRep)
|
TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)), Args, TypeRep)
|
||||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||||
not is_tuple(Name),
|
not is_tuple(Name),
|
||||||
not lists:member(private, Attrs)
|
not lists:member(private, Attrs)
|
||||||
@ -263,9 +272,9 @@ sophia_type_to_typerep(String) ->
|
|||||||
catch _:_ -> {error, bad_type}
|
catch _:_ -> {error, bad_type}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_string(Text) ->
|
parse(Text, Options) ->
|
||||||
%% Try and return something sensible here!
|
%% Try and return something sensible here!
|
||||||
case aeso_parser:string(Text) of
|
case aeso_parser:string(Text, Options) of
|
||||||
%% Yay, it worked!
|
%% Yay, it worked!
|
||||||
{ok, Contract} -> Contract;
|
{ok, Contract} -> Contract;
|
||||||
%% Scan errors.
|
%% Scan errors.
|
||||||
@ -278,12 +287,23 @@ parse_string(Text) ->
|
|||||||
parse_error(Pos, Error);
|
parse_error(Pos, Error);
|
||||||
{error, {Pos, ambiguous_parse, As}} ->
|
{error, {Pos, ambiguous_parse, As}} ->
|
||||||
ErrorString = io_lib:format("Ambiguous ~p", [As]),
|
ErrorString = io_lib:format("Ambiguous ~p", [As]),
|
||||||
parse_error(Pos, ErrorString)
|
parse_error(Pos, ErrorString);
|
||||||
|
%% Include error
|
||||||
|
{error, {Pos, include_error, File}} ->
|
||||||
|
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
parse_error({Line, Pos}, ErrorString) ->
|
parse_error(Pos, ErrorString) ->
|
||||||
Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]),
|
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
||||||
error({parse_errors, [Error]}).
|
error({parse_errors, [Error]}).
|
||||||
|
|
||||||
read_contract(Name) ->
|
read_contract(Name) ->
|
||||||
file:read_file(Name).
|
file:read_file(Name).
|
||||||
|
|
||||||
|
pos_error({Line, Pos}) ->
|
||||||
|
io_lib:format("line ~p, column ~p", [Line, Pos]);
|
||||||
|
pos_error({no_file, Line, Pos}) ->
|
||||||
|
pos_error({Line, Pos});
|
||||||
|
pos_error({File, Line, Pos}) ->
|
||||||
|
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
|
||||||
|
|
||||||
|
@ -9,7 +9,18 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-module(aeso_icode).
|
-module(aeso_icode).
|
||||||
|
|
||||||
-export([new/1, pp/1, set_name/2, set_functions/2, map_typerep/2, option_typerep/1, get_constructor_tag/2]).
|
-export([new/1,
|
||||||
|
pp/1,
|
||||||
|
set_name/2,
|
||||||
|
set_namespace/2,
|
||||||
|
enter_namespace/2,
|
||||||
|
get_namespace/1,
|
||||||
|
qualify/2,
|
||||||
|
set_functions/2,
|
||||||
|
map_typerep/2,
|
||||||
|
option_typerep/1,
|
||||||
|
get_constructor_tag/2]).
|
||||||
|
|
||||||
-export_type([icode/0]).
|
-export_type([icode/0]).
|
||||||
|
|
||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
@ -29,12 +40,13 @@
|
|||||||
|
|
||||||
-type icode() :: #{ contract_name => string()
|
-type icode() :: #{ contract_name => string()
|
||||||
, functions => [fun_dec()]
|
, functions => [fun_dec()]
|
||||||
|
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||||
, env => [bindings()]
|
, env => [bindings()]
|
||||||
, state_type => aeso_sophia:type()
|
, state_type => aeso_sophia:type()
|
||||||
, event_type => aeso_sophia:type()
|
, event_type => aeso_sophia:type()
|
||||||
, types => #{ type_name() => type_def() }
|
, types => #{ type_name() => type_def() }
|
||||||
, type_vars => #{ string() => aeso_sophia:type() }
|
, type_vars => #{ string() => aeso_sophia:type() }
|
||||||
, constructors => #{ string() => integer() } %% name to tag
|
, constructors => #{ [string()] => integer() } %% name to tag
|
||||||
, options => [any()]
|
, options => [any()]
|
||||||
}.
|
}.
|
||||||
|
|
||||||
@ -73,10 +85,10 @@ builtin_types() ->
|
|||||||
}.
|
}.
|
||||||
|
|
||||||
builtin_constructors() ->
|
builtin_constructors() ->
|
||||||
#{ "RelativeTTL" => 0
|
#{ ["RelativeTTL"] => 0
|
||||||
, "FixedTTL" => 1
|
, ["FixedTTL"] => 1
|
||||||
, "None" => 0
|
, ["None"] => 0
|
||||||
, "Some" => 1 }.
|
, ["Some"] => 1 }.
|
||||||
|
|
||||||
map_typerep(K, V) ->
|
map_typerep(K, V) ->
|
||||||
{map, K, V}.
|
{map, K, V}.
|
||||||
@ -91,11 +103,30 @@ new_env() ->
|
|||||||
set_name(Name, Icode) ->
|
set_name(Name, Icode) ->
|
||||||
maps:put(contract_name, Name, Icode).
|
maps:put(contract_name, Name, Icode).
|
||||||
|
|
||||||
|
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
|
||||||
|
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
||||||
|
|
||||||
|
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
|
||||||
|
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
|
||||||
|
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
|
||||||
|
enter_namespace(NS, Icode) ->
|
||||||
|
Icode#{ namespace => NS }.
|
||||||
|
|
||||||
|
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
|
||||||
|
get_namespace(Icode) -> maps:get(namespace, Icode, false).
|
||||||
|
|
||||||
|
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
|
||||||
|
qualify(X, Icode) ->
|
||||||
|
case get_namespace(Icode) of
|
||||||
|
false -> X;
|
||||||
|
NS -> aeso_syntax:qualify(NS, X)
|
||||||
|
end.
|
||||||
|
|
||||||
-spec set_functions([fun_dec()], icode()) -> icode().
|
-spec set_functions([fun_dec()], icode()) -> icode().
|
||||||
set_functions(NewFuns, Icode) ->
|
set_functions(NewFuns, Icode) ->
|
||||||
maps:put(functions, NewFuns, Icode).
|
maps:put(functions, NewFuns, Icode).
|
||||||
|
|
||||||
-spec get_constructor_tag(string(), icode()) -> integer().
|
-spec get_constructor_tag([string()], icode()) -> integer().
|
||||||
get_constructor_tag(Name, #{constructors := Constructors}) ->
|
get_constructor_tag(Name, #{constructors := Constructors}) ->
|
||||||
case maps:get(Name, Constructors, undefined) of
|
case maps:get(Name, Constructors, undefined) of
|
||||||
undefined -> error({undefined_constructor, Name});
|
undefined -> error({undefined_constructor, Name});
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
, args :: arg_list()
|
, args :: arg_list()
|
||||||
, body :: expr()}).
|
, body :: expr()}).
|
||||||
|
|
||||||
-record(var_ref, { name :: string() | {builtin, atom() | tuple()}}).
|
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
|
||||||
|
|
||||||
-record(prim_call_contract,
|
-record(prim_call_contract,
|
||||||
{ gas :: expr()
|
{ gas :: expr()
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
i(Code) -> aeb_opcodes:mnemonic(Code).
|
i(Code) -> aeb_opcodes:mnemonic(Code).
|
||||||
|
|
||||||
%% We don't track purity or statefulness in the type checker yet.
|
%% We don't track purity or statefulness in the type checker yet.
|
||||||
is_stateful({FName, _, _, _, _}) -> FName /= "init".
|
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
|
||||||
|
|
||||||
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
|
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ make_args(Args) ->
|
|||||||
|
|
||||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||||
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(FName), ArgType, TypeRep),
|
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||||
{integer, Hash}.
|
{integer, Hash}.
|
||||||
|
|
||||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
|
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
|
||||||
|
|
||||||
-type pos() :: {integer(), integer()}.
|
-type pos() :: {string() | no_file, integer(), integer()} | {integer(), integer()}.
|
||||||
-type token() :: {atom(), pos(), term()} | {atom(), pos()}.
|
-type token() :: {atom(), pos(), term()} | {atom(), pos()}.
|
||||||
-type tokens() :: [token()].
|
-type tokens() :: [token()].
|
||||||
-type error() :: {pos(), string() | no_error}.
|
-type error() :: {pos(), string() | no_error}.
|
||||||
|
@ -5,28 +5,37 @@
|
|||||||
-module(aeso_parser).
|
-module(aeso_parser).
|
||||||
|
|
||||||
-export([string/1,
|
-export([string/1,
|
||||||
|
string/2,
|
||||||
type/1]).
|
type/1]).
|
||||||
|
|
||||||
-include("aeso_parse_lib.hrl").
|
-include("aeso_parse_lib.hrl").
|
||||||
|
|
||||||
-spec string(string()) ->
|
-type parse_result() :: {ok, aeso_syntax:ast()}
|
||||||
{ok, aeso_syntax:ast()}
|
| {error, {aeso_parse_lib:pos(), atom(), term()}}
|
||||||
| {error, {aeso_parse_lib:pos(),
|
| {error, {aeso_parse_lib:pos(), atom()}}.
|
||||||
atom(),
|
|
||||||
term()}}
|
-spec string(string()) -> parse_result().
|
||||||
| {error, {aeso_parse_lib:pos(),
|
|
||||||
atom()}}.
|
|
||||||
string(String) ->
|
string(String) ->
|
||||||
parse_and_scan(file(), String).
|
string(String, []).
|
||||||
|
|
||||||
|
-spec string(string(), aeso_compiler:options()) -> parse_result().
|
||||||
|
string(String, Opts) ->
|
||||||
|
case parse_and_scan(file(), String, Opts) of
|
||||||
|
{ok, AST} ->
|
||||||
|
expand_includes(AST, Opts);
|
||||||
|
Err = {error, _} ->
|
||||||
|
Err
|
||||||
|
end.
|
||||||
|
|
||||||
type(String) ->
|
type(String) ->
|
||||||
parse_and_scan(type(), String).
|
parse_and_scan(type(), String, []).
|
||||||
|
|
||||||
parse_and_scan(P, S) ->
|
parse_and_scan(P, S, Opts) ->
|
||||||
case aeso_scan:scan(S) of
|
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
case aeso_scan:scan(S) of
|
||||||
Error -> Error
|
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||||
end.
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
%% -- Parsing rules ----------------------------------------------------------
|
%% -- Parsing rules ----------------------------------------------------------
|
||||||
|
|
||||||
@ -36,7 +45,9 @@ decl() ->
|
|||||||
?LAZY_P(
|
?LAZY_P(
|
||||||
choice(
|
choice(
|
||||||
%% Contract declaration
|
%% Contract declaration
|
||||||
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
||||||
|
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||||
|
, ?RULE(keyword(include), str(), {include, _2})
|
||||||
|
|
||||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||||
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
|
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
|
||||||
@ -301,6 +312,7 @@ binop(Ops) ->
|
|||||||
con() -> token(con).
|
con() -> token(con).
|
||||||
id() -> token(id).
|
id() -> token(id).
|
||||||
tvar() -> token(tvar).
|
tvar() -> token(tvar).
|
||||||
|
str() -> token(string).
|
||||||
|
|
||||||
token(Tag) ->
|
token(Tag) ->
|
||||||
?RULE(tok(Tag),
|
?RULE(tok(Tag),
|
||||||
@ -336,10 +348,17 @@ bracket_list(P) -> brackets(comma_sep(P)).
|
|||||||
-type ann_col() :: aeso_syntax:ann_col().
|
-type ann_col() :: aeso_syntax:ann_col().
|
||||||
|
|
||||||
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
||||||
pos_ann(Line, Col) -> [{line, Line}, {col, Col}].
|
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
|
||||||
|
|
||||||
|
current_file() ->
|
||||||
|
get('$current_file').
|
||||||
|
|
||||||
|
set_current_file(File) ->
|
||||||
|
put('$current_file', File).
|
||||||
|
|
||||||
ann_pos(Ann) ->
|
ann_pos(Ann) ->
|
||||||
{proplists:get_value(line, Ann),
|
{proplists:get_value(file, Ann),
|
||||||
|
proplists:get_value(line, Ann),
|
||||||
proplists:get_value(col, Ann)}.
|
proplists:get_value(col, Ann)}.
|
||||||
|
|
||||||
get_ann(Ann) when is_list(Ann) -> Ann;
|
get_ann(Ann) when is_list(Ann) -> Ann;
|
||||||
@ -357,10 +376,10 @@ set_ann(Key, Val, Node) ->
|
|||||||
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
|
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
|
||||||
|
|
||||||
get_pos(Node) ->
|
get_pos(Node) ->
|
||||||
{get_ann(line, Node), get_ann(col, Node)}.
|
{current_file(), get_ann(line, Node), get_ann(col, Node)}.
|
||||||
|
|
||||||
set_pos({L, C}, Node) ->
|
set_pos({F, L, C}, Node) ->
|
||||||
set_ann(line, L, set_ann(col, C, Node)).
|
set_ann(file, F, set_ann(line, L, set_ann(col, C, Node))).
|
||||||
|
|
||||||
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
|
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
|
||||||
|
|
||||||
@ -442,8 +461,10 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
|||||||
parse_field_pattern({field, Ann, F, E}) ->
|
parse_field_pattern({field, Ann, F, E}) ->
|
||||||
{field, Ann, F, parse_pattern(E)}.
|
{field, Ann, F, parse_pattern(E)}.
|
||||||
|
|
||||||
return_error({L, C}, Err) ->
|
return_error({no_file, L, C}, Err) ->
|
||||||
fail(io_lib:format("~p:~p:\n~s", [L, C, Err])).
|
fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
|
||||||
|
return_error({F, L, C}, Err) ->
|
||||||
|
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
|
||||||
|
|
||||||
-spec ret_doc_err(ann(), prettypr:document()) -> no_return().
|
-spec ret_doc_err(ann(), prettypr:document()) -> no_return().
|
||||||
ret_doc_err(Ann, Doc) ->
|
ret_doc_err(Ann, Doc) ->
|
||||||
@ -455,3 +476,38 @@ bad_expr_err(Reason, E) ->
|
|||||||
prettypr:sep([prettypr:text(Reason ++ ":"),
|
prettypr:sep([prettypr:text(Reason ++ ":"),
|
||||||
prettypr:nest(2, aeso_pretty:expr(E))])).
|
prettypr:nest(2, aeso_pretty:expr(E))])).
|
||||||
|
|
||||||
|
%% -- Helper functions -------------------------------------------------------
|
||||||
|
expand_includes(AST, Opts) ->
|
||||||
|
expand_includes(AST, [], Opts).
|
||||||
|
|
||||||
|
expand_includes([], Acc, _Opts) ->
|
||||||
|
{ok, lists:reverse(Acc)};
|
||||||
|
expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
|
||||||
|
case read_file(File, Opts) of
|
||||||
|
{ok, Bin} ->
|
||||||
|
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
||||||
|
case string(binary_to_list(Bin), Opts1) of
|
||||||
|
{ok, AST1} ->
|
||||||
|
expand_includes(AST1 ++ AST, Acc, Opts);
|
||||||
|
Err = {error, _} ->
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
{error, _} ->
|
||||||
|
{error, {get_pos(S), include_error, File}}
|
||||||
|
end;
|
||||||
|
expand_includes([E | AST], Acc, Opts) ->
|
||||||
|
expand_includes(AST, [E | Acc], Opts).
|
||||||
|
|
||||||
|
read_file(File, Opts) ->
|
||||||
|
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
|
||||||
|
{file_system, Paths} ->
|
||||||
|
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
|
||||||
|
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
|
||||||
|
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
|
||||||
|
{explicit_files, Files} ->
|
||||||
|
case maps:get(binary_to_list(File), Files, not_found) of
|
||||||
|
not_found -> {error, not_found};
|
||||||
|
Src -> {ok, Src}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
@ -147,6 +147,8 @@ decl(D, Options) ->
|
|||||||
-spec decl(aeso_syntax:decl()) -> doc().
|
-spec decl(aeso_syntax:decl()) -> doc().
|
||||||
decl({contract, _, C, Ds}) ->
|
decl({contract, _, C, Ds}) ->
|
||||||
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
|
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
|
||||||
|
decl({namespace, _, C, Ds}) ->
|
||||||
|
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||||
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
|
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
|
||||||
decl({type_def, _, T, Vars, Def}) ->
|
decl({type_def, _, T, Vars, Def}) ->
|
||||||
Kind = element(1, Def),
|
Kind = element(1, Def),
|
||||||
@ -234,8 +236,10 @@ type({app_t, _, Type, Args}) ->
|
|||||||
beside(type(Type), tuple_type(Args));
|
beside(type(Type), tuple_type(Args));
|
||||||
type({tuple_t, _, Args}) ->
|
type({tuple_t, _, Args}) ->
|
||||||
tuple_type(Args);
|
tuple_type(Args);
|
||||||
type({named_arg_t, _, Name, Type, Default}) ->
|
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||||
follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
%% Drop the default value
|
||||||
|
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||||
|
typed(name(Name), Type);
|
||||||
|
|
||||||
type(R = {record_t, _}) -> typedef(R);
|
type(R = {record_t, _}) -> typedef(R);
|
||||||
type(T = {id, _, _}) -> name(T);
|
type(T = {id, _, _}) -> name(T);
|
||||||
|
@ -36,8 +36,8 @@ lexer() ->
|
|||||||
, {"\\*/", pop(skip())}
|
, {"\\*/", pop(skip())}
|
||||||
, {"[^/*]+|[/*]", skip()} ],
|
, {"[^/*]+|[/*]", skip()} ],
|
||||||
|
|
||||||
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||||
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal"],
|
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
|
||||||
KW = string:join(Keywords, "|"),
|
KW = string:join(Keywords, "|"),
|
||||||
|
|
||||||
Rules =
|
Rules =
|
||||||
|
@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
-module(aeso_syntax).
|
-module(aeso_syntax).
|
||||||
|
|
||||||
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2]).
|
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
|
||||||
|
|
||||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||||
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
||||||
-export_type([bin_op/0, un_op/0]).
|
-export_type([bin_op/0, un_op/0]).
|
||||||
-export_type([decl/0, letbind/0, typedef/0]).
|
-export_type([decl/0, letbind/0, typedef/0]).
|
||||||
-export_type([arg/0, field_t/0, constructor_t/0]).
|
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]).
|
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||||
-export_type([ast/0]).
|
-export_type([ast/0]).
|
||||||
|
|
||||||
-type ast() :: [decl()].
|
-type ast() :: [decl()].
|
||||||
@ -35,6 +35,7 @@
|
|||||||
-type tvar() :: {tvar, ann(), name()}.
|
-type tvar() :: {tvar, ann(), name()}.
|
||||||
|
|
||||||
-type decl() :: {contract, ann(), con(), [decl()]}
|
-type decl() :: {contract, ann(), con(), [decl()]}
|
||||||
|
| {namespace, ann(), con(), [decl()]}
|
||||||
| {type_decl, ann(), id(), [tvar()]}
|
| {type_decl, ann(), id(), [tvar()]}
|
||||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||||
| {fun_decl, ann(), id(), type()}
|
| {fun_decl, ann(), id(), type()}
|
||||||
@ -140,3 +141,8 @@ get_ann(Key, Node) ->
|
|||||||
|
|
||||||
get_ann(Key, Node, Default) ->
|
get_ann(Key, Node, Default) ->
|
||||||
proplists:get_value(Key, get_ann(Node), Default).
|
proplists:get_value(Key, get_ann(Node), Default).
|
||||||
|
|
||||||
|
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
|
||||||
|
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
|
||||||
|
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
|
||||||
|
|
||||||
|
@ -6,89 +6,142 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
-module(aeso_syntax_utils).
|
-module(aeso_syntax_utils).
|
||||||
|
|
||||||
-export([used_ids/1, used_types/1]).
|
-export([used_ids/1, used_types/1, used/1]).
|
||||||
|
|
||||||
%% Var set combinators
|
-record(alg, {zero, plus, scoped}).
|
||||||
none() -> [].
|
|
||||||
one(X) -> [X].
|
|
||||||
union_map(F, Xs) -> lists:umerge(lists:map(F, Xs)).
|
|
||||||
minus(Xs, Ys) -> Xs -- Ys.
|
|
||||||
|
|
||||||
%% Compute names used by a definition or expression.
|
-type alg(A) :: #alg{ zero :: A
|
||||||
used_ids(Es) when is_list(Es) ->
|
, plus :: fun((A, A) -> A)
|
||||||
union_map(fun used_ids/1, Es);
|
, scoped :: fun((A, A) -> A) }.
|
||||||
used_ids({bind, A, B}) ->
|
|
||||||
minus(used_ids(B), used_ids(A));
|
|
||||||
%% Declarations
|
|
||||||
used_ids({contract, _, _, Decls}) -> used_ids(Decls);
|
|
||||||
used_ids({type_decl, _, _, _}) -> none();
|
|
||||||
used_ids({type_def, _, _, _, _}) -> none();
|
|
||||||
used_ids({fun_decl, _, _, _}) -> none();
|
|
||||||
used_ids({letval, _, _, _, E}) -> used_ids(E);
|
|
||||||
used_ids({letfun, _, _, Args, _, E}) -> used_ids({bind, Args, E});
|
|
||||||
used_ids({letrec, _, Decls}) -> used_ids(Decls);
|
|
||||||
%% Args
|
|
||||||
used_ids({arg, _, X, _}) -> used_ids(X);
|
|
||||||
used_ids({named_arg, _, _, E}) -> used_ids(E);
|
|
||||||
%% Constants
|
|
||||||
used_ids({int, _, _}) -> none();
|
|
||||||
used_ids({bool, _, _}) -> none();
|
|
||||||
used_ids({hash, _, _}) -> none();
|
|
||||||
used_ids({unit, _}) -> none();
|
|
||||||
used_ids({string, _, _}) -> none();
|
|
||||||
used_ids({char, _, _}) -> none();
|
|
||||||
%% Expressions
|
|
||||||
used_ids({lam, _, Args, E}) -> used_ids({bind, Args, E});
|
|
||||||
used_ids({'if', _, A, B, C}) -> used_ids([A, B, C]);
|
|
||||||
used_ids({switch, _, E, Bs}) -> used_ids([E, Bs]);
|
|
||||||
used_ids({app, _, E, Es}) -> used_ids([E | Es]);
|
|
||||||
used_ids({proj, _, E, _}) -> used_ids(E);
|
|
||||||
used_ids({tuple, _, Es}) -> used_ids(Es);
|
|
||||||
used_ids({list, _, Es}) -> used_ids(Es);
|
|
||||||
used_ids({typed, _, E, _}) -> used_ids(E);
|
|
||||||
used_ids({record, _, Fs}) -> used_ids(Fs);
|
|
||||||
used_ids({record, _, E, Fs}) -> used_ids([E, Fs]);
|
|
||||||
used_ids({map, _, E, Fs}) -> used_ids([E, Fs]);
|
|
||||||
used_ids({map, _, KVs}) -> used_ids([ [K, V] || {K, V} <- KVs ]);
|
|
||||||
used_ids({map_get, _, M, K}) -> used_ids([M, K]);
|
|
||||||
used_ids({map_get, _, M, K, V}) -> used_ids([M, K, V]);
|
|
||||||
used_ids({block, _, Ss}) -> used_ids_s(Ss);
|
|
||||||
used_ids({Op, _}) when is_atom(Op) -> none();
|
|
||||||
used_ids({id, _, X}) -> [X];
|
|
||||||
used_ids({qid, _, _}) -> none();
|
|
||||||
used_ids({con, _, _}) -> none();
|
|
||||||
used_ids({qcon, _, _}) -> none();
|
|
||||||
%% Switch branches
|
|
||||||
used_ids({'case', _, P, E}) -> used_ids({bind, P, E});
|
|
||||||
%% Fields
|
|
||||||
used_ids({field, _, LV, E}) -> used_ids([LV, E]);
|
|
||||||
used_ids({field, _, LV, X, E}) -> used_ids([LV, {bind, X, E}]);
|
|
||||||
used_ids({proj, _, _}) -> none();
|
|
||||||
used_ids({map_get, _, E}) -> used_ids(E).
|
|
||||||
|
|
||||||
%% Statements
|
-type kind() :: decl | type | bind_type | expr | bind_expr.
|
||||||
used_ids_s([]) -> none();
|
|
||||||
used_ids_s([S | Ss]) ->
|
|
||||||
used_ids([S, {bind, bound_ids(S), {block, [], Ss}}]).
|
|
||||||
|
|
||||||
bound_ids({letval, _, X, _, _}) -> one(X);
|
-spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A
|
||||||
bound_ids({letfun, _, X, _, _, _}) -> one(X);
|
when E :: aeso_syntax:decl()
|
||||||
bound_ids({letrec, _, Decls}) -> union_map(fun bound_ids/1, Decls);
|
| aeso_syntax:typedef()
|
||||||
bound_ids(_) -> none().
|
| aeso_syntax:field_t()
|
||||||
|
| aeso_syntax:constructor_t()
|
||||||
|
| aeso_syntax:type()
|
||||||
|
| aeso_syntax:expr()
|
||||||
|
| aeso_syntax:pat()
|
||||||
|
| aeso_syntax:arg()
|
||||||
|
| aeso_syntax:alt()
|
||||||
|
| aeso_syntax:elim()
|
||||||
|
| aeso_syntax:arg_expr()
|
||||||
|
| aeso_syntax:field(aeso_syntax:expr())
|
||||||
|
| aeso_syntax:stmt().
|
||||||
|
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||||
|
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
|
||||||
|
Same = fun(A) -> fold(Alg, Fun, K, A) end,
|
||||||
|
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
|
||||||
|
Type = fun(T) -> fold(Alg, Fun, type, T) end,
|
||||||
|
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
|
||||||
|
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
||||||
|
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
||||||
|
Top = Fun(K, X),
|
||||||
|
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
|
||||||
|
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
|
||||||
|
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
|
||||||
|
LB (_) -> Zero
|
||||||
|
end,
|
||||||
|
Rec = case X of
|
||||||
|
%% lists (bound things in head scope over tail)
|
||||||
|
[A | As] -> Scoped(Same(A), Same(As));
|
||||||
|
%% decl()
|
||||||
|
{contract, _, _, Ds} -> Decl(Ds);
|
||||||
|
{namespace, _, _, Ds} -> Decl(Ds);
|
||||||
|
{type_decl, _, I, _} -> BindType(I);
|
||||||
|
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
||||||
|
{fun_decl, _, _, T} -> Type(T);
|
||||||
|
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
|
||||||
|
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
|
||||||
|
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
|
||||||
|
%% typedef()
|
||||||
|
{alias_t, T} -> Type(T);
|
||||||
|
{record_t, Fs} -> Type(Fs);
|
||||||
|
{variant_t, Cs} -> Type(Cs);
|
||||||
|
%% field_t() and constructor_t()
|
||||||
|
{field_t, _, _, T} -> Type(T);
|
||||||
|
{constr_t, _, _, Ts} -> Type(Ts);
|
||||||
|
%% type()
|
||||||
|
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
|
||||||
|
{app_t, _, T, Ts} -> Type([T | Ts]);
|
||||||
|
{tuple_t, _, Ts} -> Type(Ts);
|
||||||
|
%% named_arg_t()
|
||||||
|
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
|
||||||
|
%% expr()
|
||||||
|
{lam, _, Args, E} -> Scoped(BindExpr(Args), Expr(E));
|
||||||
|
{'if', _, A, B, C} -> Expr([A, B, C]);
|
||||||
|
{switch, _, E, Alts} -> Expr([E, Alts]);
|
||||||
|
{app, _, A, As} -> Expr([A | As]);
|
||||||
|
{proj, _, E, _} -> Expr(E);
|
||||||
|
{tuple, _, As} -> Expr(As);
|
||||||
|
{list, _, As} -> Expr(As);
|
||||||
|
{typed, _, E, T} -> Plus(Expr(E), Type(T));
|
||||||
|
{record, _, Fs} -> Expr(Fs);
|
||||||
|
{record, _, E, Fs} -> Expr([E | Fs]);
|
||||||
|
{map, _, E, Fs} -> Expr([E | Fs]);
|
||||||
|
{map, _, KVs} -> Sum([Expr([Key, Val]) || {Key, Val} <- KVs]);
|
||||||
|
{map_get, _, A, B} -> Expr([A, B]);
|
||||||
|
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
||||||
|
{block, _, Ss} -> Expr(Ss);
|
||||||
|
%% field()
|
||||||
|
{field, _, LV, E} -> Expr([LV, E]);
|
||||||
|
{field, _, LV, _, E} -> Expr([LV, E]);
|
||||||
|
%% arg()
|
||||||
|
{arg, _, X, T} -> Plus(Expr(X), Type(T));
|
||||||
|
%% alt()
|
||||||
|
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||||
|
%% elim()
|
||||||
|
{proj, _, _} -> Zero;
|
||||||
|
{map_get, _, E} -> Expr(E);
|
||||||
|
%% arg_expr()
|
||||||
|
{named_arg, _, _, E} -> Expr(E);
|
||||||
|
_ -> Alg#alg.zero
|
||||||
|
end,
|
||||||
|
(Alg#alg.plus)(Top, Rec).
|
||||||
|
|
||||||
|
%% Name dependencies
|
||||||
|
|
||||||
|
used_ids(E) ->
|
||||||
|
[ X || {term, [X]} <- used(E) ].
|
||||||
|
|
||||||
|
used_types(T) ->
|
||||||
|
[ X || {type, [X]} <- used(T) ].
|
||||||
|
|
||||||
|
-type entity() :: {term, [string()]}
|
||||||
|
| {type, [string()]}
|
||||||
|
| {namespace, [string()]}.
|
||||||
|
|
||||||
|
-spec entity_alg() -> alg([entity()]).
|
||||||
|
entity_alg() ->
|
||||||
|
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
||||||
|
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
||||||
|
Scoped = fun(Xs, Ys) ->
|
||||||
|
{Bound, Others} = lists:partition(IsBound, Ys),
|
||||||
|
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
||||||
|
lists:umerge(Xs -- Bound1, Others)
|
||||||
|
end,
|
||||||
|
#alg{ zero = []
|
||||||
|
, plus = fun lists:umerge/2
|
||||||
|
, scoped = Scoped }.
|
||||||
|
|
||||||
|
-spec used(_) -> [entity()].
|
||||||
|
used(D) ->
|
||||||
|
Kind = fun(expr) -> term;
|
||||||
|
(bind_expr) -> bound_term;
|
||||||
|
(type) -> type;
|
||||||
|
(bind_type) -> bound_type
|
||||||
|
end,
|
||||||
|
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
||||||
|
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
||||||
|
Xs =
|
||||||
|
fold(entity_alg(),
|
||||||
|
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
|
||||||
|
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
||||||
|
(K, {con, _, X}) -> [{Kind(K), [X]}];
|
||||||
|
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
||||||
|
(_, _) -> []
|
||||||
|
end, decl, D),
|
||||||
|
lists:filter(NotBound, Xs).
|
||||||
|
|
||||||
used_types(Ts) when is_list(Ts) -> union_map(fun used_types/1, Ts);
|
|
||||||
used_types({type_def, _, _, _, T}) -> used_types(T);
|
|
||||||
used_types({alias_t, T}) -> used_types(T);
|
|
||||||
used_types({record_t, Fs}) -> used_types(Fs);
|
|
||||||
used_types({variant_t, Cs}) -> used_types(Cs);
|
|
||||||
used_types({field_t, _, _, T}) -> used_types(T);
|
|
||||||
used_types({constr_t, _, _, Ts}) -> used_types(Ts);
|
|
||||||
used_types({fun_t, _, Named, Args, T}) -> used_types([T | Named ++ Args]);
|
|
||||||
used_types({named_arg_t, _, _, T, _}) -> used_types(T);
|
|
||||||
used_types({app_t, _, T, Ts}) -> used_types([T | Ts]);
|
|
||||||
used_types({tuple_t, _, Ts}) -> used_types(Ts);
|
|
||||||
used_types({id, _, X}) -> one(X);
|
|
||||||
used_types({qid, _, _}) -> none();
|
|
||||||
used_types({con, _, _}) -> none();
|
|
||||||
used_types({qcon, _, _}) -> none();
|
|
||||||
used_types({tvar, _, _}) -> none().
|
|
||||||
|
@ -66,7 +66,7 @@ encode_decode_sophia_test() ->
|
|||||||
|
|
||||||
encode_decode_sophia_string(SophiaType, String) ->
|
encode_decode_sophia_string(SophiaType, String) ->
|
||||||
io:format("String ~p~n", [String]),
|
io:format("String ~p~n", [String]),
|
||||||
Code = [ "contract Call =\n"
|
Code = [ "contract MakeCall =\n"
|
||||||
, " function foo : ", SophiaType, " => _\n"
|
, " function foo : ", SophiaType, " => _\n"
|
||||||
, " function __call() = foo(", String, ")\n" ],
|
, " function __call() = foo(", String, ")\n" ],
|
||||||
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),
|
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),
|
||||||
|
@ -10,15 +10,10 @@
|
|||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
%% simple_compile_test_() -> ok.
|
|
||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
%% are made on the output, just that it is a binary which indicates
|
%% are made on the output, just that it is a binary which indicates
|
||||||
%% that the compilation worked.
|
%% that the compilation worked.
|
||||||
|
|
||||||
simple_compile_test_() ->
|
simple_compile_test_() ->
|
||||||
{setup,
|
|
||||||
fun () -> ok end, %Setup
|
|
||||||
fun (_) -> ok end, %Cleanup
|
|
||||||
[ {"Testing the " ++ ContractName ++ " contract",
|
[ {"Testing the " ++ ContractName ++ " contract",
|
||||||
fun() ->
|
fun() ->
|
||||||
#{byte_code := ByteCode,
|
#{byte_code := ByteCode,
|
||||||
@ -28,11 +23,34 @@ 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\n",ErrorString/binary>> = compile(ContractName),
|
case compile(ContractName) of
|
||||||
check_errors(lists:sort(ExpectedErrors), ErrorString)
|
<<"Type errors\n", ErrorString/binary>> ->
|
||||||
|
check_errors(lists:sort(ExpectedErrors), ErrorString);
|
||||||
|
<<"Parse errors\n", ErrorString/binary>> ->
|
||||||
|
check_errors(lists:sort(ExpectedErrors), ErrorString)
|
||||||
|
end
|
||||||
end} ||
|
end} ||
|
||||||
{ContractName, ExpectedErrors} <- failing_contracts() ]
|
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
||||||
}.
|
[ {"Testing include with explicit files",
|
||||||
|
fun() ->
|
||||||
|
FileSystem = maps:from_list(
|
||||||
|
[ begin
|
||||||
|
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
|
||||||
|
{File, Bin}
|
||||||
|
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
|
||||||
|
#{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
|
||||||
|
#{byte_code := Code2} = compile("include"),
|
||||||
|
?assertMatch(true, Code1 == Code2)
|
||||||
|
end} ] ++
|
||||||
|
[ {"Testing deadcode elimination",
|
||||||
|
fun() ->
|
||||||
|
#{ byte_code := NoDeadCode } = compile("nodeadcode"),
|
||||||
|
#{ byte_code := DeadCode } = compile("deadcode"),
|
||||||
|
SizeNoDeadCode = byte_size(NoDeadCode),
|
||||||
|
SizeDeadCode = byte_size(DeadCode),
|
||||||
|
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
|
||||||
|
ok
|
||||||
|
end} ].
|
||||||
|
|
||||||
check_errors(Expect, ErrorString) ->
|
check_errors(Expect, ErrorString) ->
|
||||||
%% This removes the final single \n as well.
|
%% This removes the final single \n as well.
|
||||||
@ -44,10 +62,13 @@ check_errors(Expect, ErrorString) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
compile(Name) ->
|
compile(Name) ->
|
||||||
|
compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||||
|
|
||||||
|
compile(Name, Options) ->
|
||||||
String = aeso_test_utils:read_contract(Name),
|
String = aeso_test_utils:read_contract(Name),
|
||||||
case aeso_compiler:from_string(String, []) of
|
case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
|
||||||
{ok,Map} -> Map;
|
{ok, Map} -> Map;
|
||||||
{error,ErrorString} -> ErrorString
|
{error, ErrorString} -> ErrorString
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% compilable_contracts() -> [ContractName].
|
%% compilable_contracts() -> [ContractName].
|
||||||
@ -70,7 +91,13 @@ compilable_contracts() ->
|
|||||||
"stack",
|
"stack",
|
||||||
"test",
|
"test",
|
||||||
"builtin_bug",
|
"builtin_bug",
|
||||||
"builtin_map_get_bug"
|
"builtin_map_get_bug",
|
||||||
|
"nodeadcode",
|
||||||
|
"deadcode",
|
||||||
|
"variant_types",
|
||||||
|
"state_handling",
|
||||||
|
"events",
|
||||||
|
"include"
|
||||||
].
|
].
|
||||||
|
|
||||||
%% Contracts that should produce type errors
|
%% Contracts that should produce type errors
|
||||||
@ -150,11 +177,11 @@ failing_contracts() ->
|
|||||||
<<"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)">>,
|
" - r' (at line 5, column 10)">>,
|
||||||
<<"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)">>,
|
|
||||||
<<"Repeated name x in pattern\n"
|
<<"Repeated name x in pattern\n"
|
||||||
" x :: x (at line 26, column 7)">>,
|
" x :: x (at line 26, column 7)">>,
|
||||||
<<"No record type with fields y, z (at line 14, column 22)">>]}
|
<<"No record type with fields y, z (at line 14, column 22)">>,
|
||||||
|
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
||||||
|
<<"Record type r2 does not have field y (at line 15, 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"
|
||||||
@ -167,4 +194,24 @@ failing_contracts() ->
|
|||||||
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
|
[<<"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)">>,
|
<<"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)">>]}
|
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
|
||||||
|
, {"namespace_clash",
|
||||||
|
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
|
||||||
|
, {"bad_events",
|
||||||
|
[<<"The payload type int (at line 10, column 30) should be string">>,
|
||||||
|
<<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
|
||||||
|
<<"The indexed type string (at line 9, column 25) is not a word type">>,
|
||||||
|
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
|
||||||
|
, {"bad_events2",
|
||||||
|
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many string values (max 1)">>,
|
||||||
|
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]}
|
||||||
|
, {"type_clash",
|
||||||
|
[<<"Cannot unify int\n"
|
||||||
|
" and string\n"
|
||||||
|
"when checking the record projection at line 12, column 40\n"
|
||||||
|
" r.foo : (gas : int, value : int) => Remote.themap\n"
|
||||||
|
"against the expected type\n"
|
||||||
|
" (gas : int, value : int) => map(string, int)">>]}
|
||||||
|
, {"bad_include_and_ns",
|
||||||
|
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
||||||
|
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
|
||||||
].
|
].
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
-module(contract_tests).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
make_cmd() -> "make -C " ++ aeso_test_utils:contract_path().
|
|
||||||
|
|
||||||
contracts_test_() ->
|
|
||||||
{setup,
|
|
||||||
fun() -> os:cmd(make_cmd()) end,
|
|
||||||
fun(_) -> os:cmd(make_cmd() ++ " clean") end,
|
|
||||||
[ {"Testing the " ++ Contract ++ " contract",
|
|
||||||
fun() ->
|
|
||||||
?assertCmdOutput(Expected, filename:join(aeso_test_utils:contract_path(), Contract ++ "_test"))
|
|
||||||
end} || {Contract, Expected} <- contracts() ]}.
|
|
||||||
|
|
||||||
contracts() ->
|
|
||||||
[].
|
|
||||||
%% [{"voting",
|
|
||||||
%% "Delegate before vote\n"
|
|
||||||
%% "Cake: 1\n"
|
|
||||||
%% "Beer: 2\n"
|
|
||||||
%% "Winner: Beer\n"
|
|
||||||
%% "Delegate after vote\n"
|
|
||||||
%% "Cake: 1\n"
|
|
||||||
%% "Beer: 2\n"
|
|
||||||
%% "Winner: Beer\n"
|
|
||||||
%% }].
|
|
||||||
|
|
25
test/contracts/bad_events.aes
Normal file
25
test/contracts/bad_events.aes
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
contract Events =
|
||||||
|
type alias_int = int
|
||||||
|
type alias_address = address
|
||||||
|
type alias_string = string
|
||||||
|
|
||||||
|
datatype event =
|
||||||
|
Event1(indexed alias_int, indexed int, string)
|
||||||
|
| Event2(alias_string, indexed alias_address)
|
||||||
|
| BadEvent1(indexed string, string)
|
||||||
|
| BadEvent2(indexed int, int)
|
||||||
|
| BadEvent3(indexed alias_string, string)
|
||||||
|
| BadEvent4(indexed int, alias_address)
|
||||||
|
|
||||||
|
function f1(x : int, y : string) =
|
||||||
|
Chain.event(Event1(x, x+1, y))
|
||||||
|
|
||||||
|
function f2(s : string) =
|
||||||
|
Chain.event(Event2(s, Call.caller))
|
||||||
|
|
||||||
|
function f3(x : int) =
|
||||||
|
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
|
||||||
|
|
||||||
|
function i2s(i : int) = Int.to_str(i)
|
||||||
|
function a2s(a : address) = Address.to_str(a)
|
||||||
|
|
23
test/contracts/bad_events2.aes
Normal file
23
test/contracts/bad_events2.aes
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
contract Events =
|
||||||
|
type alias_int = int
|
||||||
|
type alias_address = address
|
||||||
|
type alias_string = string
|
||||||
|
|
||||||
|
datatype event =
|
||||||
|
Event1(indexed alias_int, indexed int, string)
|
||||||
|
| Event2(alias_string, indexed alias_address)
|
||||||
|
| BadEvent1(string, string)
|
||||||
|
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
|
||||||
|
|
||||||
|
function f1(x : int, y : string) =
|
||||||
|
Chain.event(Event1(x, x+1, y))
|
||||||
|
|
||||||
|
function f2(s : string) =
|
||||||
|
Chain.event(Event2(s, Call.caller))
|
||||||
|
|
||||||
|
function f3(x : int) =
|
||||||
|
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
|
||||||
|
|
||||||
|
function i2s(i : int) = Int.to_str(i)
|
||||||
|
function a2s(a : address) = Address.to_str(a)
|
||||||
|
|
6
test/contracts/bad_include_and_ns.aes
Normal file
6
test/contracts/bad_include_and_ns.aes
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
contract Bad =
|
||||||
|
include "included.aes"
|
||||||
|
namespace Foo =
|
||||||
|
function foo() = 42
|
||||||
|
|
||||||
|
function foo() = 43
|
@ -1,6 +1,6 @@
|
|||||||
// Test more advanced chain interactions
|
// Test more advanced chain interactions
|
||||||
|
|
||||||
contract Chain =
|
contract ChainTest =
|
||||||
|
|
||||||
record state = { last_bf : address }
|
record state = { last_bf : address }
|
||||||
|
|
||||||
@ -10,4 +10,4 @@ contract Chain =
|
|||||||
function miner() = Chain.coinbase
|
function miner() = Chain.coinbase
|
||||||
|
|
||||||
function save_coinbase() =
|
function save_coinbase() =
|
||||||
put(state{last_bf = Chain.coinbase})
|
put(state{last_bf = Chain.coinbase})
|
||||||
|
21
test/contracts/deadcode.aes
Normal file
21
test/contracts/deadcode.aes
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
namespace List =
|
||||||
|
|
||||||
|
function map1(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map1(f, xs)
|
||||||
|
|
||||||
|
function map2(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map2(f, xs)
|
||||||
|
|
||||||
|
contract Deadcode =
|
||||||
|
|
||||||
|
function inc1(xs : list(int)) : list(int) =
|
||||||
|
List.map1((x) => x + 1, xs)
|
||||||
|
|
||||||
|
function inc2(xs : list(int)) : list(int) =
|
||||||
|
List.map1((x) => x + 1, xs)
|
||||||
|
|
9
test/contracts/include.aes
Normal file
9
test/contracts/include.aes
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
include "included.aes"
|
||||||
|
include "../contracts/included2.aes"
|
||||||
|
|
||||||
|
contract Include =
|
||||||
|
function foo() =
|
||||||
|
Included.foo() < Included2a.bar()
|
||||||
|
|
||||||
|
function bar() =
|
||||||
|
Included2b.foo() > Included.foo()
|
2
test/contracts/included.aes
Normal file
2
test/contracts/included.aes
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
namespace Included =
|
||||||
|
function foo() = 42
|
5
test/contracts/included2.aes
Normal file
5
test/contracts/included2.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace Included2a =
|
||||||
|
function bar() = 43
|
||||||
|
|
||||||
|
namespace Included2b =
|
||||||
|
function foo() = 44
|
5
test/contracts/namespace_clash.aes
Normal file
5
test/contracts/namespace_clash.aes
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
// You can't shadow existing contracts or namespaces.
|
||||||
|
|
||||||
|
contract Call =
|
||||||
|
function whatever() = ()
|
21
test/contracts/namespaces.aes
Normal file
21
test/contracts/namespaces.aes
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
namespace Lib =
|
||||||
|
|
||||||
|
private function rev(xs, ys) =
|
||||||
|
switch(xs)
|
||||||
|
[] => ys
|
||||||
|
x :: xs => rev(xs, x :: ys)
|
||||||
|
|
||||||
|
function reverse(xs : list('a)) : list('a) = rev(xs, [])
|
||||||
|
|
||||||
|
function eqlist(xs : list(int), ys : list(int)) =
|
||||||
|
switch((xs, ys))
|
||||||
|
([], []) => true
|
||||||
|
(x :: xs, y :: ys) => x == y && eqlist(xs, ys)
|
||||||
|
_ => false
|
||||||
|
|
||||||
|
contract TestNamespaces =
|
||||||
|
|
||||||
|
function palindrome(xs : list(int)) : bool =
|
||||||
|
Lib.eqlist(xs, Lib.reverse(xs))
|
||||||
|
|
21
test/contracts/nodeadcode.aes
Normal file
21
test/contracts/nodeadcode.aes
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
namespace List =
|
||||||
|
|
||||||
|
function map1(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map1(f, xs)
|
||||||
|
|
||||||
|
function map2(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map2(f, xs)
|
||||||
|
|
||||||
|
contract Deadcode =
|
||||||
|
|
||||||
|
function inc1(xs : list(int)) : list(int) =
|
||||||
|
List.map1((x) => x + 1, xs)
|
||||||
|
|
||||||
|
function inc2(xs : list(int)) : list(int) =
|
||||||
|
List.map2((x) => x + 1, xs)
|
||||||
|
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
contract SimpleStorage =
|
contract SimpleStorage =
|
||||||
|
|
||||||
type event = int
|
|
||||||
record state = { data : int }
|
record state = { data : int }
|
||||||
|
|
||||||
function init(value : int) : state = { data = value }
|
function init(value : int) : state = { data = value }
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
contract Remote =
|
contract Remote =
|
||||||
function look_at : (state) => ()
|
record rstate = { i : int, s : string, m : map(int, int) }
|
||||||
|
|
||||||
|
function look_at : (rstate) => ()
|
||||||
function return_s : (bool) => string
|
function return_s : (bool) => string
|
||||||
function return_m : (bool) => map(int, int)
|
function return_m : (bool) => map(int, int)
|
||||||
function get : (state) => state
|
function get : (rstate) => rstate
|
||||||
function get_i : (state) => int
|
function get_i : (rstate) => int
|
||||||
function get_s : (state) => string
|
function get_s : (rstate) => string
|
||||||
function get_m : (state) => map(int, int)
|
function get_m : (rstate) => map(int, int)
|
||||||
|
|
||||||
function fun_update_i : (state, int) => state
|
function fun_update_i : (rstate, int) => rstate
|
||||||
function fun_update_s : (state, string) => state
|
function fun_update_s : (rstate, string) => rstate
|
||||||
function fun_update_m : (state, map(int, int)) => state
|
function fun_update_m : (rstate, map(int, int)) => rstate
|
||||||
function fun_update_mk : (state, int, int) => state
|
function fun_update_mk : (rstate, int, int) => rstate
|
||||||
|
|
||||||
contract StateHandling =
|
contract StateHandling =
|
||||||
record state = { i : int, s : string, m : map(int, int) }
|
|
||||||
|
type state = Remote.rstate
|
||||||
|
|
||||||
function init(r : Remote, i : int) =
|
function init(r : Remote, i : int) =
|
||||||
let state0 = { i = 0, s = "undefined", m = {} }
|
let state0 = { i = 0, s = "undefined", m = {} }
|
||||||
|
13
test/contracts/type_clash.aes
Normal file
13
test/contracts/type_clash.aes
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
contract Remote =
|
||||||
|
|
||||||
|
type themap = map(int, string)
|
||||||
|
function foo : () => themap
|
||||||
|
|
||||||
|
contract Main =
|
||||||
|
|
||||||
|
type themap = map(string, int)
|
||||||
|
|
||||||
|
// Should fail
|
||||||
|
function foo(r : Remote) : themap = r.foo()
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user