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
45 changed files with 1094 additions and 3290 deletions
+1 -22
View File
@@ -9,26 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### 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
@@ -55,7 +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.0.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[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
+102 -16
View File
@@ -31,23 +31,48 @@ generates the following JSON structure representing the contract interface:
{ {
"contract": { "contract": {
"name": "Answers", "name": "Answers",
"type_defs": [ "state": {
"record": [
{ {
"name": "state", "name": "a",
"vars": [], "type": {
"typedef": "{a : map(string,int)}" "map": {
"key": "string",
"value": "int"
}
}
}
]
}, },
"type_defs": [
{ {
"name": "answers", "name": "answers",
"vars": [], "vars": [],
"typedef": "map(string,int)" "typedef": {
"map": {
"key": "string",
"value": "int"
}
}
} }
], ],
"functions": [ "functions": [
{ {
"name": "init", "name": "init",
"arguments": [], "arguments": [],
"type": "{a : map(string,int)}", "returns": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"stateful": true "stateful": true
}, },
{ {
@@ -62,7 +87,12 @@ generates the following JSON structure representing the contract interface:
"type": "int" "type": "int"
} }
], ],
"type": "map(string,int)", "returns": {
"map": {
"key": "string",
"value": "int"
}
},
"stateful": false "stateful": false
} }
] ]
@@ -74,7 +104,7 @@ When that encoding is decoded the following include definition is generated:
``` ```
contract Answers = contract Answers =
function new_answer : (string, int) => map(string,int) function new_answer : (string, int) => map(string, int)
``` ```
### Types ### Types
@@ -85,7 +115,7 @@ json_string() = binary()
### Exports ### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString} #### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Types Types
@@ -94,9 +124,21 @@ ConstractString = contract_string()
JSONstring = json_string() JSONstring = json_string()
``` ```
This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
#### 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. Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString. #### decode_contract(JSONstring) -> ConstractString.
Types Types
@@ -107,23 +149,67 @@ 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. 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 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. 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.
``` 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,Encoding} = aeso_aci:encode(Contract). 2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>> {ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
3> file:write_file("aci_test.aci", Encoding). 3> file:write_file("aci_test.aci", Encoding).
ok ok
4> Decoded = aeso_aci:decode(Encoding). 4> Decoded = aeso_aci:decode_contract(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">> <<"contract Answers =\n function new_answer : (string, int) => map(string, int)\n">>
5> file:write_file("aci_test.include", Decoded). 5> file:write_file("aci_test.include", Decoded).
ok ok
6> jsx:prettify(Encoding). 6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>> <<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
``` ```
The final call to `jsx:prettify(Encoding)` returns the encoding in a The final call to `jsx:prettify(Encoding)` returns the encoding in a
+17 -2
View File
@@ -3,19 +3,34 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "2f4e188"}}} {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.0.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,"2f4e1888c241a7347ffec855ab6761c2c2972f37"}}, {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",
+334 -158
View File
@@ -9,11 +9,16 @@
-module(aeso_aci). -module(aeso_aci).
%% Old deprecated interface.
-export([encode/1,encode/2,decode/1]). -export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/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]).
%% Define records for the various typed syntactic forms. These make %% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere. %% the code easier but don't seem to exist elsewhere. Unfortunately
%% sometimes the same typename is used with different fields.
%% Top-level %% Top-level
-record(contract, {ann,con,decls}). -record(contract, {ann,con,decls}).
@@ -40,6 +45,13 @@
-record(qcon, {ann,names}). -record(qcon, {ann,names}).
-record(tvar, {ann,name}). -record(tvar, {ann,name}).
%% Statements
-record(block, {ann,body}).
-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}).
%% Expressions %% Expressions
-record(bool, {ann,bool}). -record(bool, {ann,bool}).
-record(int, {ann,value}). -record(int, {ann,value}).
@@ -47,36 +59,48 @@
-record(bytes, {ann,bin}). -record(bytes, {ann,bin}).
-record(tuple, {ann,args}). -record(tuple, {ann,args}).
-record(list, {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(app, {ann,func,args}).
-record(typed, {ann,expr,type}). -record(typed, {ann,expr,type}).
%% encode(ContractString) -> {ok,JSON} | {error,String}. %% The old deprecated interface.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
encode(C) -> encode_contract(C).
encode(C, Os) -> encode_contract(C, Os).
decode(J) -> decode_contract(J).
%% 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 %% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string. %% allows us to order the fields in the contructed JSON string.
encode(ContractString) -> encode(ContractString, []). encode_contract(ContractString) ->
encode_contract(ContractString, []).
encode(ContractString, Options) when is_binary(ContractString) -> encode_contract(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options); encode_contract(binary_to_list(ContractString), Options);
encode(ContractString, Options) -> encode_contract(ContractString, Options) ->
try try
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("Ast\n~p\n", [Ast]),
%% aeso_ast:pp(Ast), %% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options), TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]), %% io:format("Typed ast\n~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst), %% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract. %% We find and look at the last contract.
Contract = lists:last(TypedAst), Contract = lists:last(TypedAst),
Cname = contract_name(Contract), Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) || Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
T <- sort_decls(contract_types(Contract)) ], Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ], not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)}, Jmap = [{<<"contract">>,
{<<"type_defs">>, Tdefs}, [{<<"name">>, do_encode_name(Cname)}] ++
{<<"functions">>, Fdefs}]}], Tdefs ++
[{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]), %% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)} {ok,jsx:encode(Jmap)}
catch catch
@@ -95,223 +119,375 @@ 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_func(Function) -> JSON %% do_encode_contract_typedefs(TypeDefs) -> [JSON].
%% Return a list of typedefs and state and event if they occur.
do_encode_contract_typedefs(Tdefs) ->
Fun = fun(T, {Ts,Ss,Es}) ->
%% Only one state and event.
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.
%% do_encode_state_typedef(StateTdef) -> JSON.
%% do_encode_event_typedef(EventTdef) -> JSON.
do_encode_state_typedef(State) ->
Def = typedef_def(State),
{<<"state">>,do_encode_alias(Def)}.
do_encode_event_typedef(State) ->
Def = typedef_def(State),
{<<"event">>,do_encode_alias(Def)}.
%% encode_func(TypedAST) -> JSON.
%% Encode a function AST into a JSON structure.
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_func(Fdef) -> do_encode_func(Fdef) ->
Name = function_name(Fdef), Name = function_name(Fdef),
Args = function_args(Fdef), Args = function_args(Fdef),
Type = function_type(Fdef), Type = function_type(Fdef),
[{<<"name">>, encode_name(Name)}, [{<<"name">>, do_encode_name(Name)},
{<<"arguments">>, encode_args(Args)}, {<<"arguments">>, do_encode_args(Args)},
{<<"returns">>, encode_type(Type)}, {<<"returns">>, do_encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}]. {<<"stateful">>, is_stateful_func(Fdef)}].
%% encode_args(Args) -> [JSON]. %% encode_arg(TypedAST) -> JSON.
%% encode_arg(Args) -> JSON. %% Encode an argument AST into a JSON structure.
encode_args(Args) -> encode_arg(AST) ->
[ encode_arg(A) || A <- Args ]. jsx:encode(do_encode_arg(AST)).
encode_arg(#arg{id=Id,type=T}) -> %% do_encode_args(ArgASTs) -> [JSONmap].
[{<<"name">>,encode_type(Id)}, %% do_encode_arg(ArgAST) -> JSONmap.
{<<"type">>,[encode_type(T)]}].
%% encode_types(Types) -> [JSON]. do_encode_args(Args) ->
%% encode_type(Type) -> JSON. [ do_encode_arg(A) || A <- Args ].
encode_types(Types) -> do_encode_arg(#arg{id=Id,type=T}) ->
[ encode_type(T) || T <- Types ]. [{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
encode_type(#tvar{name=N}) -> encode_name(N); %% encode_type(TypedAST) -> JSON.
encode_type(#id{name=N}) -> encode_name(N); %% Encode a type AST into a JSON structure.
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) -> encode_type(AST) ->
encode_name(lists:join(".", Ns)); jsx:encode(do_encode_type(AST)).
encode_type(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %? %% do_encode_types([TypeAST]) -> [JSONmap].
encode_type(#tuple_t{args=As}) -> %% do_encode_type(TypeAST) -> JsonMap.
Eas = encode_types(As),
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}]; [{<<"tuple">>,Eas}];
encode_type(#bytes_t{len=Len}) -> do_encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>, Len}; {<<"bytes">>,Len};
encode_type(#record_t{fields=Fs}) -> do_encode_type(#record_t{fields=Fs}) ->
Efs = encode_fields(Fs), Efs = do_encode_type_rec_fields(Fs),
[{<<"record">>,Efs}]; [{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) -> %% Special case lists and maps as they are built-in types.
Name = encode_type(Id), do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
Efs = encode_types(Fs), 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}]; [{Name,Efs}];
encode_type(#variant_t{cons=Cs}) -> do_encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs), Ecs = do_encode_types(Cs),
[{<<"variant">>,Ecs}]; [{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) -> do_encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C), Ec = do_encode_type(C),
Eas = encode_types(As), Eas = do_encode_types(As),
[{Ec,Eas}]; [{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) -> do_encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As), Eas = do_encode_types(As),
Et = encode_type(T), Et = do_encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}]. [{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) -> do_encode_name(Name) ->
list_to_binary(Name). list_to_binary(Name).
%% encode_fields(Fields) -> [JSON]. %% do_encode_type_rec_fields(Fields) -> [JSONmap].
%% encode_field(Field) -> JSON. %% do_encode_type_rec_field(Field) -> JSONmap.
%% Encode a record field. %% Encode a record field type.
encode_fields(Fs) -> do_encode_type_rec_fields(Fs) ->
[ encode_field(F) || F <- Fs ]. [ do_encode_type_rec_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) -> do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)}, [{<<"name">>,do_encode_type(Id)},
{<<"type">>,[encode_type(T)]}]. {<<"type">>,do_encode_type(T)}].
%% encode_typedef(TypeDef) -> JSON. %% do_encode_type_mapo_field(Field) -> JSONmap.
%% Two fields for one map type.
encode_typedef(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_alias(Def)}]. {<<"typedef">>, do_encode_alias(Def)}].
encode_tvars(Vars) -> do_encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ]. [ do_encode_tvar(V) || V <- Vars ].
encode_tvar(#tvar{name=N}) -> do_encode_tvar(#tvar{name=N}) ->
[{<<"name">>, encode_name(N)}]. [{<<"name">>, do_encode_name(N)}].
encode_alias(#alias_t{type=T}) -> do_encode_alias(#alias_t{type=T}) ->
encode_type(T); do_encode_type(T);
encode_alias(A) -> encode_type(A). do_encode_alias(A) -> do_encode_type(A).
%% encode_stmt(Stmt) -> JSON. %% encode_stmt(StmtAST) -> JSON.
%% Encode a statement AST into a JSON structure.
encode_stmt(E) -> encode_stmt(AST) ->
encode_expr(E). jsx:encode(do_encode_stmt(AST)).
%% encode_exprs(Exprs) -> [JSON]. %% do_encode_stmt(StmtAST) -> JSONmap.
%% encode_expr(Expr) -> JSON.
encode_exprs(Es) -> do_encode_stmt(#typed{expr=E}) -> %Ignore the type
[ 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{name=N}) -> encode_name(N); do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
encode_expr(#con{name=N}) -> encode_name(N); Epat = do_encode_expr(Pat), %Patterns are expessions
encode_expr(#qid{names=Ns}) -> Ebody = do_encode_stmt(Body),
encode_name(lists:join(".", Ns)); [{<<"pattern">>,Epat},{<<"body">>,Ebody}].
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %? %% encode_expr(ExprAST) -> JSON.
encode_expr(#typed{expr=E}) -> %% Encode an expression AST into a JSON structure.
encode_expr(E);
encode_expr(#bool{bool=B}) -> B; encode_expr(AST) ->
encode_expr(#int{value=V}) -> V; jsx:encode(do_encode_expr(AST)).
encode_expr(#string{bin=B}) -> B;
encode_expr(#bytes{bin=B}) -> B; %% do_encode_exprs(ExprASTs) -> [JSONmap].
encode_expr(#tuple{args=As}) -> %% do_encode_expr(ExprAST) -> JSONmap.
Eas = encode_exprs(As),
do_encode_exprs(Es) ->
[ do_encode_expr(E) || E <- Es ].
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}]; [{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) -> do_encode_expr(#list{args=As}) ->
Eas = encode_exprs(As), Eas = do_encode_exprs(As),
[{<<"list">>,Eas}]; [{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) -> do_encode_expr(#record{fields=Fs}) -> %Create a record
Ef = encode_expr(F), Efs = do_encode_expr_rec_fields(Fs),
Eas = encode_exprs(As), [{<<"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}, [{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}]; {<<"arguments">>,Eas}]}];
encode_expr({Op,_Ann}) -> 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)). list_to_binary(atom_to_list(Op)).
%% decode(JSON) -> ContractString. %% do_encode_expr_rec_fields(Fields) -> [JSON].
%% do_encode_expr_rec_field(Field) -> JSON.
%% Encode a record field expression.
do_encode_expr_rec_fields(Fs) ->
[ 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 %% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map %% 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. %% here as this is easier to work with and order is not important.
decode(Json) -> decode_contract(Json) ->
Map = jsx:decode(Json, [return_maps]), Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]), %% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map, #{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)). #{<<"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).
decode_contract(#{<<"name">> := Name, do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts),
decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. do_decode_func(#{<<"name">> := <<"init">>}) -> [];
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ", [" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n]. do_decode_args(As)," => ",do_decode_type(T),$\n].
decode_args(As) -> do_decode_args(As) ->
Das = [ decode_arg(A) || A <- 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(#{<<"variant">> := Ets}) -> do_decode_type(#{<<"variant">> := Ets}) ->
Ts = decode_types(Ets), Ts = do_decode_types(Ets),
lists:join(" | ", Ts); lists:join(" | ", Ts);
decode_type(Econs) when is_map(Econs) -> %General constructor do_decode_type(Econs) when is_map(Econs) -> %General constructor
%% io:format("~p\n", [Econs]),
[{Ec,Ets}] = maps:to_list(Econs), [{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec), C = do_decode_name(Ec),
Ts = decode_types(Ets), Ts = do_decode_types(Ets),
[C,$(,lists:join(",", Ts),$)]; [C,$(,lists:join(", ", Ts),$)];
decode_type(T) -> %Just raw names. do_decode_type(T) -> %Just raw names.
decode_name(T). do_decode_name(T).
decode_name(En) -> do_decode_name(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) || do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts #{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
]. ].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) -> do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs), [" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
" = ",decode_type(T),$\n]. " = ",do_decode_type(T),$\n].
decode_tvars([]) -> []; %No tvars, no parentheses do_decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) -> do_decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ], Dvs = [ do_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]}.
+24 -117
View File
@@ -103,9 +103,6 @@
, 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
@@ -605,8 +597,6 @@ 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}.
@@ -803,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).
@@ -845,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],
@@ -865,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),
@@ -881,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)]).
@@ -911,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);
@@ -920,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}),
@@ -984,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}) ->
@@ -1013,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],
@@ -1066,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{
@@ -1135,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,
@@ -1196,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}),
@@ -1205,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}, _}} =
@@ -1254,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, _, _}) ->
@@ -1885,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),
@@ -2001,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
+14 -40
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;
@@ -218,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'});
@@ -415,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) ->
@@ -523,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) ->
@@ -592,30 +579,16 @@ 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
string ->
#funcall{ function = #var_ref{name = {builtin, str_equal}},
args = Args };
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
#funcall{ function = #var_ref{name = {builtin, str_equal_p}},
args = [ #integer{value = 32 * length(Types)} | Args] };
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare, Ann, Op, Type})
end; end;
_ ->
gen_error({cant_compare, Ann, Op, Type})
end,
Neg(Builtin)
end;
ast_binop('++', _, A, B, Icode) -> ast_binop('++', _, A, B, Icode) ->
#funcall{ function = #var_ref{ name = {builtin, list_concat} }, #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] }; args = [ast_body(A, Icode), ast_body(B, Icode)] };
@@ -724,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);
@@ -753,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],
@@ -788,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 ] }] };
+7 -3
View File
@@ -188,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().
@@ -267,9 +267,9 @@ translate_vm_value(word, {con, _, _Name}, N) -> address_l
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N}; 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) ->
@@ -403,6 +403,10 @@ icode_to_term(word, {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.
+6 -2
View File
@@ -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()).
@@ -439,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]);
@@ -455,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}.
@@ -475,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;
+7 -2
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
@@ -432,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()}.
+22 -18
View File
@@ -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,31 +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(T) -> used_types(T) ->
[ X || {{type, [X]}, _} <- used(T) ]. [ X || {type, [X]} <- 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;
@@ -130,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.0.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]).
+1 -1
View File
@@ -160,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"], []),
+3 -3
View File
@@ -24,7 +24,7 @@ test_cases(1) ->
[#{<<"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"
@@ -44,7 +44,7 @@ test_cases(2) ->
<<"functions">> => <<"functions">> =>
[#{<<"arguments">> => [#{<<"arguments">> =>
[#{<<"name">> => <<"i">>, [#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}], <<"type">> => <<"int">>}],
<<"name">> => <<"a">>, <<"name">> => <<"a">>,
<<"returns">> => <<"int">>, <<"returns">> => <<"int">>,
<<"stateful">> => false}]}}, <<"stateful">> => false}]}},
@@ -60,7 +60,7 @@ test_cases(3) ->
[#{<<"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">>,
+1 -28
View File
@@ -86,7 +86,6 @@ compilable_contracts() ->
"dutch_auction", "dutch_auction",
"environment", "environment",
"factorial", "factorial",
"functions",
"fundme", "fundme",
"identity", "identity",
"maps", "maps",
@@ -107,9 +106,7 @@ compilable_contracts() ->
"include", "include",
"basic_auth", "basic_auth",
"bitcoin_auth", "bitcoin_auth",
"address_literals", "address_literals"
"bytes_equality",
"address_chain"
]. ].
%% Contracts that should produce type errors %% Contracts that should produce type errors
@@ -191,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)">>]}
@@ -307,26 +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)">>]}
]. ].
+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
-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
-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