Compare commits

..

9 Commits

Author SHA1 Message Date
Robert Virding d16fb82e25 Break out state and event from typedefs and update docs 2019-05-08 16:06:58 +02:00
Robert Virding d2cd97def7 Add handling of creating/updating maps and records, definitely WIP 2019-04-29 00:56:31 +02:00
Robert Virding 5455d0fcd7 Fixed a type error and test, definitely WIP 2019-04-25 12:19:49 +02:00
Robert Virding 2d3e6ab6e0 Refactor internal code and more add statements, definitely WIP 2019-04-25 11:56:21 +02:00
Robert Virding 70a0f77793 Replace hash with bytes, definitely WIP 2019-04-23 11:56:54 +02:00
Robert Virding 04b3227317 Update documentation, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding d9be8b2fca Saving even more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding a38afe7693 Saving more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding 5719730d8c Saving stuff, definitely WIP 2019-04-23 11:56:08 +02:00
50 changed files with 1466 additions and 3722 deletions
+1 -39
View File
@@ -9,42 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### Removed
## [3.1.0] - 2019-06-03
### Added
### Changed
- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`,
...) event arguments.
- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`.
- ACI is restructured and improved:
- `state` and `event` types (if present) now appear at the top level.
- Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are `contract_interface`
and `render_aci_json`.
- Fixed a bug in `create_calldata`/`to_sophia_value` - it can now handle negative
literals.
### Removed
## [3.0.0] - 2019-05-21
### Added
- `stateful` annotations are now properly enforced. Functions must be marked stateful
in order to update the state or spend tokens.
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
- `Crypto.ecverify_secp256k1` has been added.
### Changed
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
not working properly and has been fixed.
### Removed
- `let rec` has been removed from the language, it has never worked.
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
been completely removed from `aesophia`.
## [2.1.0] - 2019-04-11 ## [2.1.0] - 2019-04-11
### Added ### Added
- Stubs (not yet wired up) for compilation to FATE - Stubs (not yet wired up) for compilation to FATE
@@ -71,8 +35,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.1.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0 [2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0 [2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+139 -74
View File
@@ -30,14 +30,53 @@ generates the following JSON structure representing the contract interface:
``` json ``` json
{ {
"contract": { "contract": {
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"type_defs": [
{
"name": "answers",
"vars": [],
"typedef": {
"map": {
"key": "string",
"value": "int"
}
}
}
],
"functions": [ "functions": [
{ {
"arguments": [],
"name": "init", "name": "init",
"returns": "Answers.state", "arguments": [],
"returns": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"stateful": true "stateful": true
}, },
{ {
"name": "new_answer",
"arguments": [ "arguments": [
{ {
"name": "q", "name": "q",
@@ -48,36 +87,14 @@ generates the following JSON structure representing the contract interface:
"type": "int" "type": "int"
} }
], ],
"name": "new_answer",
"returns": { "returns": {
"map": [ "map": {
"string", "key": "string",
"int" "value": "int"
] }
}, },
"stateful": false "stateful": false
} }
],
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": "Answers.answers"
}
]
},
"type_defs": [
{
"name": "answers",
"typedef": {
"map": [
"string",
"int"
]
},
"vars": []
}
] ]
} }
} }
@@ -87,70 +104,118 @@ When that encoding is decoded the following include definition is generated:
``` ```
contract Answers = contract Answers =
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int) function new_answer : (string, int) => map(string, int)
``` ```
### Types ### Types
```erlang ``` erlang
-type aci_type() :: json | string. contract_string() = string() | binary()
-type json() :: jsx:json_term(). json_string() = binary()
-type json_text() :: binary().
``` ```
### Exports ### Exports
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()} #### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Generate the JSON encoding of the interface to a contract. The type definitions Types
and non-private functions are included in the JSON string.
#### render\_aci\_json(json() | json\_text()) -> string(). ``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate a contract interface This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
that can be included in another contract.
#### encode_contract(ContractString, Options) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
Options = [option()]
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode_contract(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
#### encode_type(TypeAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a type from the AST of the type.
#### encode_arg(ArgAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a function argument from the AST of the argument.
#### encode_stmt(StmtAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a statement from the AST of the statement.
#### encode_expr(ExprAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of an expression from the AST of the expression.
### Notes
The deprecated functions `aseo_aci:encode/2` and `aeso_aci:decode/1` are still available but should not be used.
### Example run ### Example run
This is an example of using the ACI generator from an Erlang shell. The file This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang ``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes"). 1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>} {ok,<<"contract Answers =\n\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful functio"...>>}
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract). 2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
{ok,[#{contract => {ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
#{functions => 3> file:write_file("aci_test.aci", Encoding).
[#{arguments => [],name => <<"init">>,
returns => <<"Answers.state">>,stateful => true},
#{arguments =>
[#{name => <<"q">>,type => <<"string">>},
#{name => <<"a">>,type => <<"int">>}],
name => <<"new_answer">>,
returns => #{<<"map">> => [<<"string">>,<<"int">>]},
stateful => false}],
name => <<"Answers">>,
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok ok
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI). 4> Decoded = aeso_aci:decode_contract(Encoding).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>} <<"contract Answers =\n function new_answer : (string, int) => map(string, int)\n">>
5> file:write_file("aci_test.include", InterfaceStub). 5> file:write_file("aci_test.include", Decoded).
ok ok
6> jsx:prettify(jsx:encode(JsonACI)). 6> jsx:prettify(Encoding).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>> <<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
``` ```
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description above. more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
+18 -2
View File
@@ -2,19 +2,35 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref, "f129887"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "e8253b0"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}} {tag, "2.8.0"}}}
]}. ]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! \n"}.
{provider_hooks, [{post, [{compile, escriptize}]}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
escriptize,
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
{"win32",
escriptize,
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
]}.
{dialyzer, [ {dialyzer, [
{warnings, [unknown]}, {warnings, [unknown]},
{plt_apps, all_deps}, {plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "3.1.0"}, {relx, [{release, {aesophia, "2.1.0"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+2 -2
View File
@@ -1,11 +1,11 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"f1298870e526f4e9330447d3a281af5ef4e06e17"}}, {ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}}, {ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
1}, 1},
{<<"base58">>, {<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git", {git,"https://github.com/aeternity/erl-base58.git",
+468 -235
View File
@@ -9,56 +9,100 @@
-module(aeso_aci). -module(aeso_aci).
-export([ contract_interface/2 %% Old deprecated interface.
, contract_interface/3 -export([encode/1,encode/2,decode/1]).
, render_aci_json/1 -export([encode_contract/1,encode_contract/2,decode_contract/1]).
-export([encode_func/1,encode_type/1,encode_arg/1,
encode_stmt/1,encode_expr/1]).
, json_encode_expr/1 %% Define records for the various typed syntactic forms. These make
, json_encode_type/1]). %% the code easier but don't seem to exist elsewhere. Unfortunately
%% sometimes the same typename is used with different fields.
-type aci_type() :: json | string. %% Top-level
-type json() :: jsx:json_term(). -record(contract, {ann,con,decls}).
-type json_text() :: binary(). %% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
%% External API %% Types
-spec contract_interface(aci_type(), string()) -> -record(app_t, {ann,id,fields}).
{ok, json() | string()} | {error, term()}. -record(tuple_t, {ann,args}).
contract_interface(Type, ContractString) -> -record(bytes_t, {ann,len}).
contract_interface(Type, ContractString, []). -record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
-spec contract_interface(aci_type(), string(), [term()]) -> %% Tokens
{ok, json() | string()} | {error, term()}. -record(arg, {ann,id,type}).
contract_interface(Type, ContractString, CompilerOpts) -> -record(id, {ann,name}).
do_contract_interface(Type, ContractString, CompilerOpts). -record(con, {ann,name}).
-record(qid, {ann,names}).
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
-spec render_aci_json(json() | json_text()) -> {ok, binary()}. %% Statements
render_aci_json(Json) -> -record(block, {ann,body}).
do_render_aci_json(Json). -record('if', {ann,test,then,else}). %Both statement and expression
-record(letval, {ann,pat,type,exp}).
-record(switch, {ann,arg,cases}).
-record('case', {ann,pat,body}).
-spec json_encode_expr(aeso_syntax:expr()) -> json(). %% Expressions
json_encode_expr(Expr) -> -record(bool, {ann,bool}).
encode_expr(Expr). -record(int, {ann,value}).
-record(string, {ann,bin}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(record, {ann,fields}). %Create a record
-record(field, {ann,name,value}). %A record field
-record(proj, {ann,value}). %?
-record(map, {ann,fields}). %Create a map
-record(map_get, {ann,field}).
-record(lam, {ann,args,body}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
-spec json_encode_type(aeso_syntax:type()) -> json(). %% The old deprecated interface.
json_encode_type(Type) ->
encode_type(Type).
%% Internal functions encode(C) -> encode_contract(C).
do_contract_interface(Type, Contract, Options) when is_binary(Contract) -> encode(C, Os) -> encode_contract(C, Os).
do_contract_interface(Type, binary_to_list(Contract), Options); decode(J) -> decode_contract(J).
do_contract_interface(Type, ContractString, Options) ->
%% encode_contract(ContractString) -> {ok,JSON} | {error,String}.
%% encode_contract(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
encode_contract(ContractString) ->
encode_contract(ContractString, []).
encode_contract(ContractString, Options) when is_binary(ContractString) ->
encode_contract(binary_to_list(ContractString), Options);
encode_contract(ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("Ast\n~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), %% aeso_ast:pp(Ast),
%% io:format("~p\n", [TypedAst]), TypedAst = aeso_ast_infer_types:infer(Ast, Options),
JArray = [ encode_contract(C) || C <- TypedAst ], %% io:format("Typed ast\n~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
case Type of %% We find and look at the last contract.
json -> {ok, JArray}; Contract = lists:last(TypedAst),
string -> do_render_aci_json(JArray) Cname = contract_name(Contract),
end Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>,
[{<<"name">>, do_encode_name(Cname)}] ++
Tdefs ++
[{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -75,251 +119,394 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")). list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract) -> %% do_encode_contract_typedefs(TypeDefs) -> [JSON].
C0 = #{name => encode_name(contract_name(Contract))}, %% Return a list of typedefs and state and event if they occur.
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ], do_encode_contract_typedefs(Tdefs) ->
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end, Fun = fun(T, {Ts,Ss,Es}) ->
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0), %% Only one state and event.
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1), case typedef_name(T) of
"state" -> {Ts,[do_encode_state_typedef(T)],Es};
"event" -> {Ts,Ss,[do_encode_event_typedef(T)]};
_Name -> {Ts ++ [do_encode_typedef(T)],Ss,Es}
end
end,
{Ts,Ss,Es} = lists:foldl(Fun, {[],[],[]}, Tdefs),
Ss ++ [{<<"type_defs">>, Ts}] ++ Es.
C1 = C0#{type_defs => Tdefs}, %% do_encode_state_typedef(StateTdef) -> JSON.
%% do_encode_event_typedef(EventTdef) -> JSON.
C2 = case Es of do_encode_state_typedef(State) ->
[] -> C1; Def = typedef_def(State),
[#{typedef := ET}] -> C1#{event => ET} {<<"state">>,do_encode_alias(Def)}.
end,
C3 = case Ss of do_encode_event_typedef(State) ->
[] -> C2; Def = typedef_def(State),
[#{typedef := ST}] -> C2#{state => ST} {<<"event">>,do_encode_alias(Def)}.
end,
Fdefs = [ encode_function(F) %% encode_func(TypedAST) -> JSON.
|| F <- sort_decls(contract_funcs(Contract)), %% Encode a function AST into a JSON structure.
not is_private(F) ],
#{contract => C3#{functions => Fdefs}}. encode_func(AST) ->
jsx:encode(do_encode_func(AST)).
%% do_encode_func(Function) -> JSONmap
%% Encode a function definition. Currently we are only interested in %% Encode a function definition. Currently we are only interested in
%% the interface and type. %% the interface and type.
encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name),
arguments => encode_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDef)};
encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) ->
#{name => encode_name(Name),
arguments => encode_anon_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDecl)}.
encode_anon_args(Types) -> do_encode_func(Fdef) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], Name = function_name(Fdef),
[ #{name => V, type => encode_type(T)} Args = function_args(Fdef),
|| {V, T} <- lists:zip(Anons, Types) ]. Type = function_type(Fdef),
[{<<"name">>, do_encode_name(Name)},
{<<"arguments">>, do_encode_args(Args)},
{<<"returns">>, do_encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_args(Args) -> [ encode_arg(A) || A <- Args ]. %% encode_arg(TypedAST) -> JSON.
%% Encode an argument AST into a JSON structure.
encode_arg({arg, _, Id, T}) -> encode_arg(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_arg(AST)).
type => encode_type(T)}.
encode_typedef(Type) -> %% do_encode_args(ArgASTs) -> [JSONmap].
%% do_encode_arg(ArgAST) -> JSONmap.
do_encode_args(Args) ->
[ do_encode_arg(A) || A <- Args ].
do_encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% encode_type(TypedAST) -> JSON.
%% Encode a type AST into a JSON structure.
encode_type(AST) ->
jsx:encode(do_encode_type(AST)).
%% do_encode_types([TypeAST]) -> [JSONmap].
%% do_encode_type(TypeAST) -> JsonMap.
do_encode_types(Types) ->
[ do_encode_type(T) || T <- Types ].
do_encode_type(#tvar{name=N}) -> do_encode_name(N);
do_encode_type(#id{name=N}) -> do_encode_name(N);
do_encode_type(#con{name=N}) -> do_encode_name(N);
do_encode_type(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_type(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_type(#tuple_t{args=As}) ->
Eas = do_encode_types(As),
[{<<"tuple">>,Eas}];
do_encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>,Len};
do_encode_type(#record_t{fields=Fs}) ->
Efs = do_encode_type_rec_fields(Fs),
[{<<"record">>,Efs}];
%% Special case lists and maps as they are built-in types.
do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
Ef = do_encode_type(F),
[{<<"list">>,Ef}];
do_encode_type(#app_t{id=#id{name="map"},fields=Fs}) ->
Ef = do_encode_type_mapo_field(Fs),
[{<<"map">>,Ef}];
%% Other applications.
do_encode_type(#app_t{id=Id,fields=Fs}) ->
Name = do_encode_type(Id),
Efs = do_encode_types(Fs),
[{Name,Efs}];
do_encode_type(#variant_t{cons=Cs}) ->
Ecs = do_encode_types(Cs),
[{<<"variant">>,Ecs}];
do_encode_type(#constr_t{con=C,args=As}) ->
Ec = do_encode_type(C),
Eas = do_encode_types(As),
[{Ec,Eas}];
do_encode_type(#fun_t{args=As,type=T}) ->
Eas = do_encode_types(As),
Et = do_encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
do_encode_name(Name) ->
list_to_binary(Name).
%% do_encode_type_rec_fields(Fields) -> [JSONmap].
%% do_encode_type_rec_field(Field) -> JSONmap.
%% Encode a record field type.
do_encode_type_rec_fields(Fs) ->
[ do_encode_type_rec_field(F) || F <- Fs ].
do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% do_encode_type_mapo_field(Field) -> JSONmap.
%% Two fields for one map type.
do_encode_type_mapo_field([K,V]) ->
[{<<"key">>,do_encode_type(K)},
{<<"value">>,do_encode_type(V)}].
%% do_encode_typedef(TypeDefAST) -> JSON.
do_encode_typedef(Type) ->
Name = typedef_name(Type), Name = typedef_name(Type),
Vars = typedef_vars(Type), Vars = typedef_vars(Type),
Def = typedef_def(Type), Def = typedef_def(Type),
#{name => encode_name(Name), [{<<"name">>, do_encode_name(Name)},
vars => encode_tvars(Vars), {<<"vars">>, do_encode_tvars(Vars)},
typedef => encode_type(Def)}. {<<"typedef">>, do_encode_alias(Def)}].
encode_tvars(Vars) -> do_encode_tvars(Vars) ->
[ #{name => encode_type(V)} || V <- Vars ]. [ do_encode_tvar(V) || V <- Vars ].
%% Encode type do_encode_tvar(#tvar{name=N}) ->
encode_type({tvar, _, N}) -> encode_name(N); [{<<"name">>, do_encode_name(N)}].
encode_type({id, _, N}) -> encode_name(N);
encode_type({con, _, N}) -> encode_name(N);
encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)};
encode_type({bytes_t, _, Len}) -> #{bytes => Len};
encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)};
encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)};
encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)};
encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
encode_type({alias_t, Type}) -> encode_type(Type);
encode_type({fun_t, _, _, As, T}) -> #{function =>
#{arguments => encode_types(As),
returns => encode_type(T)}}.
encode_types(Ts) -> [ encode_type(T) || T <- Ts ]. do_encode_alias(#alias_t{type=T}) ->
do_encode_type(T);
do_encode_alias(A) -> do_encode_type(A).
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ]. %% encode_stmt(StmtAST) -> JSON.
%% Encode a statement AST into a JSON structure.
encode_type_field({field_t, _, Id, T}) -> encode_stmt(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_stmt(AST)).
type => encode_type(T)}.
encode_name(Name) when is_list(Name) -> %% do_encode_stmt(StmtAST) -> JSONmap.
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
%% Encode Expr do_encode_stmt(#typed{expr=E}) -> %Ignore the type
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ]. do_encode_stmt(E);
do_encode_stmt(#block{body=Body}) ->
Eblock = [ do_encode_stmt(B) || B <- Body ],
[{<<"block">>,Eblock}];
do_encode_stmt(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_stmt(Then),
Eelse = do_encode_stmt(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_stmt(#letval{pat=Pat,exp=Exp}) ->
Epat = do_encode_expr(Pat),
Eexp = do_encode_expr(Exp),
[{<<"let">>,[{<<"pattern">>,Epat},{<<"expression">>,Eexp}]}];
do_encode_stmt(#switch{arg=Arg,cases=Cases}) ->
Earg = do_encode_expr(Arg),
Ecases = [ do_encode_stmt_case(Case) || Case <- Cases ],
[{<<"switch">>,[{<<"arg">>,Earg},{<<"cases">>,Ecases}]}];
do_encode_stmt(E) ->
do_encode_expr(E).
encode_expr({id, _, N}) -> encode_name(N); do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
encode_expr({con, _, N}) -> encode_name(N); Epat = do_encode_expr(Pat), %Patterns are expessions
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns)); Ebody = do_encode_stmt(Body),
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns)); [{<<"pattern">>,Epat},{<<"body">>,Ebody}].
encode_expr({typed, _, E}) -> encode_expr(E);
encode_expr({bool, _, B}) -> B;
encode_expr({int, _, V}) -> V;
encode_expr({string, _, S}) -> S;
encode_expr({tuple, _, As}) -> encode_exprs(As);
encode_expr({list, _, As}) -> encode_exprs(As);
encode_expr({bytes, _, B}) ->
Digits = byte_size(B),
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
#{Ef => Eas};
encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds));
encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ];
encode_expr({Op,_Ann}) ->
error({encode_expr_todo, Op}).
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ]. %% encode_expr(ExprAST) -> JSON.
%% Encode an expression AST into a JSON structure.
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) -> encode_expr(AST) ->
{encode_name(Fld), encode_expr(Val)}. jsx:encode(do_encode_expr(AST)).
do_render_aci_json(Json) -> %% do_encode_exprs(ExprASTs) -> [JSONmap].
Contracts = %% do_encode_expr(ExprAST) -> JSONmap.
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || #{contract := C} <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{name := Name, do_encode_exprs(Es) ->
type_defs := Ts0, [ do_encode_expr(E) || E <- Es ].
functions := Fs} = C) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
["contract"," ",io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. do_encode_expr(#typed{expr=E}) -> %Ignore the type
do_encode_expr(E);
do_encode_expr(#id{name=N}) -> do_encode_name(N);
do_encode_expr(#con{name=N}) -> do_encode_name(N);
do_encode_expr(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_expr(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_expr(#bool{bool=B}) -> B;
do_encode_expr(#int{value=V}) -> V;
do_encode_expr(#string{bin=B}) ->
[{<<"string">>,B}];
do_encode_expr(#bytes{bin=B}) -> B;
do_encode_expr(#tuple{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"tuple">>,Eas}];
do_encode_expr(#list{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"list">>,Eas}];
do_encode_expr(#record{fields=Fs}) -> %Create a record
Efs = do_encode_expr_rec_fields(Fs),
[{<<"create_record">>,Efs}];
do_encode_expr({record,_Ann,Rec,Fs}) -> %Update a record
Erec = do_encode_expr(Rec),
Efs = do_encode_expr_rec_fields(Fs),
[{<<"update_record">>,[Erec,Efs]}];
do_encode_expr(#lam{args=As,body=B}) ->
Eas = do_encode_args(As),
Eb = do_encode_stmt(B),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"body">>,Eb}]}];
do_encode_expr(#map{fields=Fs}) -> %Create a map
Efs = do_encode_expr_map_fields(Fs),
[{<<"create_map">>,Efs}];
do_encode_expr({map,_Ann,Map,Fs}) -> %Update a map
Emap = do_encode_expr(Map),
Efs = do_encode_expr_map_fields(Fs),
[{<<"update_map">>,[Emap,Efs]}];
do_encode_expr(#map_get{field=F}) ->
do_encode_expr(F);
do_encode_expr(#proj{value=V}) ->
do_encode_expr(V);
do_encode_expr(#app{func=F,args=As}) ->
Ef = do_encode_expr(F),
Eas = do_encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
do_encode_expr(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_expr(Then),
Eelse = do_encode_expr(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode_func(#{name := init}) -> []; %% do_encode_expr_rec_fields(Fields) -> [JSON].
decode_func(#{name := Name, arguments := As, returns := T}) -> %% do_encode_expr_rec_field(Field) -> JSON.
[" function", " ", io_lib:format("~s", [Name]), " : ", %% Encode a record field expression.
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) -> do_encode_expr_rec_fields(Fs) ->
Das = [ decode_arg(A) || A <- As ], [ do_encode_expr_rec_field(F) || F <- Fs ].
do_encode_expr_rec_field(#field{name=[N],value=V}) ->
[{<<"name">>,do_encode_expr(N)},
{<<"value">>,do_encode_expr(V)}].
%% do_encode_expr_map_fields(Fields) -> [JSON].
%% do_encode_expr_map_field(Field) -> JSON.
%% Encode a map field expression.
do_encode_expr_map_fields(Fs) ->
[ do_encode_expr_map_field(F) || F <- Fs ].
do_encode_expr_map_field({K,V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}];
do_encode_expr_map_field(#field{name=[K],value=V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}].
%% decode_contract(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode_contract(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
#{<<"name">> := Name, <<"type_defs">> := Ts, <<"functions">> := Fs} = C,
CS = ["contract"," ",io_lib:format("~s", [Name])," =\n",
do_decode_tdefs(Ts),
do_decode_funcs(Fs)],
list_to_binary(CS).
do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
do_decode_func(#{<<"name">> := <<"init">>}) -> [];
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
do_decode_args(As)," => ",do_decode_type(T),$\n].
do_decode_args(As) ->
Das = [ do_decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)]. [$(,lists:join(", ", Das),$)].
decode_arg(#{type := T}) -> decode_type(T). do_decode_arg(#{<<"type">> := T}) -> do_decode_type(T).
decode_types(Ets) -> do_decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ]. [ do_decode_type(Et) || Et <- Ets ].
decode_type(#{tuple := Ets}) -> do_decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets), Ts = do_decode_types(Ets),
[$(,lists:join(",", Ts),$)]; [$(,lists:join(", ", Ts),$)];
decode_type(#{record := Efs}) -> do_decode_type(#{<<"record">> := Efs}) ->
Fs = decode_fields(Efs), Fs = do_decode_type_rec_fields(Efs),
[${,lists:join(",", Fs),$}]; [${,lists:join(", ", Fs),$}];
decode_type(#{list := [Et]}) -> do_decode_type(#{<<"list">> := Et}) ->
T = decode_type(Et), T = do_decode_type(Et),
["list",$(,T,$)]; ["list",$(,T,$)];
decode_type(#{map := Ets}) -> do_decode_type(#{<<"map">> := Et}) ->
Ts = decode_types(Ets), T = do_decode_type_map(Et),
["map",$(,lists:join(",", Ts),$)]; ["map",$(,T,$)];
decode_type(#{bytes := Len}) -> do_decode_type(#{<<"variant">> := Ets}) ->
["bytes(", integer_to_list(Len), ")"]; Ts = do_decode_types(Ets),
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts); lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) -> do_decode_type(Econs) when is_map(Econs) -> %General constructor
[decode_type(#{tuple => Args}), " => ", decode_type(R)]; %% io:format("~p\n", [Econs]),
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs), [{Ec,Ets}] = maps:to_list(Econs),
AppName = decode_name(Ec), C = do_decode_name(Ec),
AppArgs = decode_types(Ets), Ts = do_decode_types(Ets),
case AppArgs of [C,$(,lists:join(", ", Ts),$)];
[] -> [AppName]; do_decode_type(T) -> %Just raw names.
_ -> [AppName,$(,lists:join(", ", AppArgs),$)] do_decode_name(T).
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En); do_decode_name(En) ->
decode_name(En) when is_binary(En) -> binary_to_list(En). binary_to_list(En).
decode_fields(Efs) -> do_decode_type_rec_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ]. [ do_decode_type_rec_field(Ef) || Ef <- Efs ].
decode_field(#{name := En, type := Et}) -> do_decode_type_rec_field(#{<<"name">> := En,<<"type">> := Et}) ->
Name = decode_name(En), Name = do_decode_name(En),
Type = decode_type(Et), Type = do_decode_type(Et),
[Name," : ",Type]. [Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString]. do_decode_type_map(#{<<"key">> := Ek,<<"value">> := Ev}) ->
Key = do_decode_type(Ek),
Value = do_decode_type(Ev),
[Key,", ",Value].
%% do_decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the %% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants. %% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ]. do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) -> do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
TypeDef = decode_type(T), [" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
DefType = decode_deftype(T), " = ",do_decode_type(T),$\n].
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_deftype(#{record := _Efs}) -> "record"; do_decode_tvars([]) -> []; %No tvars, no parentheses
decode_deftype(#{variant := _}) -> "datatype"; do_decode_tvars(Vs) ->
decode_deftype(_T) -> "type". Dvs = [ do_decode_tvar(V) || V <- Vs ],
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)]. [$(,lists:join(", ", Dvs),$)].
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). do_decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%% #contract{Ann, Con, [Declarations]}. %% #contract{Ann, Con, [Declarations]}.
contract_name({contract, _, {con, _, Name}, _}) -> Name; contract_name(#contract{con=#con{name=N}}) -> N.
contract_name({namespace, _, {con, _, Name}, _}) -> Name.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_fun(D)]. [ D || D <- Decls, is_record(D, letfun) ].
contract_types({C, _, _, Decls}) when C == contract; C == namespace -> contract_types(#contract{decls=Decls}) ->
[ D || D <- Decls, is_type(D) ]. [ D || D <- Decls, is_record(D, type_def) ].
is_fun({letfun, _, _, _, _, _}) -> true; %% To keep dialyzer happy and quiet.
is_fun({fun_decl, _, _, _}) -> true; %% namespace_name(#namespace{con=#con{name=N}}) -> N.
is_fun(_) -> false. %%
%% namespace_funcs(#namespace{decls=Decls}) ->
is_type({type_def, _, _, _, _}) -> true; %% [ D || D <- Decls, is_record(D, letfun) ].
is_type(_) -> false. %%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
sort_decls(Ds) -> sort_decls(Ds) ->
Sort = fun (D1, D2) -> Sort = fun (D1, D2) ->
@@ -328,12 +515,58 @@ sort_decls(Ds) ->
end, end,
lists:sort(Sort, Ds). lists:sort(Sort, Ds).
%% #letfun{Ann, Id, [Arg], Type, Typedef}.
is_private(Node) -> aeso_syntax:get_ann(private, Node, false). function_name(#letfun{id=#id{name=N}}) -> N.
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name. function_args(#letfun{args=Args}) -> Args.
typedef_vars({type_def, _, _, Vars, _}) -> Vars. function_type(#letfun{type=Type}) -> Type.
typedef_def({type_def, _, _, _, Def}) -> Def. is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
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.
parse_error(Pos, ErrorString) ->
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
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]).
+61 -162
View File
@@ -98,14 +98,11 @@
-type scope() :: #scope{}. -type scope() :: #scope{}.
-record(env, -record(env,
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() } { scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}] , vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()] , typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global , fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname() , namespace = [] :: qname()
, in_pattern = false :: boolean()
, stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id()
}). }).
-type env() :: #env{}. -type env() :: #env{}.
@@ -199,7 +196,7 @@ bind_state(Env) ->
false -> {id, Ann, "event"} %% will cause type error if used(?) false -> {id, Ann, "event"} %% will cause type error if used(?)
end, end,
Env1 = bind_funs([{"state", State}, Env1 = bind_funs([{"state", State},
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env), {"put", {fun_t, Ann, [], [State], Unit}}], Env),
%% We bind Chain.event in a local 'Chain' namespace. %% We bind Chain.event in a local 'Chain' namespace.
pop_scope( pop_scope(
@@ -359,12 +356,11 @@ global_env() ->
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end, Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
Fun1 = fun(S, T) -> Fun([S], T) end, Fun1 = fun(S, T) -> Fun([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end, TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"}, SignId = {id, Ann, "signature"},
SignDef = {bytes, Ann, <<0:64/unit:8>>}, SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}}, Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end, SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
TTL = {qid, Ann, ["Chain", "ttl"]}, TTL = {qid, Ann, ["Chain", "ttl"]},
Fee = Int, Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
@@ -382,7 +378,7 @@ global_env() ->
%% Abort %% Abort
{"abort", Fun1(String, A)}]) {"abort", Fun1(String, A)}])
, types = MkDefs( , types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0}, [{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
{"hash", {[], {alias_t, Bytes(32)}}}, {"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}}, {"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0}, {"bits", 0},
@@ -393,7 +389,7 @@ global_env() ->
ChainScope = #scope ChainScope = #scope
{ funs = MkDefs( { funs = MkDefs(
%% Spend transaction. %% Spend transaction.
[{"spend", StateFun([Address, Int], Unit)}, [{"spend", Fun([Address, Int], Unit)},
%% Chain environment %% Chain environment
{"balance", Fun1(Address, Int)}, {"balance", Fun1(Address, Int)},
{"block_hash", Fun1(Int, Int)}, {"block_hash", Fun1(Int, Int)},
@@ -407,7 +403,7 @@ global_env() ->
ContractScope = #scope ContractScope = #scope
{ funs = MkDefs( { funs = MkDefs(
[{"address", Address}, [{"address", Address},
{"creator", Address}, %% {"owner", Int}, %% Not in EVM
{"balance", Int}]) }, {"balance", Int}]) },
CallScope = #scope CallScope = #scope
@@ -423,13 +419,11 @@ global_env() ->
{ funs = MkDefs( { funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))}, [{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"query_fee", Fun([Oracle(Q, R)], Fee)}, {"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))}, {"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)}, {"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)}, {"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
{"extend", SignFun([Oracle(Q, R), TTL], Unit)}, {"extend", SignFun([Oracle(Q, R), TTL], Unit)},
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}, {"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}]) },
{"check", Fun([Oracle(Q, R)], Bool)},
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
AENSScope = #scope AENSScope = #scope
{ funs = MkDefs( { funs = MkDefs(
@@ -487,9 +481,7 @@ global_env() ->
%% Conversion %% Conversion
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}]) },
{"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)}]) },
#env{ scopes = #env{ scopes =
#{ [] => TopScope #{ [] => TopScope
@@ -514,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) -> infer(Contracts) ->
infer(Contracts, []). infer(Contracts, []).
-type option() :: return_env | dont_unfold. -type option() :: return_env.
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_env(). init_env(_Options) -> global_env().
@@ -526,7 +518,7 @@ infer(Contracts, Options) ->
Env = init_env(Options), Env = init_env(Options),
create_options(Options), create_options(Options),
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
{Env1, Decls} = infer1(Env, Contracts, [], Options), {Env1, Decls} = infer1(Env, Contracts, []),
case proplists:get_value(return_env, Options, false) of case proplists:get_value(return_env, Options, false) of
false -> Decls; false -> Decls;
true -> {Env1, Decls} true -> {Env1, Decls}
@@ -535,22 +527,21 @@ infer(Contracts, Options) ->
clean_up_ets() clean_up_ets()
end. end.
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer1(Env, [], Acc) -> {Env, lists:reverse(Acc)};
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) ->
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code),
Contract1 = {contract, Ann, ConName, Code1}, Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options); infer1(Env3, Rest, [Contract1 | Acc]);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) ->
check_scope_name_clash(Env, namespace, Name), check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code),
Namespace1 = {namespace, Ann, Name, Code1}, Namespace1 = {namespace, Ann, Name, Code1},
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options). infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]).
check_scope_name_clash(Env, Kind, Name) -> check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of case get_scope(Env, qname(Name)) of
@@ -561,16 +552,13 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. end.
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer_contract_top(Env, Kind, Defs0) ->
infer_contract_top(Env, Kind, Defs0, Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
{Env1, Defs1} = infer_contract(Env, Kind, Defs), {Env1, Defs1} = infer_contract(Env, Kind, Defs),
case proplists:get_value(dont_unfold, Options, false) of Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
true -> {Env1, Defs1}; Defs2 = unfold_record_types(Env2, Defs1),
false -> Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), {Env2, Defs2}.
{Env2, unfold_record_types(Env2, Defs1)}
end.
%% TODO: revisit %% TODO: revisit
infer_constant({letval, Attrs,_Pattern, Type, E}) -> infer_constant({letval, Attrs,_Pattern, Type, E}) ->
@@ -609,17 +597,15 @@ infer_contract(Env, What, Defs) ->
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]), %% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
{Env4, TypeDefs ++ Decls ++ Defs1}. {Env4, TypeDefs ++ Decls ++ Defs1}.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env = #env{ namespace = Ns }, Defs) -> check_typedefs(Env, Defs) ->
create_type_errors(), create_type_errors(),
GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end, GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end,
TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]), TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Ns, Def) end, TypeMap), DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap),
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
{Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []), {Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []),
destroy_and_report_type_errors(Env), destroy_and_report_type_errors(Env),
@@ -742,23 +728,25 @@ check_event(Env, "event", Ann, Def) ->
check_event(_Env, _Name, _Ann, Def) -> Def. check_event(_Env, _Name, _Ann, Def) -> Def.
check_event_con(Env, {constr_t, Ann, Con, Args}) -> check_event_con(Env, {constr_t, Ann, Con, Args}) ->
IsIndexed = fun(T) -> IsIndexed = fun(T) -> case aeso_syntax:get_ann(indexed, T, false) of
T1 = unfold_types_in_type(Env, T), true -> indexed;
%% `indexed` is optional but if used it has to be correctly used false -> notindexed
case {is_word_type(T1), is_string_type(T1), aeso_syntax:get_ann(indexed, T, false)} of end end,
{true, _, _} -> indexed;
{false, true, true} -> type_error({indexed_type_must_be_word, T, T1});
{false, true, _} -> notindexed;
{false, false, _} -> type_error({event_arg_type_word_or_string, T, T1}), error
end
end,
Indices = lists:map(IsIndexed, Args), Indices = lists:map(IsIndexed, Args),
Indexed = [ T || T <- Args, IsIndexed(T) == indexed ], Indexed = [ T || T <- Args, IsIndexed(T) == indexed ],
NonIndexed = Args -- Indexed, NonIndexed = Args -- Indexed,
[ check_event_arg_type(Env, Ix, Type) || {Ix, Type} <- lists:zip(Indices, Args) ],
[ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ], [ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ],
[ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ], [ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ],
{constr_t, [{indices, Indices} | Ann], Con, Args}. {constr_t, [{indices, Indices} | Ann], Con, Args}.
check_event_arg_type(Env, Ix, Type0) ->
Type = unfold_types_in_type(Env, Type0),
case Ix of
indexed -> [ type_error({indexed_type_must_be_word, Type0, Type}) || not is_word_type(Type) ];
notindexed -> [ type_error({payload_type_must_be_string, Type0, Type}) || not is_string_type(Type) ]
end.
%% Not so nice. %% Not so nice.
is_word_type({id, _, Name}) -> is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]); lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
@@ -805,7 +793,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
end; end;
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) -> check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
Defs = [ maps:get(X, Funs) || X <- Xs ], Defs = [ maps:get(X, Funs) || X <- Xs ],
{TypeSigs, Defs1} = infer_letrec(Env, Defs), {TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
Env1 = bind_funs(TypeSigs, Env), Env1 = bind_funs(TypeSigs, Env),
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc). check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
@@ -847,7 +835,7 @@ check_special_funs(_, _) -> ok.
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}. typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
infer_letrec(Env, Defs) -> infer_letrec(Env, {letrec, Attrs, Defs}) ->
create_constraints(), create_constraints(),
Funs = [{Name, fresh_uvar(A)} Funs = [{Name, fresh_uvar(A)}
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs], || {letfun, _, {id, A, Name}, _, _, _} <- Defs],
@@ -867,12 +855,9 @@ infer_letrec(Env, Defs) ->
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]), TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
NewDefs = instantiate([D || {_, D} <- Inferred]), NewDefs = instantiate([D || {_, D} <- Inferred]),
[print_typesig(S) || S <- TypeSigs], [print_typesig(S) || S <- TypeSigs],
{TypeSigs, NewDefs}. {TypeSigs, {letrec, Attrs, NewDefs}}.
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) -> infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
current_function = Fun },
check_unique_arg_names(Fun, Args),
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args], ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
ExpectedType = check_type(Env, arg_type(What)), ExpectedType = check_type(Env, arg_type(What)),
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType), NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
@@ -883,13 +868,6 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo
{{Name, TypeSig}, {{Name, TypeSig},
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}. {letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
check_unique_arg_names(Fun, Args) ->
Name = fun({arg, _, {id, _, X}, _}) -> X end,
Names = lists:map(Name, Args),
Dups = lists:usort(Names -- lists:usort(Names)),
[ type_error({repeated_arg, Fun, Arg}) || Arg <- Dups ],
ok.
print_typesig({Name, TypeSig}) -> print_typesig({Name, TypeSig}) ->
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]). ?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
@@ -913,7 +891,6 @@ lookup_name(Env, As, Id, Options) ->
{Id, fresh_uvar(As)}; {Id, fresh_uvar(As)};
{QId, {_, Ty}} -> {QId, {_, Ty}} ->
Freshen = proplists:get_value(freshen, Options, false), Freshen = proplists:get_value(freshen, Options, false),
check_stateful(Env, Id, Ty),
Ty1 = case Ty of Ty1 = case Ty of
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty)); {type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
_ when Freshen -> freshen_type(Ty); _ when Freshen -> freshen_type(Ty);
@@ -922,61 +899,6 @@ lookup_name(Env, As, Id, Options) ->
{set_qname(QId, Id), Ty1} {set_qname(QId, Id), Ty1}
end. end.
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
case aeso_syntax:get_ann(stateful, Type, false) of
false -> ok;
true ->
type_error({stateful_not_allowed, Id, Fun})
end;
check_stateful(_Env, _Id, _Type) -> ok.
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
%% works since the user can't create functions with named arguments.
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
case Default of
{int, _, 0} -> ok;
_ -> type_error({value_arg_not_allowed, Default, Fun})
end;
check_stateful_named_arg(_, _, _) -> ok.
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env, Defs) ->
Top = Env#env.namespace,
GetState = Top ++ ["state"],
SetState = Top ++ ["put"],
Init = Top ++ ["init"],
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of
false -> ok; %% No init, so nothing to check
_ ->
[ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ],
[ type_error({init_depends_on_state, put, Chain})
|| Chain <- get_call_chains(Deps, Init, SetState) ],
ok
end.
%% Compute all paths (not sharing intermediate nodes) from Start to Stop in Graph.
get_call_chains(Graph, Start, Stop) ->
get_call_chains(Graph, #{}, queue:from_list([{Start, [], []}]), Stop, []).
get_call_chains(Graph, Visited, Queue, Stop, Acc) ->
case queue:out(Queue) of
{empty, _} -> lists:reverse(Acc);
{{value, {Stop, Ann, Path}}, Queue1} ->
get_call_chains(Graph, Visited, Queue1, Stop, [lists:reverse([{Stop, Ann} | Path]) | Acc]);
{{value, {Node, Ann, Path}}, Queue1} ->
case maps:is_key(Node, Visited) of
true -> get_call_chains(Graph, Visited, Queue1, Stop, Acc);
false ->
Calls = maps:get(Node, Graph, []),
NewQ = queue:from_list([{New, Ann1, [{Node, Ann} | Path]} || {New, Ann1} <- Calls]),
get_call_chains(Graph, Visited#{Node => true}, queue:join(Queue1, NewQ), Stop, Acc)
end
end.
check_expr(Env, Expr, Type) -> check_expr(Env, Expr, Type) ->
E = {typed, _, _, Type1} = infer_expr(Env, Expr), E = {typed, _, _, Type1} = infer_expr(Env, Expr),
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}), unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
@@ -986,8 +908,6 @@ infer_expr(_Env, Body={bool, As, _}) ->
{typed, As, Body, {id, As, "bool"}}; {typed, As, Body, {id, As, "bool"}};
infer_expr(_Env, Body={int, As, _}) -> infer_expr(_Env, Body={int, As, _}) ->
{typed, As, Body, {id, As, "int"}}; {typed, As, Body, {id, As, "int"}};
infer_expr(_Env, Body={char, As, _}) ->
{typed, As, Body, {id, As, "char"}};
infer_expr(_Env, Body={string, As, _}) -> infer_expr(_Env, Body={string, As, _}) ->
{typed, As, Body, {id, As, "string"}}; {typed, As, Body, {id, As, "string"}};
infer_expr(_Env, Body={bytes, As, Bin}) -> infer_expr(_Env, Body={bytes, As, Bin}) ->
@@ -1015,6 +935,8 @@ infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon -> infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
{QName, Type} = lookup_name(Env, As, Id, [freshen]), {QName, Type} = lookup_name(Env, As, Id, [freshen]),
{typed, As, QName, Type}; {typed, As, QName, Type};
infer_expr(Env, {unit, As}) ->
infer_expr(Env, {tuple, As, []});
infer_expr(Env, {tuple, As, Cpts}) -> infer_expr(Env, {tuple, As, Cpts}) ->
NewCpts = [infer_expr(Env, C) || C <- Cpts], NewCpts = [infer_expr(Env, C) || C <- Cpts],
CptTypes = [T || {typed, _, _, T} <- NewCpts], CptTypes = [T || {typed, _, _, T} <- NewCpts],
@@ -1068,7 +990,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
constrain([ #record_create_constraint{ constrain([ #record_create_constraint{
record_t = RecordType1, record_t = RecordType1,
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ], fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
context = Attrs } || not Env#env.in_pattern ] ++ context = Attrs } ] ++
[begin [begin
[{proj, _, FieldName}] = LV, [{proj, _, FieldName}] = LV,
#field_constraint{ #field_constraint{
@@ -1137,7 +1059,6 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) -> infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E), CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
check_stateful_named_arg(Env, Id, E),
add_named_argument_constraint( add_named_argument_constraint(
#named_argument_constraint{ #named_argument_constraint{
args = NamedArgs, args = NamedArgs,
@@ -1198,7 +1119,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
[] -> ok; [] -> ok;
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)}) Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
end, end,
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }), NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern), NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
NewBranch = check_expr(NewEnv, Branch, SwitchType), NewBranch = check_expr(NewEnv, Branch, SwitchType),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
@@ -1207,11 +1128,12 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType) %% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
infer_block(_Env, Attrs, [], BlockType) -> infer_block(_Env, Attrs, [], BlockType) ->
error({impossible, empty_block, Attrs, BlockType}); error({impossible, empty_block, Attrs, BlockType});
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), NewDef = infer_letfun(Env, Def),
FunT = freshen_type(typesig_to_fun_t(TypeSig)), [NewDef|infer_block(Env, Attrs, Rest, BlockType)];
NewE = bind_var({id, Ann, Name}, FunT, Env), infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)]; NewDef = infer_letrec(Env, Def),
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) -> infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}), NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} = {'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
@@ -1256,8 +1178,6 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
free_vars({int, _, _}) -> free_vars({int, _, _}) ->
[]; [];
free_vars({char, _, _}) ->
[];
free_vars({string, _, _}) -> free_vars({string, _, _}) ->
[]; [];
free_vars({bool, _, _}) -> free_vars({bool, _, _}) ->
@@ -1854,8 +1774,8 @@ instantiate(E) ->
instantiate1(dereference(E)). instantiate1(dereference(E)).
instantiate1({uvar, Attr, R}) -> instantiate1({uvar, Attr, R}) ->
Next = proplists:get_value(next, ets_lookup(type_vars, next), 0), Next = proplists:get_value(next, ets_lookup(type_vars, next), 1),
TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)}, TVar = {tvar, Attr, "'" ++ integer_to_list(Next)},
ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]), ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]),
TVar; TVar;
instantiate1({fun_t, Ann, Named, Args, Ret}) -> instantiate1({fun_t, Ann, Named, Args, Ret}) ->
@@ -1875,12 +1795,6 @@ instantiate1([A|B]) ->
instantiate1(X) -> instantiate1(X) ->
X. X.
integer_to_tvar(X) when X < 26 ->
[$a + X];
integer_to_tvar(X) ->
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
%% Save unification failures for error messages. %% Save unification failures for error messages.
cannot_unify(A, B, When) -> cannot_unify(A, B, When) ->
@@ -1893,7 +1807,7 @@ create_type_errors() ->
ets_new(type_errors, [bag]). ets_new(type_errors, [bag]).
destroy_and_report_type_errors(Env) -> destroy_and_report_type_errors(Env) ->
Errors = lists:reverse(ets_tab2list(type_errors)), Errors = ets_tab2list(type_errors),
%% io:format("Type errors now: ~p\n", [Errors]), %% io:format("Type errors now: ~p\n", [Errors]),
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ], PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors), ets_delete(type_errors),
@@ -2009,21 +1923,6 @@ pp_error({include, {string, Pos, Name}}) ->
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]); [Name, pp_loc(Pos)]);
pp_error({repeated_arg, Fun, Arg}) ->
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
[Arg, pp(Fun), pp_loc(Fun)]);
pp_error({stateful_not_allowed, Id, Fun}) ->
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp(Id), pp_loc(Id), pp(Fun)]);
pp_error({value_arg_not_allowed, Value, Fun}) ->
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
[if Which == put -> "write"; true -> "read" end,
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]);
pp_error(Err) -> pp_error(Err) ->
io_lib:format("Unknown error: ~p\n", [Err]). io_lib:format("Unknown error: ~p\n", [Err]).
File diff suppressed because it is too large Load Diff
+23 -43
View File
@@ -102,6 +102,13 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
QName = aeso_icode:qualify(Name, Icode), QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, 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) ->
%% OBS! This code ignores the letrec structure of the source,
%% because the back end treats ALL declarations as recursive! We
%% need to decide whether to (a) modify the back end to respect
%% the letrec structure, or (b) (preferably) modify the front end
%% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode; contract_to_icode([], Icode) -> Icode;
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) -> contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode); contract_to_icode(Code, Icode);
@@ -145,7 +152,6 @@ ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) -> ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left; prim_gas_left;
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address; ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address }; ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin; ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller; ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
@@ -173,7 +179,8 @@ ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
%% Abort %% Abort
ast_body(?id_app("abort", [String], _, _), Icode) -> ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, abort} },
args = [ast_body(String, Icode)] };
%% Authentication %% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
@@ -217,17 +224,6 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0}, prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode))); [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'}); ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'}); ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'}); ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
@@ -369,11 +365,13 @@ ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
%% Strings %% Strings
%% -- String length %% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_length} },
args = [ast_body(String, Icode)] };
%% -- String concat %% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_concat} },
args = [ast_body(String1, Icode), ast_body(String2, Icode)] };
%% -- String hash (sha3) %% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
@@ -412,12 +410,6 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) -> ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]); builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
@@ -520,8 +512,6 @@ ast_body({switch,_,A,Cases}, Icode) ->
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) -> ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode), #switch{expr=ast_body(E, Icode),
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]}; cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
ast_body({block,_,[]}, _Icode) -> ast_body({block,_,[]}, _Icode) ->
#tuple{cpts=[]}; #tuple{cpts=[]};
ast_body({block,_,[E]}, Icode) -> ast_body({block,_,[E]}, Icode) ->
@@ -589,30 +579,19 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
_ when not Monomorphic -> _ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type}); gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
OtherType -> string ->
Neg = case Op of Neg = case Op of
'==' -> fun(X) -> X end; '==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare, Ann, Op, Type})
end, end,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
Builtin = args = [ast_body(A, Icode), ast_body(B, Icode)] });
case OtherType of _ -> gen_error({cant_compare, Ann, Op, Type})
string ->
builtin_call(str_equal, Args);
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]);
_ -> gen_error({cant_compare, Ann, Op, Type})
end;
_ ->
gen_error({cant_compare, Ann, Op, Type})
end,
Neg(Builtin)
end; end;
ast_binop('++', _, A, B, Icode) -> ast_binop('++', _, A, B, Icode) ->
builtin_call(list_concat, [ast_body(A, Icode), ast_body(B, Icode)]); #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -718,7 +697,7 @@ ast_typerep({qid, _, Name}, Icode) ->
ast_typerep({con, _, _}, _) -> ast_typerep({con, _, _}, _) ->
word; %% Contract type word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) -> ast_typerep({bytes_t, _, Len}, _) ->
bytes_t(Len); {bytes, Len};
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) -> ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ], ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode); lookup_type_id(Name, ArgReps, Icode);
@@ -747,8 +726,7 @@ ttl_t(Icode) ->
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode). ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
sign_t() -> bytes_t(64). sign_t() -> bytes_t(64).
bytes_t(Len) when Len =< 32 -> word; bytes_t(Len) -> {bytes, Len}.
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
get_signature_arg(Args0) -> get_signature_arg(Args0) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
@@ -782,6 +760,8 @@ type_value({list, A}) ->
type_value({tuple, As}) -> type_value({tuple, As}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG }, #tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG },
#list{ elems = [ type_value(A) || A <- As ] }] }; #list{ elems = [ type_value(A) || A <- As ] }] };
type_value({bytes, Len}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_BYTES_TAG }, #integer{ value = Len }] };
type_value({variant, Cs}) -> type_value({variant, Cs}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG }, #tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG },
#list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] }; #list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] };
+24 -33
View File
@@ -18,7 +18,6 @@
, to_sophia_value/4 , to_sophia_value/4
, to_sophia_value/5 , to_sophia_value/5
, decode_calldata/3 , decode_calldata/3
, parse/2
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -76,14 +75,23 @@ file(File, Options) ->
end. end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(Contract, Options) -> from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) #{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -96,26 +104,6 @@ from_string(Backend, ContractString, Options) ->
%% General programming errors in the compiler just signal error. %% General programming errors in the compiler just signal error.
end. end.
from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}};
from_string1(fate, ContractString, Options) ->
Ast = parse(ContractString, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
FCode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
{ok, aeso_fcode_to_fate:compile(FCode, Options)}.
-spec string_to_icode(string(), [option()]) -> map(). -spec string_to_icode(string(), [option()]) -> map().
string_to_icode(ContractString, Options) -> string_to_icode(ContractString, Options) ->
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
@@ -200,7 +188,7 @@ insert_call_function(Code, FunName, Args, Options) ->
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), lists:duplicate(Ind, " "),
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n" "function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]). ]).
-spec insert_init_function(string(), options()) -> string(). -spec insert_init_function(string(), options()) -> string().
@@ -276,12 +264,12 @@ translate_vm_value(word, {id, _, "address"}, N) -> address_l
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
translate_vm_value(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1}; translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N}); translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 -> translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>}; {bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({tuple, _}, {bytes_t, _, Len}, Val) -> translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)}; {bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S}; translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) -> translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
@@ -412,10 +400,13 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% Translate an icode value (error if not value) to an Erlang term that can be %% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary(). %% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N; icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) -> icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>, <<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str; Str;
icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 ->
Value;
icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32->
list_to_tuple([W || {integer, W} <- Words]);
icode_to_term({list, T}, {list, Vs}) -> icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ]; [ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) -> icode_to_term({tuple, Ts}, {tuple, Vs}) ->
File diff suppressed because it is too large Load Diff
-2
View File
@@ -343,8 +343,6 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
%% Environment primitives %% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) -> assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)]; [i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) -> assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)]; [i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) -> assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
+299
View File
@@ -0,0 +1,299 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Fate backend for Sophia compiler
%%% @end
%%% Created : 11 Jan 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_fate).
-include("aeso_icode.hrl").
-export([compile/2]).
%% -- Preamble ---------------------------------------------------------------
-define(TODO(What), error({todo, ?FILE, ?LINE, ?FUNCTION_NAME, What})).
-define(i(__X__), {immediate, __X__}).
-define(a, {stack, 0}).
-record(env, { args = [], stack = [], tailpos = true }).
%% -- Debugging --------------------------------------------------------------
%% debug(Options, Fmt) -> debug(Options, Fmt, []).
debug(Options, Fmt, Args) ->
case proplists:get_value(debug, Options, true) of
true -> io:format(Fmt, Args);
false -> ok
end.
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(ICode, Options) ->
#{ contract_name := _ContractName,
state_type := _StateType,
functions := Functions } = ICode,
SFuns = functions_to_scode(Functions, Options),
SFuns1 = optimize_scode(SFuns, Options),
to_basic_blocks(SFuns1, Options).
functions_to_scode(Functions, Options) ->
maps:from_list(
[ {list_to_binary(Name), function_to_scode(Name, Args, Body, Type, Options)}
|| {Name, _Ann, Args, Body, Type} <- Functions, Name /= "init" ]). %% TODO: skip init for now
function_to_scode(Name, Args, Body, Type, Options) ->
debug(Options, "Compiling ~p ~p : ~p ->\n ~p\n", [Name, Args, Type, Body]),
ArgTypes = [ icode_type_to_fate(T) || {_, T} <- Args ],
ResType = icode_type_to_fate(Type),
SCode = to_scode(init_env(Args), Body),
debug(Options, " scode: ~p\n", [SCode]),
{{ArgTypes, ResType}, SCode}.
%% -- Types ------------------------------------------------------------------
%% TODO: the Fate types don't seem to be specified anywhere...
icode_type_to_fate(word) -> integer;
icode_type_to_fate(string) -> string;
icode_type_to_fate({tuple, Types}) ->
{tuple, lists:map(fun icode_type_to_fate/1, Types)};
icode_type_to_fate({list, Type}) ->
{list, icode_type_to_fate(Type)};
icode_type_to_fate(typerep) -> typerep;
icode_type_to_fate(Type) -> ?TODO(Type).
%% -- Phase I ----------------------------------------------------------------
%% Icode to structured assembly
%% -- Environment functions --
init_env(Args) ->
#env{ args = Args, stack = [], tailpos = true }.
push_env(Type, Env) ->
Env#env{ stack = [{"_", Type} | Env#env.stack] }.
notail(Env) -> Env#env{ tailpos = false }.
lookup_var(#env{ args = Args, stack = S }, X) ->
case {keyfind_index(X, 1, S), keyfind_index(X, 1, Args)} of
{false, false} -> false;
{false, Arg} -> {arg, Arg};
{Local, _} -> {stack, Local}
end.
%% -- The compiler --
to_scode(_Env, #integer{ value = N }) ->
[aeb_fate_code:push(?i(N))]; %% Doesn't exist (yet), translated by desugaring
to_scode(Env, #var_ref{name = X}) ->
case lookup_var(Env, X) of
false -> error({unbound_variable, X, Env});
{stack, N} -> [aeb_fate_code:dup(?i(N))];
{arg, N} -> [aeb_fate_code:push({arg, N})]
end;
to_scode(Env, #binop{ op = Op, left = A, right = B }) ->
[ to_scode(notail(Env), B)
, to_scode(push_env(binop_type_r(Op), Env), A)
, binop_to_scode(Op) ];
to_scode(Env, #ifte{decision = Dec, then = Then, else = Else}) ->
[ to_scode(notail(Env), Dec)
, {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
to_scode(_Env, Icode) -> ?TODO(Icode).
%% -- Operators --
binop_types('+') -> {word, word};
binop_types('-') -> {word, word};
binop_types('==') -> {word, word};
binop_types(Op) -> ?TODO(Op).
%% binop_type_l(Op) -> element(1, binop_types(Op)).
binop_type_r(Op) -> element(2, binop_types(Op)).
binop_to_scode('+') -> add_a_a_a(); %% Optimization introduces other variants
binop_to_scode('-') -> sub_a_a_a();
binop_to_scode('==') -> eq_a_a_a().
% binop_to_scode(Op) -> ?TODO(Op).
add_a_a_a() -> aeb_fate_code:add(?a, ?a, ?a).
sub_a_a_a() -> aeb_fate_code:sub(?a, ?a, ?a).
eq_a_a_a() -> aeb_fate_code:eq(?a, ?a, ?a).
%% -- Phase II ---------------------------------------------------------------
%% Optimize
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
flatten_s({ifte, Then, Else}) -> {ifte, flatten(Then), flatten(Else)};
flatten_s(I) -> I.
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
Code0 = flatten(Code),
debug(Options, "Optimizing ~s\n", [Name]),
debug(Options, " original : ~p\n", [Code0]),
Code1 = simplify(Code0),
debug(Options, " simplified: ~p\n", [Code1]),
Code2 = desugar(Code1),
debug(Options, " desugared : ~p\n", [Code2]),
{{Args, Res}, Code2}.
simplify([]) -> [];
simplify([I | Code]) ->
simpl_top(simpl_s(I), simplify(Code)).
simpl_s({ifte, Then, Else}) ->
{ifte, simplify(Then), simplify(Else)};
simpl_s(I) -> I.
%% add_i 0 --> nop
simpl_top({'ADD', _, ?i(0), _}, Code) -> Code;
%% push n, add_a --> add_i n
simpl_top({'PUSH', ?a, ?i(N)},
[{'ADD', ?a, ?a, ?a} | Code]) ->
simpl_top( aeb_fate_code:add(?a, ?i(N), ?a), Code);
%% push n, add_i m --> add_i (n + m)
simpl_top({'PUSH', ?a, ?i(N)}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top(aeb_fate_code:push(?i(N + M)), Code);
%% add_i n, add_i m --> add_i (n + m)
simpl_top({'ADD', ?a, ?i(N), ?a}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top({'ADD', ?a, ?i(N + M), ?a}, Code);
simpl_top(I, Code) -> [I | Code].
%% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_code:inc()];
desugar({ifte, Then, Else}) -> [{ifte, desugar(Then), desugar(Else)}];
desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I].
%% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks
to_basic_blocks(Funs, Options) ->
maps:from_list([ {Name, {{Args, Res},
bb(Name, Code ++ [aeb_fate_code:return()], Options)}}
|| {Name, {{Args, Res}, Code}} <- maps:to_list(Funs) ]).
bb(Name, Code, Options) ->
Blocks0 = blocks(Code),
Blocks = optimize_blocks(Blocks0),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
BBs = [ set_labels(Labels, B) || B <- Blocks ],
debug(Options, "Final code for ~s:\n ~p\n", [Name, BBs]),
maps:from_list(BBs).
%% -- Break up scode into basic blocks --
blocks(Code) ->
Top = make_ref(),
blocks([{Top, Code}], []).
blocks([], Acc) ->
lists:reverse(Acc);
blocks([{Ref, Code} | Blocks], Acc) ->
block(Ref, Code, [], Blocks, Acc).
block(Ref, [], CodeAcc, Blocks, BlockAcc) ->
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
block(Ref, [{ifte, Then, Else} | Code], Acc, Blocks, BlockAcc) ->
ThenLbl = make_ref(),
RestLbl = make_ref(),
block(Ref, Else ++ [{jump, RestLbl}],
[{jumpif, ThenLbl} | Acc],
[{ThenLbl, Then ++ [{jump, RestLbl}]},
{RestLbl, Code} | Blocks],
BlockAcc);
block(Ref, [I | Code], Acc, Blocks, BlockAcc) ->
block(Ref, Code, [I | Acc], Blocks, BlockAcc).
%% -- Reorder, inline, and remove dead blocks --
optimize_blocks(Blocks) ->
%% We need to look at the last instruction a lot, so reverse all blocks.
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
RBlocks = Rev(Blocks),
RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
RBlocks3 = remove_dead_blocks(RBlocks2),
Rev(RBlocks3).
%% Choose the next block based on the final jump.
reorder_blocks([], Acc) ->
lists:reverse(Acc);
reorder_blocks([{Ref, Code} | Blocks], Acc) ->
reorder_blocks(Ref, Code, Blocks, Acc).
reorder_blocks(Ref, Code, Blocks, Acc) ->
Acc1 = [{Ref, Code} | Acc],
case Code of
['RETURN'|_] -> reorder_blocks(Blocks, Acc1);
[{'RETURNR', _}|_] -> reorder_blocks(Blocks, Acc1);
[{jump, L}|_] ->
NotL = fun({L1, _}) -> L1 /= L end,
case lists:splitwith(NotL, Blocks) of
{Blocks1, [{L, Code1} | Blocks2]} ->
reorder_blocks(L, Code1, Blocks1 ++ Blocks2, Acc1);
{_, []} -> reorder_blocks(Blocks, Acc1)
end
end.
%% Inline short blocks ( 2 instructions)
inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
case maps:get(L, BlockMap, nocode) of
Dest when length(Dest) < 3 ->
%% Remove Ref to avoid infinite loops
inline_block(maps:remove(Ref, BlockMap), L, Dest) ++ Code;
_ -> Code0
end;
inline_block(_, _, Code) -> Code.
%% Remove unused blocks
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
BlockMap = maps:from_list(Blocks),
LiveBlocks = chase_labels([Top], BlockMap, #{}),
[ B || B = {L, _} <- Blocks, maps:is_key(L, LiveBlocks) ].
chase_labels([], _, Live) -> Live;
chase_labels([L | Ls], Map, Live) ->
Code = maps:get(L, Map),
Jump = fun({jump, A}) -> [A || not maps:is_key(A, Live)];
({jumpif, A}) -> [A || not maps:is_key(A, Live)];
(_) -> [] end,
New = lists:flatmap(Jump, Code),
chase_labels(New ++ Ls, Map, Live#{ L => true }).
%% -- Translate label refs to indices --
set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
set_labels(Labels, {jump, Ref}) -> aeb_fate_code:jump(maps:get(Ref, Labels));
set_labels(Labels, {jumpif, Ref}) -> aeb_fate_code:jumpif(?a, maps:get(Ref, Labels));
set_labels(_, I) -> I.
%% -- Helpers ----------------------------------------------------------------
with_ixs(Xs) ->
lists:zip(lists:seq(0, length(Xs) - 1), Xs).
keyfind_index(X, J, Xs) ->
case [ I || {I, E} <- with_ixs(Xs), X == element(J, E) ] of
[I | _] -> I;
[] -> false
end.
+15 -17
View File
@@ -87,7 +87,7 @@ constructors() ->
sep1(constructor(), tok('|')). sep1(constructor(), tok('|')).
constructor() -> %% TODO: format for Con() vs Con constructor() -> %% TODO: format for Con() vs Con
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}), choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})). ?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
con_args() -> paren_list(con_arg()). con_args() -> paren_list(con_arg()).
@@ -99,7 +99,9 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations ------------------------------------------------------- %% -- Let declarations -------------------------------------------------------
letdecl() -> letdecl() ->
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)). choice(
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
letdef() -> choice(valdef(), fundef()). letdef() -> choice(valdef(), fundef()).
@@ -211,7 +213,7 @@ exprAtom() ->
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true} , {bool, keyword(true), true}
, {bool, keyword(false), false} , {bool, keyword(false), false}
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, {list, [], bracket_list(Expr)} , {list, [], bracket_list(Expr)}
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
@@ -254,20 +256,14 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []}; record([]) -> {map, [], []};
record(Fs) -> record(Fs) ->
case record_or_map(Fs) of case record_or_map(Fs) of
record -> record -> {record, get_ann(hd(Fs)), Fs};
Fld = fun({field, _, [_], _} = F) -> F;
({field, Ann, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
({field, Ann, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
map -> map ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val}; KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, FAnn, LV, Id, _}) -> ({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id)); bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, FAnn, LV, _}) -> ({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end, bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
{map, Ann, lists:map(KV, Fs)} {map, Ann, lists:map(KV, Fs)}
end. end.
@@ -445,7 +441,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) -> build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
{'if', Ann, Cond, Then, Else}; {'if', Ann, Cond, Then, Else};
build_if(Ann, Cond, Then, []) -> build_if(Ann, Cond, Then, []) ->
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}. {'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) -> else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]); else_branches(Stmts, [Elif | Acc]);
@@ -461,6 +457,7 @@ fun_t(Domains, Type) ->
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end, lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
Type, Domains). Type, Domains).
tuple_e(Ann, []) -> {unit, Ann};
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}. tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
@@ -481,6 +478,7 @@ parse_pattern({record, Ann, Fs}) ->
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)}; {record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
parse_pattern(E = {con, _, _}) -> E; parse_pattern(E = {con, _, _}) -> E;
parse_pattern(E = {id, _, _}) -> E; parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E; parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E; parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E; parse_pattern(E = {bytes, _, _}) -> E;
@@ -497,11 +495,11 @@ return_error({no_file, L, C}, Err) ->
return_error({F, L, C}, Err) -> return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [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()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). return_error(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return().
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E), ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
+7 -3
View File
@@ -160,7 +160,8 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
text(atom_to_list(Mod)); text(atom_to_list(Mod));
(_) -> empty() end, (_) -> empty() end,
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]); hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D). decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
decl(D = {letrec, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
@@ -183,7 +184,9 @@ name({typed, _, Name, _}) -> name(Name).
letdecl(Let, {letval, _, F, T, E}) -> letdecl(Let, {letval, _, F, T, E}) ->
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E); block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, E}) -> letdecl(Let, {letfun, _, F, Args, T, E}) ->
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E). block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
letdecl(Let, {letrec, _, [D | Ds]}) ->
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
-spec args([aeso_syntax:arg()]) -> doc(). -spec args([aeso_syntax:arg()]) -> doc().
args(Args) -> args(Args) ->
@@ -329,6 +332,7 @@ expr_p(_, {Type, _, Bin})
Type == oracle_pubkey; Type == oracle_pubkey;
Type == oracle_query_id -> Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) -> expr_p(_, {char, _, C}) ->
case C of case C of
@@ -361,7 +365,6 @@ stmt_p({else, Else}) ->
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}. -spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('||') -> {200, 300, 200}; bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300}; bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500}; bin_prec('<') -> {400, 500, 500};
@@ -433,6 +436,7 @@ statements(Stmts) ->
statement(S = {letval, _, _, _, _}) -> letdecl("let", S); statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S); statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
statement(S = {letrec, _, _}) -> letdecl("let", S);
statement(E) -> expr(E). statement(E) -> expr(E).
get_elifs(Expr) -> get_elifs(Expr, []). get_elifs(Expr) -> get_elifs(Expr, []).
+2 -2
View File
@@ -36,8 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())} , {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"], "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
+4 -2
View File
@@ -25,7 +25,7 @@
-type ann_origin() :: system | user. -type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif. -type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private]. -type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type name() :: string(). -type name() :: string().
-type id() :: {id, ann(), name()}. -type id() :: {id, ann(), name()}.
@@ -43,7 +43,8 @@
-type letbind() -type letbind()
:: {letval, ann(), id(), type(), expr()} :: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}. | {letfun, ann(), id(), [arg()], type(), expr()}
| {letrec, ann(), [letbind()]}.
-type arg() :: {arg, ann(), id(), type()}. -type arg() :: {arg, ann(), id(), type()}.
@@ -74,6 +75,7 @@
| {contract_pubkey, binary()} | {contract_pubkey, binary()}
| {oracle_pubkey, binary()} | {oracle_pubkey, binary()}
| {oracle_query_id, binary()} | {oracle_query_id, binary()}
| {unit, ann()}
| {string, ann(), binary()} | {string, ann(), binary()}
| {char, ann(), integer()}. | {char, ann(), integer()}.
+24 -24
View File
@@ -6,7 +6,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_syntax_utils). -module(aeso_syntax_utils).
-export([used_ids/1, used_types/2, used/1]). -export([used_ids/1, used_types/1, used/1]).
-record(alg, {zero, plus, scoped}). -record(alg, {zero, plus, scoped}).
@@ -39,6 +39,11 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end, BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end, BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X), 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 Rec = case X of
%% lists (bound things in head scope over tail) %% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As)); [A | As] -> Scoped(Same(A), Same(As));
@@ -50,6 +55,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_decl, _, _, T} -> Type(T); {fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]); {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))]); {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef() %% typedef()
{alias_t, T} -> Type(T); {alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs); {record_t, Fs} -> Type(Fs);
@@ -98,35 +104,29 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies %% Name dependencies
used_ids(E) -> used_ids(E) ->
[ X || {{term, [X]}, _} <- used(E) ]. [ X || {term, [X]} <- used(E) ].
used_types([Top] = _CurrentNS, T) -> used_types(T) ->
F = fun({{type, [X]}, _}) -> [X]; [ X || {type, [X]} <- used(T) ].
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]} -type entity() :: {term, [string()]}
| {type, [string()]} | {type, [string()]}
| {namespace, [string()]}. | {namespace, [string()]}.
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}). -spec entity_alg() -> alg([entity()]).
entity_alg() -> entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end, IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end, Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end,
Scoped = fun(Xs, Ys) -> Scoped = fun(Xs, Ys) ->
Bound = [E || E <- maps:keys(Ys), IsBound(E)], {Bound, Others} = lists:partition(IsBound, Ys),
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ], Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
maps:merge(Remove(Bound1, Xs), Others) lists:umerge(Xs -- Bound1, Others)
end, end,
#alg{ zero = #{} #alg{ zero = []
, plus = fun maps:merge/2 , plus = fun lists:umerge/2
, scoped = Scoped }. , scoped = Scoped }.
-spec used(_) -> [{entity(), aeso_syntax:ann()}]. -spec used(_) -> [entity()].
used(D) -> used(D) ->
Kind = fun(expr) -> term; Kind = fun(expr) -> term;
(bind_expr) -> bound_term; (bind_expr) -> bound_term;
@@ -134,14 +134,14 @@ used(D) ->
(bind_type) -> bound_type (bind_type) -> bound_type
end, end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end, NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end, NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs = Xs =
maps:to_list(fold(entity_alg(), fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann}; fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann}; (K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> #{} (_, _) -> []
end, decl, D)), end, decl, D),
lists:filter(NotBound, Xs). lists:filter(NotBound, Xs).
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "3.1.0"}, {vsn, "2.1.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+78
View File
@@ -0,0 +1,78 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
case Opts of
[version] ->
print_vsn();
[help] ->
usage();
_ ->
compile(Opts)
end;
{ok, {_, NonOpts}} ->
io:format("Can't understand ~p\n\n", [NonOpts]),
usage();
{error, {Reason, Data}} ->
io:format("Error: ~s ~p\n\n", [Reason, Data]),
usage()
end.
compile(Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined ->
io:format("Error: no input source file\n\n"),
usage();
File ->
compile(File, Opts)
end.
compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined),
try
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
write_outfile(OutFile, Res),
io:format("\nCompiled successfully!\n")
catch
%% The compiler errors.
error:{type_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
error:{parse_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
error:{code_errors, Errors} ->
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
%% General programming errors in the compiler.
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
io:format("\n~s\n", [ErrorString])
end.
write_outfile(undefined, _) -> ok;
write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+2 -3
View File
@@ -1,7 +1,7 @@
-module(aeso_abi_tests). -module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-compile([export_all, nowarn_export_all]). -compile(export_all).
-define(SANDBOX(Code), sandbox(fun() -> Code end)). -define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123). -define(DUMMY_HASH_WORD, 16#123).
@@ -62,7 +62,6 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
@@ -161,7 +160,7 @@ oracle_test() ->
permissive_literals_fail_test() -> permissive_literals_fail_test() ->
Contract = Contract =
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful function haxx(o : oracle(list(string), option(int))) =\n" " function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n", " Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} = {error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []), aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
+45 -91
View File
@@ -2,30 +2,31 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N), do_test() ->
fun() -> test_contract(N) end} test_contract(1),
|| N <- [1, 2, 3]]. test_contract(2),
test_contract(3).
test_contract(N) -> test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N), {Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract), {ok,JSON} = aeso_aci:encode(Contract),
?assertEqual([MapACI], JSON), ?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual(DecACI, aeso_aci:decode(JSON)).
test_cases(1) -> test_cases(1) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>, " function a(i : int) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, #{<<"name">> => <<"C">>,
type_defs => [], <<"type_defs">> => [],
functions => <<"functions">> =>
[#{name => <<"a">>, [#{<<"name">> => <<"a">>,
arguments => <<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"int">>}], <<"type">> => <<"int">>}],
returns => <<"int">>, <<"returns">> => <<"int">>,
stateful => false}]}}, <<"stateful">> => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" function a : (int) => int\n">>, " function a : (int) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
@@ -34,89 +35,42 @@ test_cases(2) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type allan = int\n" " type allan = int\n"
" function a(i : allan) = i+1\n">>, " function a(i : allan) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, #{<<"name">> => <<"C">>,
type_defs => <<"type_defs">> =>
[#{name => <<"allan">>, [#{<<"name">> => <<"allan">>,
typedef => <<"int">>, <<"typedef">> => <<"int">>,
vars => []}], <<"vars">> => []}],
functions => <<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"C.allan">>}], <<"type">> => <<"int">>}],
name => <<"a">>, <<"name">> => <<"a">>,
returns => <<"int">>, <<"returns">> => <<"int">>,
stateful => false}]}}, <<"stateful">> => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type allan = int\n" " function a : (int) => int\n">>,
" function a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(3) -> test_cases(3) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type state = ()\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" function a(i : bert(string)) = 1\n">>, " function a(i : bert(string)) = 1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{functions => #{<<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"type">> =>
#{<<"C.bert">> => [<<"string">>]}}], #{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>, <<"name">> => <<"a">>,<<"returns">> => <<"int">>,
stateful => false}], <<"stateful">> => false}],
name => <<"C">>, <<"name">> => <<"C">>,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, <<"type_defs">> =>
state => #{tuple => []}, [#{<<"name">> => <<"bert">>,
type_defs => <<"typedef">> =>
[#{name => <<"bert">>, #{<<"variant">> =>
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]}, [#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}}, <<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type state = ()\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" function a : (C.bert(string)) => int\n">>, " function a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}. {Contract,MapACI,DecACI}.
%% Rounttrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
_:R ->
io:format("Error: ~p\n", [R]),
error(R)
end;
{error, E} ->
io:format("Error: ~p\n", [E]),
error({parse_error, E})
end.
+35 -74
View File
@@ -16,24 +16,20 @@
%% 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_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", [ {"Testing the " ++ ContractName ++ " contract",
fun() -> fun() ->
case compile(Backend, ContractName) of case compile(ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
?assertMatch(Code when is_binary(Code), ByteCode);
Code when Backend == fate, is_tuple(Code) ->
?assertMatch(#{}, aeb_fate_code:functions(Code));
ErrBin -> ErrBin ->
io:format("\n~s", [ErrBin]), io:format("\n~s", [ErrBin]),
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts() ] ++
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of case compile(ContractName) of
<<"Type errors\n", ErrorString/binary>> -> <<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString); check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> -> <<"Parse errors\n", ErrorString/binary>> ->
@@ -48,14 +44,14 @@ simple_compile_test_() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), {ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin} {File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]), end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), #{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"), #{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2) ?assertMatch(true, Code1 == Code2)
end} ] ++ end} ] ++
[ {"Testing deadcode elimination", [ {"Testing deadcode elimination",
fun() -> fun() ->
#{ byte_code := NoDeadCode } = compile(aevm, "nodeadcode"), #{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile(aevm, "deadcode"), #{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode), SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode), SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}), ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
@@ -71,12 +67,12 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra) {Missing, Extra} -> ?assertEqual(Missing, Extra)
end. end.
compile(Backend, Name) -> compile(Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]). compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name, Options) -> compile(Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) 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.
@@ -90,7 +86,6 @@ compilable_contracts() ->
"dutch_auction", "dutch_auction",
"environment", "environment",
"factorial", "factorial",
"functions",
"fundme", "fundme",
"identity", "identity",
"maps", "maps",
@@ -111,21 +106,9 @@ compilable_contracts() ->
"include", "include",
"basic_auth", "basic_auth",
"bitcoin_auth", "bitcoin_auth",
"address_literals", "address_literals"
"bytes_equality",
"address_chain"
]. ].
not_yet_compilable(fate) ->
["oracles", %% Oracle.register
"events", %% events
"basic_auth", %% auth_tx_hash instruction
"bitcoin_auth", %% auth_tx_hash instruction
"address_literals", %% oracle_query_id literals
"address_chain" %% Oracle.check_query
];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
failing_contracts() -> failing_contracts() ->
@@ -205,8 +188,6 @@ failing_contracts() ->
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"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)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 12).">>,
<<"Repeated argument y to function repeated_arg (at line 44, column 12).">>,
<<"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)">>, <<"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)">>]} <<"Record type r2 does not have field y (at line 15, column 22)">>]}
@@ -221,15 +202,20 @@ failing_contracts() ->
, {"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)">>, [<<"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('a) (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", , {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} [<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events", , {"bad_events",
[<<"The indexed type string (at line 9, column 25) is not a word type">>, [<<"The payload type int (at line 10, column 30) should be string">>,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} <<"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", , {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, [<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash", , {"type_clash",
[<<"Cannot unify int\n" [<<"Cannot unify int\n"
" and string\n" " and string\n"
@@ -256,46 +242,46 @@ failing_contracts() ->
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<"Cannot unify oracle_query('1, '2)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('1, '2)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<"Cannot unify oracle_query('3, '4)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('3, '4)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<"Cannot unify oracle_query('5, '6)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('5, '6)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<"Cannot unify oracle('7, '8)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('7, '8)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<"Cannot unify oracle('9, '10)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('9, '10)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<"Cannot unify oracle('11, '12)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('11, '12)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<"Cannot unify address\n"
@@ -316,29 +302,4 @@ failing_contracts() ->
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>]} " bytes(32)">>]}
, {"stateful",
[<<"Cannot reference stateful function Chain.spend (at line 13, column 33)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 33)\nin the definition of non-stateful function fail2.">>,
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<"Cannot reference stateful function Chain.spend (at line 35, column 53)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 55)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 54)\nin the definition of non-stateful function fail7.">>,
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
, {"bad_init_state_access",
[<<"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n"
" - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 36), which calls\n"
" - put (at line 7, column 37)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 27)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>]}
, {"field_parse_error",
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
"Cannot use nested fields or keys in record construction: p.x\n">>]}
]. ].
+1 -1
View File
@@ -62,7 +62,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts %% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]), [ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end} fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
}. }.
parse_contract(Name) -> parse_contract(Name) ->
+1 -1
View File
@@ -41,7 +41,7 @@ all_tokens() ->
%% Operators %% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords %% Keywords
lists:map(Lit, [contract, type, 'let', switch]) ++ lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
%% Comment token (not an actual token), just for tests %% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"}, [{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++ {comment, 0, "/* bla /* bla bla */*/"}] ++
-33
View File
@@ -1,33 +0,0 @@
contract Remote =
function main : (int) => ()
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
function is_o(a : address) =
Address.is_oracle(a)
function is_c(a : address) =
Address.is_contract(a)
// function get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// function get_c(a : address) : option(Remote) =
// Address.get_contract(a)
function check_o(o : o_type) =
Oracle.check(o)
function check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// function h_to_i(h : hash) : int =
// Hash.to_int(h)
// function a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
function c_creator() : address =
Contract.creator
+5
View File
@@ -36,6 +36,11 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => () (x, y :: _) => ()
function mutual() =
let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
recFun(0)
let hash : address = #01ab0fff11 let hash : address = #01ab0fff11
let b = false let b = false
let qcon = Mod.Con let qcon = Mod.Con
+4 -2
View File
@@ -6,8 +6,10 @@ contract Events =
datatype event = datatype event =
Event1(indexed alias_int, indexed int, string) Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string) | BadEvent1(indexed string, string)
| BadEvent2(indexed alias_string) | BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
function f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
+1
View File
@@ -8,6 +8,7 @@ contract Events =
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(string, string) | BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address) | BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
function f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
-13
View File
@@ -1,13 +0,0 @@
contract BadInit =
type state = int
function new_state(n) = state + n
stateful function roundabout(n) = put(n)
stateful function set_state(n) = roundabout(n)
stateful function init() =
set_state(4)
new_state(0)
state + state
+1 -1
View File
@@ -4,7 +4,7 @@ contract BasicAuth =
function init() = { nonce = 1, owner = Call.caller } function init() = { nonce = 1, owner = Call.caller }
stateful function authorize(n : int, s : signature) : bool = function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low") require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high") require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 }) put(state{ nonce = n + 1 })
+1 -1
View File
@@ -3,7 +3,7 @@ contract BitcoinAuth =
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' } function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
stateful function authorize(n : int, s : signature) : bool = function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low") require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high") require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 }) put(state{ nonce = n + 1 })
-18
View File
@@ -1,18 +0,0 @@
contract BytesEquality =
function eq16(a : bytes(16), b) = a == b
function ne16(a : bytes(16), b) = a != b
function eq32(a : bytes(32), b) = a == b
function ne32(a : bytes(32), b) = a != b
function eq47(a : bytes(47), b) = a == b
function ne47(a : bytes(47), b) = a != b
function eq64(a : bytes(64), b) = a == b
function ne64(a : bytes(64), b) = a != b
function eq65(a : bytes(65), b) = a == b
function ne65(a : bytes(65), b) = a != b
+1 -1
View File
@@ -5,5 +5,5 @@ contract Counter =
function init(val) = { value = val } function init(val) = { value = val }
function get() = state.value function get() = state.value
stateful function tick() = put(state{ value = state.value + 1 }) function tick() = put(state{ value = state.value + 1 })
+1 -1
View File
@@ -10,7 +10,7 @@ contract DutchAuction =
sold : bool } sold : bool }
// Add to work around current lack of predefined functions // Add to work around current lack of predefined functions
private stateful function spend(to, amount) = private function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
+2 -2
View File
@@ -12,7 +12,7 @@ contract Environment =
function init(remote) = {remote = remote} function init(remote) = {remote = remote}
stateful function set_remote(remote) = put({remote = remote}) function set_remote(remote) = put({remote = remote})
// -- Information about the this contract --- // -- Information about the this contract ---
@@ -38,7 +38,7 @@ contract Environment =
// Value // Value
function call_value() : int = Call.value function call_value() : int = Call.value
stateful function nested_value(value : int) : int = function nested_value(value : int) : int =
state.remote.call_value(value = value / 2) state.remote.call_value(value = value / 2)
// Gas price // Gas price
+1 -1
View File
@@ -9,7 +9,7 @@ contract Factorial =
function init(worker) = {worker = worker} function init(worker) = {worker = worker}
stateful function set_worker(worker) = put(state{worker = worker}) function set_worker(worker) = put(state{worker = worker})
function fac(x : int) : int = function fac(x : int) : int =
if(x == 0) 1 if(x == 0) 1
-5
View File
@@ -1,5 +0,0 @@
contract Fail =
record pt = {x : int, y : int}
record r = {p : pt}
function fail() = {p.x = 0, p.y = 0}
-15
View File
@@ -1,15 +0,0 @@
contract Functions =
private function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
private function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
private function map'() = map
private function plus(x, y) = x + y
function test1(xs : list(int)) = map(curry(plus)(5), xs)
function test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
function test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+1 -1
View File
@@ -15,7 +15,7 @@ contract FundMe =
private function require(b : bool, err : string) = private function require(b : bool, err : string) =
if(!b) abort(err) if(!b) abort(err)
private stateful function spend(args : spend_args) = private function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount) Chain.spend(args.recipient, args.amount)
public function init(beneficiary, deadline, goal) : state = public function init(beneficiary, deadline, goal) : state =
+12 -12
View File
@@ -17,8 +17,8 @@ contract Maps =
{ ["one"] = {x = 1, y = 2}, { ["one"] = {x = 1, y = 2},
["two"] = {x = 3, y = 4}, ["two"] = {x = 3, y = 4},
["three"] = {x = 5, y = 6} } ["three"] = {x = 5, y = 6} }
stateful function map_state_i() = put(state{ map_i = map_i() }) function map_state_i() = put(state{ map_i = map_i() })
stateful function map_state_s() = put(state{ map_s = map_s() }) function map_state_s() = put(state{ map_s = map_s() })
// m[k] // m[k]
function get_i(k, m : map(int, pt)) = m[k] function get_i(k, m : map(int, pt)) = m[k]
@@ -35,20 +35,20 @@ contract Maps =
// m{[k] = v} // m{[k] = v}
function set_i(k, p, m : map(int, pt)) = m{ [k] = p } function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
function set_s(k, p, m : map(string, pt)) = m{ [k] = p } function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
stateful function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) }) function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) }) function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
// m{f[k].x = v} // m{f[k].x = v}
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x } function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x } function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
stateful function setx_state_i(k, x) = put(state{ map_i[k].x = x }) function setx_state_i(k, x) = put(state{ map_i[k].x = x })
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x }) function setx_state_s(k, x) = put(state{ map_s[k].x = x })
// m{[k] @ x = v } // m{[k] @ x = v }
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d } function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d } function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
stateful function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d }) function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d }) function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
// m{[k = def] @ x = v } // m{[k = def] @ x = v }
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d } function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
@@ -77,8 +77,8 @@ contract Maps =
// Map.delete // Map.delete
function delete_i(k, m : map(int, pt)) = Map.delete(k, m) function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
function delete_s(k, m : map(string, pt)) = Map.delete(k, m) function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
stateful function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) }) function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) }) function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
// Map.size // Map.size
function size_i(m : map(int, pt)) = Map.size(m) function size_i(m : map(int, pt)) = Map.size(m)
@@ -95,6 +95,6 @@ contract Maps =
// Map.from_list // Map.from_list
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs) function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs) function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) }) function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) }) function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
+13 -13
View File
@@ -9,22 +9,22 @@ contract Oracles =
type oracle_id = oracle(query_t, answer_t) type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t) type query_id = oracle_query(query_t, answer_t)
stateful function registerOracle(acct : address, function registerOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful function registerIntIntOracle(acct : address, function registerIntIntOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle(int, int) = ttl : ttl) : oracle(int, int) =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful function registerStringStringOracle(acct : address, function registerStringStringOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle(string, string) = ttl : ttl) : oracle(string, string) =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful function signedRegisterOracle(acct : address, function signedRegisterOracle(acct : address,
sign : signature, sign : signature,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
@@ -33,7 +33,7 @@ contract Oracles =
function queryFee(o : oracle_id) : fee = function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o) Oracle.query_fee(o)
stateful function createQuery(o : oracle_id, function createQuery(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -42,7 +42,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl) Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production! // Do not use in production!
stateful function unsafeCreateQuery(o : oracle_id, function unsafeCreateQuery(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -50,7 +50,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl) Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production! // Do not use in production!
stateful function unsafeCreateQueryThenErr(o : oracle_id, function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -59,21 +59,21 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error") require(qfee >= 100000000000000000, "causing a late error")
res res
stateful function extendOracle(o : oracle_id, function extendOracle(o : oracle_id,
ttl : ttl) : () = ttl : ttl) : () =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful function signedExtendOracle(o : oracle_id, function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address sign : signature, // Signed oracle address
ttl : ttl) : () = ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful function respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
r : answer_t) : () = r : answer_t) : () =
Oracle.respond(o, q, r) Oracle.respond(o, q, r)
stateful function signedRespond(o : oracle_id, function signedRespond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : () = r : answer_t) : () =
@@ -96,13 +96,13 @@ contract Oracles =
datatype complexQuestion = Why(int) | How(string) datatype complexQuestion = Why(int) | How(string)
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int) datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
stateful function complexOracle(question) = function complexOracle(question) =
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer) let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100)) let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337)) Oracle.respond(o, q, Answer(question, "magic", 1337))
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
stateful function signedComplexOracle(question, sig) = function signedComplexOracle(question, sig) =
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer) let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100)) let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig) Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
+1 -1
View File
@@ -11,7 +11,7 @@ contract Remote3 =
contract RemoteCall = contract RemoteCall =
stateful function call(r : Remote1, x : int) : int = function call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x) r.main(gas = 10000, value = 10, x)
function staged_call(r1 : Remote1, r2 : Remote2, x : int) = function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
+99
View File
@@ -0,0 +1,99 @@
contract Oracles =
function registerOracle :
(address,
int,
Chain.ttl) => oracle(string, int)
function createQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function unsafeCreateQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function respond :
(oracle(string, int),
oracle_query(string, int),
int) => ()
contract OraclesErr =
function unsafeCreateQueryThenErr :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
contract RemoteOracles =
public function callRegisterOracle(
r : Oracles,
acct : address,
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
r.registerOracle(acct, qfee, ttl)
public function callCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
require(value =< Call.value, "insufficient value")
r.createQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryThenErr(
r : OraclesErr,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryAndThenErr(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
public function callRespond(
r : Oracles,
o : oracle(string, int),
q : oracle_query(string, int),
qr : int) =
r.respond(o, q, qr)
private function require(b : bool, err : string) =
if(!b) abort(err)
+1 -1
View File
@@ -24,5 +24,5 @@ contract SimpleStorage =
function get() : int = state.data function get() : int = state.data
stateful function set(value : int) = function set(value : int) =
put(state{data = value}) put(state{data = value})
+4 -4
View File
@@ -4,19 +4,19 @@ contract SpendContract =
contract SpendTest = contract SpendTest =
stateful function spend(to, amount) = function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
stateful function withdraw(amount) : int = function withdraw(amount) : int =
spend(Call.caller, amount) spend(Call.caller, amount)
stateful function withdraw_from(account, amount) = function withdraw_from(account, amount) =
account.withdraw(amount) account.withdraw(amount)
withdraw(amount) withdraw(amount)
stateful function spend_from(from, to, amount) = function spend_from(from, to, amount) =
from.withdraw(amount) from.withdraw(amount)
Chain.spend(to, amount) Chain.spend(to, amount)
Chain.balance(to) Chain.balance(to)
+9 -9
View File
@@ -27,13 +27,13 @@ contract StateHandling =
function read_s() = state.s function read_s() = state.s
function read_m() = state.m function read_m() = state.m
stateful function update(new_state : state) = put(new_state) function update(new_state : state) = put(new_state)
stateful function update_i(new_i) = put(state{ i = new_i }) function update_i(new_i) = put(state{ i = new_i })
stateful function update_s(new_s) = put(state{ s = new_s }) function update_s(new_s) = put(state{ s = new_s })
stateful function update_m(new_m) = put(state{ m = new_m }) function update_m(new_m) = put(state{ m = new_m })
function pass_it(r : Remote) = r.look_at(state) function pass_it(r : Remote) = r.look_at(state)
stateful function nop(r : Remote) = put(state{ i = state.i }) function nop(r : Remote) = put(state{ i = state.i })
function return_it_s(r : Remote, big : bool) = function return_it_s(r : Remote, big : bool) =
let x = r.return_s(big) let x = r.return_s(big)
String.length(x) String.length(x)
@@ -50,10 +50,10 @@ contract StateHandling =
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s) function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m) function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
stateful function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i)) function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
stateful function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s)) function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
stateful function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m)) function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v)) function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
// remote called // remote called
function look_at(s : state) = () function look_at(s : state) = ()
-54
View File
@@ -1,54 +0,0 @@
contract Remote =
stateful function remote_spend : (address, int) => ()
function remote_pure : int => int
contract Stateful =
private function pure(x) = x + 1
private stateful function local_spend(a) =
Chain.spend(a, 1000)
// Non-stateful functions cannot mention stateful functions
function fail1(a : address) = Chain.spend(a, 1000)
function fail2(a : address) = local_spend(a)
function fail3(a : address) =
let foo = Chain.spend
foo(a, 1000)
// Private functions must also be annotated
private function fail4(a) = Chain.spend(a, 1000)
// If annotated, stateful functions are allowed
stateful function ok1(a : address) = Chain.spend(a, 1000)
// And pure functions are always allowed
stateful function ok2(a : address) = pure(5)
stateful function ok3(a : address) =
let foo = pure
foo(5)
// No error here (fail4 is annotated as not stateful)
function ok4(a : address) = fail4(a)
// Lamdbas are checked at the construction site
private function fail5() : address => () = (a) => Chain.spend(a, 1000)
// .. so you can pass a stateful lambda to a non-stateful higher-order
// function:
private function apply(f : 'a => 'b, x) = f(x)
stateful function ok5(a : address) =
apply((val) => Chain.spend(a, val), 1000)
// It doesn't matter if remote calls are stateful or not
function ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
function ok7(r : Remote) = r.remote_pure(5)
// But you can't send any tokens if not stateful
function fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
function fail7(r : Remote) = r.remote_pure(value = 1000, 5)
function fail8(r : Remote) =
let foo = r.remote_pure
foo(value = 1000, 5)
function ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
-2
View File
@@ -40,5 +40,3 @@ contract Test =
function type_error(r, x) = function type_error(r, x) =
set_x(set_x(x, r), x) set_x(set_x(x, r), x)
function repeated_arg(x : int, y, x : string, y : bool) : string = x
+3 -3
View File
@@ -11,11 +11,11 @@ contract VariantTypes =
function require(b) = if(!b) abort("required") function require(b) = if(!b) abort("required")
stateful function start(bal : int) = function start(bal : int) =
switch(state) switch(state)
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)})) Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
stateful function stop() = function stop() =
switch(state) switch(state)
Started(st) => Started(st) =>
require(Call.caller == st.owner) require(Call.caller == st.owner)
@@ -23,7 +23,7 @@ contract VariantTypes =
st.balance st.balance
function get_color() = switch(state) Started(st) => st.color function get_color() = switch(state) Started(st) => st.color
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c})) function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
function get_state() = state function get_state() = state
+56
View File
@@ -0,0 +1,56 @@
/* Example from Solidity by Example
http://solidity.readthedocs.io/en/develop/common-patterns.html
contract WithdrawalContract {
address public richest
uint public mostSent
mapping (address => uint) pendingWithdrawals
function WithdrawalContract() payable {
richest = msg.sender
mostSent = msg.value
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value
richest = msg.sender
mostSent = msg.value
return true
} else {
return false
}
}
function withdraw() {
uint amount = pendingWithdrawals[msg.sender]
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0
msg.sender.transfer(amount)
}
}
*/
contract WithdrawalContract =
record state = { richest : address,
mostSent : uint,
pendingWithdrawals : map(address, uint) }
function becomeRichest() : result(bool) =
if (call().value > state.mostSent)
let totalAmount : uint = Map.get_(state.richest, pendingWithdrawals) + call().value
{state = state{ pendingWithdrawals = Map.insert(state.richest, call().value, state.pendingWithdrawals),
richest = call().sender,
mostSent = call().value },
result = true}
else
{result = false}
function withdraw() =
let amount : uint = Map.get_(call().sender, state.pendingWithdrawals)
{ state.pendingWithdrawals = Map.insert(call().sender, 0, state.pendingWithdrawals),
transactions = spend_tx(amount, call().sender) }