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
91 changed files with 2095 additions and 5383 deletions
+1 -3
View File
@@ -1,5 +1,5 @@
.rebar3 .rebar3
_[^_]* _*
.eunit .eunit
*.o *.o
*.beam *.beam
@@ -19,5 +19,3 @@ rebar3.crashdump
*.erl~ *.erl~
*.aes~ *.aes~
aesophia aesophia
.qcci
current_counterexample.eqc
+1 -72
View File
@@ -9,74 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### Removed
## [3.2.0] - 2019-06-28
### Added
- New builtin function `require : (bool, string) => ()`. Defined as
```
function require(b, err) = if(!b) abort(err)
```
- New builtin functions
```
Bytes.to_str : bytes(_) => string
Bytes.to_int : bytes(_) => int
```
for converting a byte array to a hex string and interpreting it as a
big-endian encoded integer respectively.
### Changed
- Public contract functions must now be declared as *entrypoints*:
```
contract Example =
// Exported
entrypoint exported_fun(x) = local_fun(x)
// Not exported
function local_fun(x) = x
```
Functions in namespaces still use `function` (and `private function` for
private functions).
- The return type of `Chain.block_hash(height)` has changed, it used to
be `int`, where `0` denoted an incorrect height. New return type is
`option(hash)`, where `None` represents an incorrect height.
- Event name hashes now use BLAKE2b instead of Keccak256.
- Fixed bugs when defining record types in namespaces.
- Fixed a bug in include path handling when passing options to the compiler.
### Removed
## [3.1.0] - 2019-06-03
### Added
### Changed
- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`,
...) event arguments.
- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`.
- ACI is restructured and improved:
- `state` and `event` types (if present) now appear at the top level.
- Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are `contract_interface`
and `render_aci_json`.
- Fixed a bug in `create_calldata`/`to_sophia_value` - it can now handle negative
literals.
### Removed
## [3.0.0] - 2019-05-21
### Added
- `stateful` annotations are now properly enforced. Functions must be marked stateful
in order to update the state or spend tokens.
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
- `Crypto.ecverify_secp256k1` has been added.
### Changed
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
not working properly and has been fixed.
### Removed
- `let rec` has been removed from the language, it has never worked.
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
been completely removed from `aesophia`.
## [2.1.0] - 2019-04-11 ## [2.1.0] - 2019-04-11
### Added ### Added
- Stubs (not yet wired up) for compilation to FATE - Stubs (not yet wired up) for compilation to FATE
@@ -103,9 +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.2.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0 [2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0 [2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+138 -73
View File
@@ -30,14 +30,53 @@ generates the following JSON structure representing the contract interface:
``` json ``` json
{ {
"contract": { "contract": {
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"type_defs": [
{
"name": "answers",
"vars": [],
"typedef": {
"map": {
"key": "string",
"value": "int"
}
}
}
],
"functions": [ "functions": [
{ {
"arguments": [],
"name": "init", "name": "init",
"returns": "Answers.state", "arguments": [],
"returns": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"stateful": true "stateful": true
}, },
{ {
"name": "new_answer",
"arguments": [ "arguments": [
{ {
"name": "q", "name": "q",
@@ -48,36 +87,14 @@ generates the following JSON structure representing the contract interface:
"type": "int" "type": "int"
} }
], ],
"name": "new_answer",
"returns": { "returns": {
"map": [ "map": {
"string", "key": "string",
"int" "value": "int"
] }
}, },
"stateful": false "stateful": false
} }
],
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": "Answers.answers"
}
]
},
"type_defs": [
{
"name": "answers",
"typedef": {
"map": [
"string",
"int"
]
},
"vars": []
}
] ]
} }
} }
@@ -87,70 +104,118 @@ When that encoding is decoded the following include definition is generated:
``` ```
contract Answers = contract Answers =
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int) function new_answer : (string, int) => map(string, int)
``` ```
### Types ### Types
``` erlang ``` erlang
-type aci_type() :: json | string. contract_string() = string() | binary()
-type json() :: jsx:json_term(). json_string() = binary()
-type json_text() :: binary().
``` ```
### Exports ### Exports
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()} #### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Generate the JSON encoding of the interface to a contract. The type definitions Types
and non-private functions are included in the JSON string.
#### render\_aci\_json(json() | json\_text()) -> string(). ``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate a contract interface This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
that can be included in another contract.
#### encode_contract(ContractString, Options) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
Options = [option()]
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode_contract(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
#### encode_type(TypeAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a type from the AST of the type.
#### encode_arg(ArgAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a function argument from the AST of the argument.
#### encode_stmt(StmtAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a statement from the AST of the statement.
#### encode_expr(ExprAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of an expression from the AST of the expression.
### Notes
The deprecated functions `aseo_aci:encode/2` and `aeso_aci:decode/1` are still available but should not be used.
### Example run ### Example run
This is an example of using the ACI generator from an Erlang shell. The file This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang ``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes"). 1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>} {ok,<<"contract Answers =\n\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful functio"...>>}
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract). 2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
{ok,[#{contract => {ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
#{functions => 3> file:write_file("aci_test.aci", Encoding).
[#{arguments => [],name => <<"init">>,
returns => <<"Answers.state">>,stateful => true},
#{arguments =>
[#{name => <<"q">>,type => <<"string">>},
#{name => <<"a">>,type => <<"int">>}],
name => <<"new_answer">>,
returns => #{<<"map">> => [<<"string">>,<<"int">>]},
stateful => false}],
name => <<"Answers">>,
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok ok
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI). 4> Decoded = aeso_aci:decode_contract(Encoding).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>} <<"contract Answers =\n function new_answer : (string, int) => map(string, int)\n">>
5> file:write_file("aci_test.include", InterfaceStub). 5> file:write_file("aci_test.include", Decoded).
ok ok
6> jsx:prettify(jsx:encode(JsonACI)). 6> jsx:prettify(Encoding).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>> <<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
``` ```
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description above. more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
+18 -3
View File
@@ -2,20 +2,35 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"adf3664"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "e8253b0"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {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.2.0"}, {relx, [{release, {aesophia, "2.1.0"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+3 -3
View File
@@ -1,17 +1,17 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"adf3664dd03626c115756f79fa0e602fda24318d"}}, {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",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}}, {ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2}, 2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, {<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>, {<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git", {git,"https://github.com/talentdeficit/jsx.git",
BIN
View File
Binary file not shown.
+466 -254
View File
@@ -9,70 +9,100 @@
-module(aeso_aci). -module(aeso_aci).
-export([ file/2 %% Old deprecated interface.
, file/3 -export([encode/1,encode/2,decode/1]).
, contract_interface/2
, contract_interface/3
, render_aci_json/1 -export([encode_contract/1,encode_contract/2,decode_contract/1]).
-export([encode_func/1,encode_type/1,encode_arg/1,
encode_stmt/1,encode_expr/1]).
, json_encode_expr/1 %% Define records for the various typed syntactic forms. These make
, json_encode_type/1]). %% the code easier but don't seem to exist elsewhere. Unfortunately
%% sometimes the same typename is used with different fields.
-type aci_type() :: json | string. %% Top-level
-type json() :: jsx:json_term(). -record(contract, {ann,con,decls}).
-type json_text() :: binary(). %% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
%% External API %% Types
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}. -record(app_t, {ann,id,fields}).
file(Type, File) -> -record(tuple_t, {ann,args}).
file(Type, File, []). -record(bytes_t, {ann,len}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
file(Type, File, Options0) -> %% Tokens
Options = aeso_compiler:add_include_path(File, Options0), -record(arg, {ann,id,type}).
case file:read_file(File) of -record(id, {ann,name}).
{ok, BinCode} -> -record(con, {ann,name}).
do_contract_interface(Type, binary_to_list(BinCode), Options); -record(qid, {ann,names}).
{error, _} = Err -> Err -record(qcon, {ann,names}).
end. -record(tvar, {ann,name}).
-spec contract_interface(aci_type(), string()) -> %% Statements
{ok, json() | string()} | {error, term()}. -record(block, {ann,body}).
contract_interface(Type, ContractString) -> -record('if', {ann,test,then,else}). %Both statement and expression
contract_interface(Type, ContractString, []). -record(letval, {ann,pat,type,exp}).
-record(switch, {ann,arg,cases}).
-record('case', {ann,pat,body}).
-spec contract_interface(aci_type(), string(), [term()]) -> %% Expressions
{ok, json() | string()} | {error, term()}. -record(bool, {ann,bool}).
contract_interface(Type, ContractString, CompilerOpts) -> -record(int, {ann,value}).
do_contract_interface(Type, ContractString, CompilerOpts). -record(string, {ann,bin}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(record, {ann,fields}). %Create a record
-record(field, {ann,name,value}). %A record field
-record(proj, {ann,value}). %?
-record(map, {ann,fields}). %Create a map
-record(map_get, {ann,field}).
-record(lam, {ann,args,body}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
-spec render_aci_json(json() | json_text()) -> {ok, binary()}. %% The old deprecated interface.
render_aci_json(Json) ->
do_render_aci_json(Json).
-spec json_encode_expr(aeso_syntax:expr()) -> json(). encode(C) -> encode_contract(C).
json_encode_expr(Expr) -> encode(C, Os) -> encode_contract(C, Os).
encode_expr(Expr). decode(J) -> decode_contract(J).
-spec json_encode_type(aeso_syntax:type()) -> json(). %% encode_contract(ContractString) -> {ok,JSON} | {error,String}.
json_encode_type(Type) -> %% encode_contract(ContractString, Options) -> {ok,JSON} | {error,String}.
encode_type(Type). %% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
%% Internal functions encode_contract(ContractString) ->
do_contract_interface(Type, Contract, Options) when is_binary(Contract) -> encode_contract(ContractString, []).
do_contract_interface(Type, binary_to_list(Contract), Options); encode_contract(ContractString, Options) when is_binary(ContractString) ->
do_contract_interface(Type, ContractString, Options) -> encode_contract(binary_to_list(ContractString), Options);
encode_contract(ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("Ast\n~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), %% aeso_ast:pp(Ast),
%% io:format("~p\n", [TypedAst]), TypedAst = aeso_ast_infer_types:infer(Ast, Options),
JArray = [ encode_contract(C) || C <- TypedAst ], %% io:format("Typed ast\n~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
case Type of %% We find and look at the last contract.
json -> {ok, JArray}; Contract = lists:last(TypedAst),
string -> do_render_aci_json(JArray) Cname = contract_name(Contract),
end Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>,
[{<<"name">>, do_encode_name(Cname)}] ++
Tdefs ++
[{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -89,259 +119,394 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")). list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> %% do_encode_contract_typedefs(TypeDefs) -> [JSON].
C0 = #{name => encode_name(Name)}, %% Return a list of typedefs and state and event if they occur.
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ], do_encode_contract_typedefs(Tdefs) ->
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end, Fun = fun(T, {Ts,Ss,Es}) ->
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0), %% Only one state and event.
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1), case typedef_name(T) of
"state" -> {Ts,[do_encode_state_typedef(T)],Es};
C1 = C0#{type_defs => Tdefs}, "event" -> {Ts,Ss,[do_encode_event_typedef(T)]};
_Name -> {Ts ++ [do_encode_typedef(T)],Ss,Es}
C2 = case Es of end
[] -> C1;
[#{typedef := ET}] -> C1#{event => ET}
end, end,
{Ts,Ss,Es} = lists:foldl(Fun, {[],[],[]}, Tdefs),
Ss ++ [{<<"type_defs">>, Ts}] ++ Es.
C3 = case Ss of %% do_encode_state_typedef(StateTdef) -> JSON.
[] -> C2; %% do_encode_event_typedef(EventTdef) -> JSON.
[#{typedef := ST}] -> C2#{state => ST}
end,
Fdefs = [ encode_function(F) do_encode_state_typedef(State) ->
|| F <- sort_decls(contract_funcs(Contract)), Def = typedef_def(State),
is_entrypoint(F) ], {<<"state">>,do_encode_alias(Def)}.
#{contract => C3#{functions => Fdefs}}; do_encode_event_typedef(State) ->
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> Def = typedef_def(State),
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ], {<<"event">>,do_encode_alias(Def)}.
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
%% 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_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name),
arguments => encode_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDef)};
encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) ->
#{name => encode_name(Name),
arguments => encode_anon_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDecl)}.
encode_anon_args(Types) -> do_encode_func(Fdef) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], Name = function_name(Fdef),
[ #{name => V, type => encode_type(T)} Args = function_args(Fdef),
|| {V, T} <- lists:zip(Anons, Types) ]. Type = function_type(Fdef),
[{<<"name">>, do_encode_name(Name)},
{<<"arguments">>, do_encode_args(Args)},
{<<"returns">>, do_encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_args(Args) -> [ encode_arg(A) || A <- Args ]. %% encode_arg(TypedAST) -> JSON.
%% Encode an argument AST into a JSON structure.
encode_arg({arg, _, Id, T}) -> encode_arg(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_arg(AST)).
type => encode_type(T)}.
encode_typedef(Type) -> %% do_encode_args(ArgASTs) -> [JSONmap].
%% do_encode_arg(ArgAST) -> JSONmap.
do_encode_args(Args) ->
[ do_encode_arg(A) || A <- Args ].
do_encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% encode_type(TypedAST) -> JSON.
%% Encode a type AST into a JSON structure.
encode_type(AST) ->
jsx:encode(do_encode_type(AST)).
%% do_encode_types([TypeAST]) -> [JSONmap].
%% do_encode_type(TypeAST) -> JsonMap.
do_encode_types(Types) ->
[ do_encode_type(T) || T <- Types ].
do_encode_type(#tvar{name=N}) -> do_encode_name(N);
do_encode_type(#id{name=N}) -> do_encode_name(N);
do_encode_type(#con{name=N}) -> do_encode_name(N);
do_encode_type(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_type(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_type(#tuple_t{args=As}) ->
Eas = do_encode_types(As),
[{<<"tuple">>,Eas}];
do_encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>,Len};
do_encode_type(#record_t{fields=Fs}) ->
Efs = do_encode_type_rec_fields(Fs),
[{<<"record">>,Efs}];
%% Special case lists and maps as they are built-in types.
do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
Ef = do_encode_type(F),
[{<<"list">>,Ef}];
do_encode_type(#app_t{id=#id{name="map"},fields=Fs}) ->
Ef = do_encode_type_mapo_field(Fs),
[{<<"map">>,Ef}];
%% Other applications.
do_encode_type(#app_t{id=Id,fields=Fs}) ->
Name = do_encode_type(Id),
Efs = do_encode_types(Fs),
[{Name,Efs}];
do_encode_type(#variant_t{cons=Cs}) ->
Ecs = do_encode_types(Cs),
[{<<"variant">>,Ecs}];
do_encode_type(#constr_t{con=C,args=As}) ->
Ec = do_encode_type(C),
Eas = do_encode_types(As),
[{Ec,Eas}];
do_encode_type(#fun_t{args=As,type=T}) ->
Eas = do_encode_types(As),
Et = do_encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
do_encode_name(Name) ->
list_to_binary(Name).
%% do_encode_type_rec_fields(Fields) -> [JSONmap].
%% do_encode_type_rec_field(Field) -> JSONmap.
%% Encode a record field type.
do_encode_type_rec_fields(Fs) ->
[ do_encode_type_rec_field(F) || F <- Fs ].
do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% do_encode_type_mapo_field(Field) -> JSONmap.
%% Two fields for one map type.
do_encode_type_mapo_field([K,V]) ->
[{<<"key">>,do_encode_type(K)},
{<<"value">>,do_encode_type(V)}].
%% do_encode_typedef(TypeDefAST) -> JSON.
do_encode_typedef(Type) ->
Name = typedef_name(Type), Name = typedef_name(Type),
Vars = typedef_vars(Type), Vars = typedef_vars(Type),
Def = typedef_def(Type), Def = typedef_def(Type),
#{name => encode_name(Name), [{<<"name">>, do_encode_name(Name)},
vars => encode_tvars(Vars), {<<"vars">>, do_encode_tvars(Vars)},
typedef => encode_type(Def)}. {<<"typedef">>, do_encode_alias(Def)}].
encode_tvars(Vars) -> do_encode_tvars(Vars) ->
[ #{name => encode_type(V)} || V <- Vars ]. [ do_encode_tvar(V) || V <- Vars ].
%% Encode type do_encode_tvar(#tvar{name=N}) ->
encode_type({tvar, _, N}) -> encode_name(N); [{<<"name">>, do_encode_name(N)}].
encode_type({id, _, N}) -> encode_name(N);
encode_type({con, _, N}) -> encode_name(N);
encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)};
encode_type({bytes_t, _, Len}) -> #{bytes => Len};
encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)};
encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)};
encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)};
encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
encode_type({alias_t, Type}) -> encode_type(Type);
encode_type({fun_t, _, _, As, T}) -> #{function =>
#{arguments => encode_types(As),
returns => encode_type(T)}}.
encode_types(Ts) -> [ encode_type(T) || T <- Ts ]. do_encode_alias(#alias_t{type=T}) ->
do_encode_type(T);
do_encode_alias(A) -> do_encode_type(A).
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ]. %% encode_stmt(StmtAST) -> JSON.
%% Encode a statement AST into a JSON structure.
encode_type_field({field_t, _, Id, T}) -> encode_stmt(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_stmt(AST)).
type => encode_type(T)}.
encode_name(Name) when is_list(Name) -> %% do_encode_stmt(StmtAST) -> JSONmap.
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
%% Encode Expr do_encode_stmt(#typed{expr=E}) -> %Ignore the type
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ]. do_encode_stmt(E);
do_encode_stmt(#block{body=Body}) ->
Eblock = [ do_encode_stmt(B) || B <- Body ],
[{<<"block">>,Eblock}];
do_encode_stmt(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_stmt(Then),
Eelse = do_encode_stmt(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_stmt(#letval{pat=Pat,exp=Exp}) ->
Epat = do_encode_expr(Pat),
Eexp = do_encode_expr(Exp),
[{<<"let">>,[{<<"pattern">>,Epat},{<<"expression">>,Eexp}]}];
do_encode_stmt(#switch{arg=Arg,cases=Cases}) ->
Earg = do_encode_expr(Arg),
Ecases = [ do_encode_stmt_case(Case) || Case <- Cases ],
[{<<"switch">>,[{<<"arg">>,Earg},{<<"cases">>,Ecases}]}];
do_encode_stmt(E) ->
do_encode_expr(E).
encode_expr({id, _, N}) -> encode_name(N); do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
encode_expr({con, _, N}) -> encode_name(N); Epat = do_encode_expr(Pat), %Patterns are expessions
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns)); Ebody = do_encode_stmt(Body),
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns)); [{<<"pattern">>,Epat},{<<"body">>,Ebody}].
encode_expr({typed, _, E}) -> encode_expr(E);
encode_expr({bool, _, B}) -> B;
encode_expr({int, _, V}) -> V;
encode_expr({string, _, S}) -> S;
encode_expr({tuple, _, As}) -> encode_exprs(As);
encode_expr({list, _, As}) -> encode_exprs(As);
encode_expr({bytes, _, B}) ->
Digits = byte_size(B),
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
#{Ef => Eas};
encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds));
encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ];
encode_expr({Op,_Ann}) ->
error({encode_expr_todo, Op}).
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ]. %% encode_expr(ExprAST) -> JSON.
%% Encode an expression AST into a JSON structure.
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) -> encode_expr(AST) ->
{encode_name(Fld), encode_expr(Val)}. jsx:encode(do_encode_expr(AST)).
do_render_aci_json(Json) -> %% do_encode_exprs(ExprASTs) -> [JSONmap].
Contracts = %% do_encode_expr(ExprAST) -> JSONmap.
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || C <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name, do_encode_exprs(Es) ->
type_defs := Ts0, [ do_encode_expr(E) || E <- Es ].
functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
["contract ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. do_encode_expr(#typed{expr=E}) -> %Ignore the type
do_encode_expr(E);
do_encode_expr(#id{name=N}) -> do_encode_name(N);
do_encode_expr(#con{name=N}) -> do_encode_name(N);
do_encode_expr(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_expr(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_expr(#bool{bool=B}) -> B;
do_encode_expr(#int{value=V}) -> V;
do_encode_expr(#string{bin=B}) ->
[{<<"string">>,B}];
do_encode_expr(#bytes{bin=B}) -> B;
do_encode_expr(#tuple{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"tuple">>,Eas}];
do_encode_expr(#list{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"list">>,Eas}];
do_encode_expr(#record{fields=Fs}) -> %Create a record
Efs = do_encode_expr_rec_fields(Fs),
[{<<"create_record">>,Efs}];
do_encode_expr({record,_Ann,Rec,Fs}) -> %Update a record
Erec = do_encode_expr(Rec),
Efs = do_encode_expr_rec_fields(Fs),
[{<<"update_record">>,[Erec,Efs]}];
do_encode_expr(#lam{args=As,body=B}) ->
Eas = do_encode_args(As),
Eb = do_encode_stmt(B),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"body">>,Eb}]}];
do_encode_expr(#map{fields=Fs}) -> %Create a map
Efs = do_encode_expr_map_fields(Fs),
[{<<"create_map">>,Efs}];
do_encode_expr({map,_Ann,Map,Fs}) -> %Update a map
Emap = do_encode_expr(Map),
Efs = do_encode_expr_map_fields(Fs),
[{<<"update_map">>,[Emap,Efs]}];
do_encode_expr(#map_get{field=F}) ->
do_encode_expr(F);
do_encode_expr(#proj{value=V}) ->
do_encode_expr(V);
do_encode_expr(#app{func=F,args=As}) ->
Ef = do_encode_expr(F),
Eas = do_encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
do_encode_expr(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_expr(Then),
Eelse = do_encode_expr(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode_func(#{name := init}) -> []; %% do_encode_expr_rec_fields(Fields) -> [JSON].
decode_func(#{name := Name, arguments := As, returns := T}) -> %% do_encode_expr_rec_field(Field) -> JSON.
[" entrypoint", " ", io_lib:format("~s", [Name]), " : ", %% Encode a record field expression.
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) -> do_encode_expr_rec_fields(Fs) ->
Das = [ decode_arg(A) || A <- As ], [ do_encode_expr_rec_field(F) || F <- Fs ].
do_encode_expr_rec_field(#field{name=[N],value=V}) ->
[{<<"name">>,do_encode_expr(N)},
{<<"value">>,do_encode_expr(V)}].
%% do_encode_expr_map_fields(Fields) -> [JSON].
%% do_encode_expr_map_field(Field) -> JSON.
%% Encode a map field expression.
do_encode_expr_map_fields(Fs) ->
[ do_encode_expr_map_field(F) || F <- Fs ].
do_encode_expr_map_field({K,V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}];
do_encode_expr_map_field(#field{name=[K],value=V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}].
%% decode_contract(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode_contract(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
#{<<"name">> := Name, <<"type_defs">> := Ts, <<"functions">> := Fs} = C,
CS = ["contract"," ",io_lib:format("~s", [Name])," =\n",
do_decode_tdefs(Ts),
do_decode_funcs(Fs)],
list_to_binary(CS).
do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
do_decode_func(#{<<"name">> := <<"init">>}) -> [];
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
do_decode_args(As)," => ",do_decode_type(T),$\n].
do_decode_args(As) ->
Das = [ do_decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)]. [$(,lists:join(", ", Das),$)].
decode_arg(#{type := T}) -> decode_type(T). do_decode_arg(#{<<"type">> := T}) -> do_decode_type(T).
decode_types(Ets) -> do_decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ]. [ do_decode_type(Et) || Et <- Ets ].
decode_type(#{tuple := Ets}) -> do_decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets), Ts = do_decode_types(Ets),
case Ts of [$(,lists:join(", ", Ts),$)];
[] -> ["unit"]; do_decode_type(#{<<"record">> := Efs}) ->
_ -> [$(,lists:join(" * ", Ts),$)] Fs = do_decode_type_rec_fields(Efs),
end;
decode_type(#{record := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(", ", Fs),$}]; [${,lists:join(", ", Fs),$}];
decode_type(#{list := [Et]}) -> do_decode_type(#{<<"list">> := Et}) ->
T = decode_type(Et), T = do_decode_type(Et),
["list",$(,T,$)]; ["list",$(,T,$)];
decode_type(#{map := Ets}) -> do_decode_type(#{<<"map">> := Et}) ->
Ts = decode_types(Ets), T = do_decode_type_map(Et),
["map",$(,lists:join(",", Ts),$)]; ["map",$(,T,$)];
decode_type(#{bytes := Len}) -> do_decode_type(#{<<"variant">> := Ets}) ->
["bytes(", integer_to_list(Len), ")"]; Ts = do_decode_types(Ets),
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts); lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) -> do_decode_type(Econs) when is_map(Econs) -> %General constructor
[decode_type(#{tuple => Args}), " => ", decode_type(R)]; %% io:format("~p\n", [Econs]),
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs), [{Ec,Ets}] = maps:to_list(Econs),
AppName = decode_name(Ec), C = do_decode_name(Ec),
AppArgs = decode_types(Ets), Ts = do_decode_types(Ets),
case AppArgs of [C,$(,lists:join(", ", Ts),$)];
[] -> [AppName]; do_decode_type(T) -> %Just raw names.
_ -> [AppName,$(,lists:join(", ", AppArgs),$)] do_decode_name(T).
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En); do_decode_name(En) ->
decode_name(En) when is_binary(En) -> binary_to_list(En). binary_to_list(En).
decode_fields(Efs) -> do_decode_type_rec_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ]. [ do_decode_type_rec_field(Ef) || Ef <- Efs ].
decode_field(#{name := En, type := Et}) -> do_decode_type_rec_field(#{<<"name">> := En,<<"type">> := Et}) ->
Name = decode_name(En), Name = do_decode_name(En),
Type = decode_type(Et), Type = do_decode_type(Et),
[Name," : ",Type]. [Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString]. do_decode_type_map(#{<<"key">> := Ek,<<"value">> := Ev}) ->
Key = do_decode_type(Ek),
Value = do_decode_type(Ev),
[Key,", ",Value].
%% do_decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the %% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants. %% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ]. do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) -> do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
TypeDef = decode_type(T), [" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
DefType = decode_deftype(T), " = ",do_decode_type(T),$\n].
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_deftype(#{record := _Efs}) -> "record"; do_decode_tvars([]) -> []; %No tvars, no parentheses
decode_deftype(#{variant := _}) -> "datatype"; do_decode_tvars(Vs) ->
decode_deftype(_T) -> "type". Dvs = [ do_decode_tvar(V) || V <- Vs ],
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)]. [$(,lists:join(", ", Dvs),$)].
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). do_decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%% #contract{Ann, Con, [Declarations]}. %% #contract{Ann, Con, [Declarations]}.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> contract_name(#contract{con=#con{name=N}}) -> N.
[ D || D <- Decls, is_fun(D)].
contract_types({C, _, _, Decls}) when C == contract; C == namespace -> contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_type(D) ]. [ D || D <- Decls, is_record(D, letfun) ].
is_fun({letfun, _, _, _, _, _}) -> true; contract_types(#contract{decls=Decls}) ->
is_fun({fun_decl, _, _, _}) -> true; [ D || D <- Decls, is_record(D, type_def) ].
is_fun(_) -> false.
is_type({type_def, _, _, _, _}) -> true; %% To keep dialyzer happy and quiet.
is_type(_) -> false. %% namespace_name(#namespace{con=#con{name=N}}) -> N.
%%
%% namespace_funcs(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, letfun) ].
%%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
sort_decls(Ds) -> sort_decls(Ds) ->
Sort = fun (D1, D2) -> Sort = fun (D1, D2) ->
@@ -350,11 +515,58 @@ sort_decls(Ds) ->
end, end,
lists:sort(Sort, Ds). lists:sort(Sort, Ds).
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false). %% #letfun{Ann, Id, [Arg], Type, Typedef}.
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name. function_name(#letfun{id=#id{name=N}}) -> N.
typedef_vars({type_def, _, _, Vars, _}) -> Vars. function_args(#letfun{args=Args}) -> Args.
typedef_def({type_def, _, _, _, Def}) -> Def. function_type(#letfun{type=Type}) -> Type.
is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error(Pos, ErrorString) ->
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
+84 -335
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{}.
@@ -135,10 +132,6 @@ on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
Scope = maps:get(NS, Scopes), Scope = maps:get(NS, Scopes),
Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }. Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }.
-spec on_scopes(env(), fun((scope()) -> scope())) -> env().
on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), type(), env()) -> env(). -spec bind_var(aeso_syntax:id(), type(), env()) -> env().
bind_var({id, Ann, X}, T, Env) -> bind_var({id, Ann, X}, T, Env) ->
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }. Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
@@ -156,7 +149,7 @@ bind_tvars(Xs, Env) ->
check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) -> check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) ->
case TVars == unrestricted orelse lists:member(X, TVars) of case TVars == unrestricted orelse lists:member(X, TVars) of
true -> ok; true -> ok;
false -> type_error({unbound_type, T}) false -> type_error({unbound_type_variable, T})
end, end,
T. T.
@@ -203,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(
@@ -363,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"]),
@@ -384,11 +376,9 @@ global_env() ->
{"RelativeTTL", Fun1(Int, TTL)}, {"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)}, {"FixedTTL", Fun1(Int, TTL)},
%% Abort %% Abort
{"abort", Fun1(String, A)}, {"abort", Fun1(String, A)}])
{"require", Fun([Bool, String], Unit)}])
, types = MkDefs( , types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0}, [{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
{"unit", {[], {alias_t, Unit}}},
{"hash", {[], {alias_t, Bytes(32)}}}, {"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}}, {"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0}, {"bits", 0},
@@ -399,10 +389,10 @@ 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, Option(Hash))}, {"block_hash", Fun1(Int, Int)},
{"coinbase", Address}, {"coinbase", Address},
{"timestamp", Int}, {"timestamp", Int},
{"block_height", Int}, {"block_height", Int},
@@ -413,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
@@ -429,21 +419,19 @@ 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(
[{"resolve", Fun([String, String], option_t(Ann, A))}, [{"resolve", Fun([String, String], option_t(Ann, A))},
{"preclaim", SignFun([Address, Hash], Unit)}, {"preclaim", SignFun([Address, Hash], Unit)},
{"claim", SignFun([Address, String, Int, Int], Unit)}, {"claim", SignFun([Address, String, Int], Unit)},
{"transfer", SignFun([Address, Address, String], Unit)}, {"transfer", SignFun([Address, Address, Hash], Unit)},
{"revoke", SignFun([Address, String], Unit)}]) }, {"revoke", SignFun([Address, Hash], Unit)}]) },
MapScope = #scope MapScope = #scope
{ funs = MkDefs( { funs = MkDefs(
@@ -491,17 +479,9 @@ global_env() ->
{"none", Bits}, {"none", Bits},
{"all", Bits}]) }, {"all", Bits}]) },
%% Bytes
BytesScope = #scope
{ funs = MkDefs(
[{"to_int", Fun1(Bytes(any), Int)},
{"to_str", Fun1(Bytes(any), String)}]) },
%% 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
@@ -515,7 +495,6 @@ global_env() ->
, ["Crypto"] => CryptoScope , ["Crypto"] => CryptoScope
, ["String"] => StringScope , ["String"] => StringScope
, ["Bits"] => BitsScope , ["Bits"] => BitsScope
, ["Bytes"] => BytesScope
, ["Int"] => IntScope , ["Int"] => IntScope
, ["Address"] => AddressScope , ["Address"] => AddressScope
} }. } }.
@@ -527,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) -> infer(Contracts) ->
infer(Contracts, []). infer(Contracts, []).
-type option() :: return_env | dont_unfold. -type option() :: return_env.
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_env(). init_env(_Options) -> global_env().
@@ -539,38 +518,30 @@ infer(Contracts, Options) ->
Env = init_env(Options), Env = init_env(Options),
create_options(Options), create_options(Options),
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
check_modifiers(Env, Contracts), {Env1, Decls} = infer1(Env, Contracts, []),
{Env1, Decls} = infer1(Env, Contracts, [], Options),
{Env2, Decls2} =
case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls};
false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{E, unfold_record_types(E, Decls)}
end,
case proplists:get_value(return_env, Options, false) of case proplists:get_value(return_env, Options, false) of
false -> Decls2; false -> Decls;
true -> {Env2, Decls2} true -> {Env1, Decls}
end end
after after
clean_up_ets() clean_up_ets()
end. end.
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer1(Env, [], Acc) -> {Env, lists:reverse(Acc)};
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) ->
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code),
Contract1 = {contract, Ann, ConName, Code1}, Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options); infer1(Env3, Rest, [Contract1 | Acc]);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) ->
check_scope_name_clash(Env, namespace, Name), check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code),
Namespace1 = {namespace, Ann, Name, Code1}, Namespace1 = {namespace, Ann, Name, Code1},
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options). infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]).
check_scope_name_clash(Env, Kind, Name) -> check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of case get_scope(Env, qname(Name)) of
@@ -581,11 +552,13 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. end.
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer_contract_top(Env, Kind, Defs0) ->
infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
infer_contract(Env, Kind, Defs). {Env1, Defs1} = infer_contract(Env, Kind, Defs),
Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
Defs2 = unfold_record_types(Env2, Defs1),
{Env2, Defs2}.
%% TODO: revisit %% TODO: revisit
infer_constant({letval, Attrs,_Pattern, Type, E}) -> infer_constant({letval, Attrs,_Pattern, Type, E}) ->
@@ -624,17 +597,15 @@ infer_contract(Env, What, Defs) ->
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]), %% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
{Env4, TypeDefs ++ Decls ++ Defs1}. {Env4, TypeDefs ++ Decls ++ Defs1}.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env = #env{ namespace = Ns }, Defs) -> check_typedefs(Env, Defs) ->
create_type_errors(), create_type_errors(),
GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end, GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end,
TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]), TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Ns, Def) end, TypeMap), DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap),
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
{Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []), {Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []),
destroy_and_report_type_errors(Env), destroy_and_report_type_errors(Env),
@@ -687,40 +658,6 @@ check_typedef(Env, {variant_t, Cons}) ->
check_unexpected(Xs) -> check_unexpected(Xs) ->
[ type_error(X) || X <- Xs ]. [ type_error(X) || X <- Xs ].
check_modifiers(Env, Contracts) ->
create_type_errors(),
[ case C of
{contract, _, Con, Decls} ->
check_modifiers1(contract, Decls),
case {lists:keymember(letfun, 1, Decls),
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
{true, []} -> type_error({contract_has_no_entrypoints, Con});
_ -> ok
end;
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls)
end || C <- Contracts ],
destroy_and_report_type_errors(Env).
-spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok.
check_modifiers1(What, Decls) when is_list(Decls) ->
_ = [ check_modifiers1(What, Decl) || Decl <- Decls ],
ok;
check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl ->
Public = aeso_syntax:get_ann(public, Decl, false),
Private = aeso_syntax:get_ann(private, Decl, false),
Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false),
FunDecl = element(1, Decl) == fun_decl,
{id, _, Name} = element(3, Decl),
_ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ],
_ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ],
_ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, Name == "init", What == contract ],
_ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ],
_ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ],
_ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ],
_ = [ type_error({private_and_public, Decl}) || Private, Public ],
ok;
check_modifiers1(_, _) -> ok.
-spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type(). -spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type().
check_type(Env, T) -> check_type(Env, T) ->
check_type(Env, T, 0). check_type(Env, T, 0).
@@ -791,35 +728,35 @@ check_event(Env, "event", Ann, Def) ->
check_event(_Env, _Name, _Ann, Def) -> Def. check_event(_Env, _Name, _Ann, Def) -> Def.
check_event_con(Env, {constr_t, Ann, Con, Args}) -> check_event_con(Env, {constr_t, Ann, Con, Args}) ->
IsIndexed = fun(T) -> IsIndexed = fun(T) -> case aeso_syntax:get_ann(indexed, T, false) of
T1 = unfold_types_in_type(Env, T), true -> indexed;
%% `indexed` is optional but if used it has to be correctly used false -> notindexed
case {is_word_type(T1), is_string_type(T1), aeso_syntax:get_ann(indexed, T, false)} of end end,
{true, _, _} -> indexed;
{false, true, true} -> type_error({indexed_type_must_be_word, T, T1});
{false, true, _} -> notindexed;
{false, false, _} -> type_error({event_arg_type_word_or_string, T, T1}), error
end
end,
Indices = lists:map(IsIndexed, Args), Indices = lists:map(IsIndexed, Args),
Indexed = [ T || T <- Args, IsIndexed(T) == indexed ], Indexed = [ T || T <- Args, IsIndexed(T) == indexed ],
NonIndexed = Args -- Indexed, NonIndexed = Args -- Indexed,
[ check_event_arg_type(Env, Ix, Type) || {Ix, Type} <- lists:zip(Indices, Args) ],
[ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ], [ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ],
[ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ], [ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ],
{constr_t, [{indices, Indices} | Ann], Con, Args}. {constr_t, [{indices, Indices} | Ann], Con, Args}.
check_event_arg_type(Env, Ix, Type0) ->
Type = unfold_types_in_type(Env, Type0),
case Ix of
indexed -> [ type_error({indexed_type_must_be_word, Type0, Type}) || not is_word_type(Type) ];
notindexed -> [ type_error({payload_type_must_be_string, Type0, Type}) || not is_string_type(Type) ]
end.
%% Not so nice. %% Not so nice.
is_word_type({id, _, Name}) -> is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]); lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
is_word_type({app_t, _, {id, _, Name}, [_, _]}) -> is_word_type({app_t, _, {id, _, Name}, [_, _]}) ->
lists:member(Name, ["oracle", "oracle_query"]); lists:member(Name, ["oracle", "oracle_query"]);
is_word_type({bytes_t, _, N}) -> N =< 32;
is_word_type({con, _, _}) -> true; is_word_type({con, _, _}) -> true;
is_word_type({qcon, _, _}) -> true; is_word_type({qcon, _, _}) -> true;
is_word_type(_) -> false. is_word_type(_) -> false.
is_string_type({id, _, "string"}) -> true; is_string_type({id, _, "string"}) -> true;
is_string_type({bytes_t, _, N}) -> N > 32;
is_string_type(_) -> false. is_string_type(_) -> false.
-spec check_constructor_overlap(env(), aeso_syntax:con(), type()) -> ok | no_return(). -spec check_constructor_overlap(env(), aeso_syntax:con(), type()) -> ok | no_return().
@@ -856,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).
@@ -898,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],
@@ -918,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),
@@ -934,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)]).
@@ -964,70 +891,14 @@ 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(As, typesig_to_fun_t(Ty)); {type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
_ when Freshen -> freshen_type(As, Ty); _ when Freshen -> freshen_type(Ty);
_ -> Ty _ -> Ty
end, end,
{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}),
@@ -1037,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}) ->
@@ -1066,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],
@@ -1119,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{
@@ -1184,17 +1055,10 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} = {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} =
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType), infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}.
infer_expr(Env, Let = {letval, Attrs, _, _, _}) ->
type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing 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,
@@ -1255,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}),
@@ -1264,18 +1128,19 @@ 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, _, [E], BlockType) -> infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
[check_expr(Env, E, BlockType)]; NewDef = infer_letfun(Env, Def),
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> [NewDef|infer_block(Env, Attrs, Rest, BlockType)];
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), NewDef = infer_letrec(Env, Def),
NewE = bind_var({id, Ann, Name}, FunT, Env), [NewDef|infer_block(Env, Attrs, Rest, BlockType)];
[LetFun|infer_block(NewE, 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}, _}} =
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType), infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
[{letval, Attrs, NewPattern, Type, NewE}|NewRest]; [{letval, Attrs, NewPattern, Type, NewE}|NewRest];
infer_block(Env, _, [E], BlockType) ->
[check_expr(Env, E, BlockType)];
infer_block(Env, Attrs, [E|Rest], BlockType) -> infer_block(Env, Attrs, [E|Rest], BlockType) ->
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)]. [infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
@@ -1311,13 +1176,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
Int = {id, As, "int"}, Int = {id, As, "int"},
{fun_t, As, [], [Int], Int}. {fun_t, As, [], [Int], Int}.
abort_expr(Ann, Str) ->
{app, Ann, {id, Ann, "abort"}, [{string, Ann, Str}]}.
free_vars({int, _, _}) -> free_vars({int, _, _}) ->
[]; [];
free_vars({char, _, _}) ->
[];
free_vars({string, _, _}) -> free_vars({string, _, _}) ->
[]; [];
free_vars({bool, _, _}) -> free_vars({bool, _, _}) ->
@@ -1408,17 +1268,14 @@ when_option(Opt, Do) ->
create_constraints() -> create_constraints() ->
create_named_argument_constraints(), create_named_argument_constraints(),
create_bytes_constraints(),
create_field_constraints(). create_field_constraints().
solve_constraints(Env) -> solve_constraints(Env) ->
solve_named_argument_constraints(Env), solve_named_argument_constraints(Env),
solve_bytes_constraints(Env),
solve_field_constraints(Env). solve_field_constraints(Env).
destroy_and_report_unsolved_constraints(Env) -> destroy_and_report_unsolved_constraints(Env) ->
destroy_and_report_unsolved_field_constraints(Env), destroy_and_report_unsolved_field_constraints(Env),
destroy_and_report_unsolved_bytes_constraints(Env),
destroy_and_report_unsolved_named_argument_constraints(Env). destroy_and_report_unsolved_named_argument_constraints(Env).
%% -- Named argument constraints -- %% -- Named argument constraints --
@@ -1467,37 +1324,6 @@ destroy_and_report_unsolved_named_argument_constraints(Env) ->
destroy_named_argument_constraints(), destroy_named_argument_constraints(),
ok. ok.
%% -- Bytes constraints --
-type byte_constraint() :: {is_bytes, utype()}.
create_bytes_constraints() ->
ets_new(bytes_constraints, [bag]).
get_bytes_constraints() ->
ets_tab2list(bytes_constraints).
-spec add_bytes_constraint(byte_constraint()) -> true.
add_bytes_constraint(Constraint) ->
ets_insert(bytes_constraints, Constraint).
solve_bytes_constraints(_Env) ->
ok.
destroy_bytes_constraints() ->
ets_delete(bytes_constraints).
destroy_and_report_unsolved_bytes_constraints(Env) ->
[ check_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
destroy_bytes_constraints().
check_bytes_constraint(Env, {is_bytes, Type}) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
case Type1 of
{bytes_t, _, _} -> ok;
_ -> type_error({cannot_unify, Type1, {bytes_t, [], any}, {at, Type}})
end.
%% -- Field constraints -- %% -- Field constraints --
create_field_constraints() -> create_field_constraints() ->
@@ -1907,10 +1733,6 @@ occurs_check1(R, {tuple_t, _, Ts}) ->
occurs_check(R, Ts); occurs_check(R, Ts);
occurs_check1(R, {named_arg_t, _, _, T, _}) -> occurs_check1(R, {named_arg_t, _, _, T, _}) ->
occurs_check(R, T); occurs_check(R, T);
occurs_check1(R, {record_t, Fields}) ->
occurs_check(R, Fields);
occurs_check1(R, {field_t, _, _, T}) ->
occurs_check(R, T);
occurs_check1(R, [H | T]) -> occurs_check1(R, [H | T]) ->
occurs_check(R, H) orelse occurs_check(R, T); occurs_check(R, H) orelse occurs_check(R, T);
occurs_check1(_, []) -> false. occurs_check1(_, []) -> false.
@@ -1924,31 +1746,26 @@ create_freshen_tvars() ->
destroy_freshen_tvars() -> destroy_freshen_tvars() ->
ets_delete(freshen_tvars). ets_delete(freshen_tvars).
freshen_type(Ann, Type) -> freshen_type(Type) ->
create_freshen_tvars(), create_freshen_tvars(),
Type1 = freshen(Ann, Type), Type1 = freshen(Type),
destroy_freshen_tvars(), destroy_freshen_tvars(),
Type1. Type1.
freshen(Type) -> freshen({tvar, As, Name}) ->
freshen(aeso_syntax:get_ann(Type), Type).
freshen(Ann, {tvar, _, Name}) ->
NewT = case ets_lookup(freshen_tvars, Name) of NewT = case ets_lookup(freshen_tvars, Name) of
[] -> fresh_uvar(Ann); [] ->
[{Name, T}] -> T fresh_uvar(As);
[{Name, T}] ->
T
end, end,
ets_insert(freshen_tvars, {Name, NewT}), ets_insert(freshen_tvars, {Name, NewT}),
NewT; NewT;
freshen(Ann, {bytes_t, _, any}) -> freshen(T) when is_tuple(T) ->
X = fresh_uvar(Ann), list_to_tuple(freshen(tuple_to_list(T)));
add_bytes_constraint({is_bytes, X}), freshen([A|B]) ->
X; [freshen(A)|freshen(B)];
freshen(Ann, T) when is_tuple(T) -> freshen(X) ->
list_to_tuple(freshen(Ann, tuple_to_list(T)));
freshen(Ann, [A | B]) ->
[freshen(Ann, A) | freshen(Ann, B)];
freshen(_, X) ->
X. X.
%% Dereferences all uvars and replaces the uninstantiated ones with a %% Dereferences all uvars and replaces the uninstantiated ones with a
@@ -1957,8 +1774,8 @@ instantiate(E) ->
instantiate1(dereference(E)). instantiate1(dereference(E)).
instantiate1({uvar, Attr, R}) -> instantiate1({uvar, Attr, R}) ->
Next = proplists:get_value(next, ets_lookup(type_vars, next), 0), Next = proplists:get_value(next, ets_lookup(type_vars, next), 1),
TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)}, TVar = {tvar, Attr, "'" ++ integer_to_list(Next)},
ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]), ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]),
TVar; TVar;
instantiate1({fun_t, Ann, Named, Args, Ret}) -> instantiate1({fun_t, Ann, Named, Args, Ret}) ->
@@ -1978,12 +1795,6 @@ instantiate1([A|B]) ->
instantiate1(X) -> instantiate1(X) ->
X. X.
integer_to_tvar(X) when X < 26 ->
[$a + X];
integer_to_tvar(X) ->
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
%% Save unification failures for error messages. %% Save unification failures for error messages.
cannot_unify(A, B, When) -> cannot_unify(A, B, When) ->
@@ -1996,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),
@@ -2106,73 +1917,16 @@ pp_error({duplicate_definition, Name, Locs}) ->
pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> pp_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n",
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]);
pp_error({include, _, {string, Pos, Name}}) -> pp_error({include, {string, Pos, Name}}) ->
io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n",
[binary_to_list(Name), pp_loc(Pos)]); [binary_to_list(Name), pp_loc(Pos)]);
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({missing_body_for_let, Ann}) ->
io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]);
pp_error({public_modifier_in_contract, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({init_must_be_an_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n",
[pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({proto_must_be_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({proto_in_namespace, Decl}) ->
io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n",
[pp_loc(Decl)]);
pp_error({entrypoint_in_namespace, Decl}) ->
io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n",
[pp_loc(Decl)]);
pp_error({private_entrypoint, Decl}) ->
io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
pp_error({private_and_public, Decl}) ->
io_lib:format("The function ~s (at ~s) cannot be both public and private.\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
pp_error({contract_has_no_entrypoints, Con}) ->
io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n"
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]);
pp_error({unbound_type, Type}) ->
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]);
pp_error(Err) -> pp_error(Err) ->
io_lib:format("Unknown error: ~p\n", [Err]). io_lib:format("Unknown error: ~p\n", [Err]).
mk_entrypoint(Decl) ->
Ann = [entrypoint | lists:keydelete(public, 1,
lists:keydelete(private, 1,
aeso_syntax:get_ann(Decl))) -- [public, private]],
aeso_syntax:set_ann(Ann, Decl).
pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]);
pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]);
pp_when({check_typesig, Name, Inferred, Given}) -> pp_when({check_typesig, Name, Inferred, Given}) ->
io_lib:format("when checking the definition of ~s\n" io_lib:format("when checking the definition of ~s\n"
" inferred type: ~s\n" " inferred type: ~s\n"
@@ -2323,11 +2077,8 @@ pp({uvar, _, Ref}) ->
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ]; ["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
pp({tvar, _, Name}) -> pp({tvar, _, Name}) ->
Name; Name;
pp({tuple_t, _, []}) ->
"unit";
pp({tuple_t, _, Cpts}) -> pp({tuple_t, _, Cpts}) ->
["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"]; ["(", pp(Cpts), ")"];
pp({bytes_t, _, any}) -> "bytes(_)";
pp({bytes_t, _, Len}) -> pp({bytes_t, _, Len}) ->
["bytes(", integer_to_list(Len), ")"]; ["bytes(", integer_to_list(Len), ")"];
pp({app_t, _, T, []}) -> pp({app_t, _, T, []}) ->
@@ -2339,9 +2090,7 @@ pp({named_arg_t, _, Name, Type, Default}) ->
pp({fun_t, _, Named = {uvar, _, _}, As, B}) -> pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
["(", pp(Named), " | ", pp(As), ") => ", pp(B)]; ["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
pp({fun_t, _, Named, As, B}) when is_list(Named) -> pp({fun_t, _, Named, As, B}) when is_list(Named) ->
["(", pp(Named ++ As), ") => ", pp(B)]; ["(", pp(Named ++ As), ") => ", pp(B)].
pp(Other) ->
io_lib:format("~p", [Other]).
%% -- Pre-type checking desugaring ------------------------------------------- %% -- Pre-type checking desugaring -------------------------------------------
File diff suppressed because it is too large Load Diff
+46 -76
View File
@@ -21,19 +21,18 @@ convert_typed(TypedTree, Options) ->
{contract, _, {con, _, Con}, _} -> Con; {contract, _, {con, _, Con}, _} -> Con;
_ -> gen_error(last_declaration_must_be_contract) _ -> gen_error(last_declaration_must_be_contract)
end, end,
NewIcode = aeso_icode:set_name(Name, aeso_icode:new(Options)), Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))),
Icode = code(TypedTree, NewIcode, Options),
deadcode_elimination(Icode). deadcode_elimination(Icode).
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) -> code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
code(Rest, NewIcode, Options); code(Rest, NewIcode);
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) -> code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
%% TODO: nested namespaces %% TODO: nested namespaces
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)), NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)),
code(Rest, NewIcode, Options); code(Rest, NewIcode);
code([], Icode, Options) -> code([], Icode) ->
add_default_init_function(add_builtins(Icode), Options). add_default_init_function(add_builtins(Icode)).
%% Generate error on correct format. %% Generate error on correct format.
@@ -41,12 +40,10 @@ gen_error(Error) ->
error({code_errors, [Error]}). error({code_errors, [Error]}).
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
NoCode = proplists:get_value(no_code, Options, false),
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode), {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when NoCode -> Icode;
false when State /= {tuple, []} -> false when State /= {tuple, []} ->
gen_error(missing_init_function); gen_error(missing_init_function);
false -> false ->
@@ -105,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);
@@ -144,11 +148,10 @@ ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_n
ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) -> ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) ->
#prim_balance{ address = ast_body(Address, Icode) }; #prim_balance{ address = ast_body(Address, Icode) };
ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) -> ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]); #prim_block_hash{ height = ast_body(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;
@@ -176,9 +179,8 @@ ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
%% Abort %% Abort
ast_body(?id_app("abort", [String], _, _), Icode) -> ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, abort} },
ast_body(?id_app("require", [Bool, String], _, _), Icode) -> args = [ast_body(String, Icode)] };
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Authentication %% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
@@ -222,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'});
@@ -263,21 +254,21 @@ ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) ->
[word, word, sign_t()], {tuple, []}); [word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args), {Sign, [Addr, Name, Salt]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(Sign, Icode), ast_body(NameFee, Icode)], [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(Sign, Icode)],
[word, string, word, sign_t(), word], {tuple, []}); [word, string, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args), {Sign, [FromAddr, ToAddr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []}); [word, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args), {Sign, [Addr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(Addr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []}); [word, word, sign_t()], {tuple, []});
ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'}); ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'});
@@ -374,11 +365,13 @@ ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
%% Strings %% Strings
%% -- String length %% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_length} },
args = [ast_body(String, Icode)] };
%% -- String concat %% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_concat} },
args = [ast_body(String1, Icode), ast_body(String2, Icode)] };
%% -- String hash (sha3) %% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
@@ -417,19 +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);
ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
@@ -477,7 +457,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
Gas = proplists:get_value("gas", ArgOpts ++ Defaults), Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
Value = proplists:get_value("value", ArgOpts ++ Defaults), Value = proplists:get_value("value", ArgOpts ++ Defaults),
OutType = ast_typerep(OutT, Icode), OutType = ast_typerep(OutT, Icode),
<<TypeHash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType), <<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
%% The function is represented by its type hash (which includes the name) %% The function is represented by its type hash (which includes the name)
Fun = #integer{value = TypeHash}, Fun = #integer{value = TypeHash},
#prim_call_contract{ #prim_call_contract{
@@ -532,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) ->
@@ -601,30 +579,19 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
_ when not Monomorphic -> _ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type}); gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
OtherType -> string ->
Neg = case Op of Neg = case Op of
'==' -> fun(X) -> X end; '==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare, Ann, Op, Type})
end, end,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
Builtin = args = [ast_body(A, Icode), ast_body(B, Icode)] });
case OtherType of
string ->
builtin_call(str_equal, Args);
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]);
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> 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) ->
builtin_call(list_concat, [ast_body(A, Icode), ast_body(B, Icode)]); #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -691,7 +658,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
true -> true ->
PrimBin = binary:encode_unsigned(Prim), PrimBin = binary:encode_unsigned(Prim),
ArgType = {tuple, ArgTypes}, ArgType = {tuple, ArgTypes},
<<TH:256>> = aeb_aevm_abi:function_type_hash(PrimBin, ArgType, OutType), <<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
TH; TH;
false -> false ->
0 0
@@ -730,8 +697,8 @@ 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, _, {I, _, Name}, Args}, Icode) when I =:= id; I =:= qid -> 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);
ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) -> ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) ->
@@ -759,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],
@@ -794,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 ] }] };
@@ -820,11 +788,13 @@ has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if not an 'entrypoint', or if it's not defined in the %% A function is private if marked 'private' or 'internal', or if it's not
%% main contract name space. (NOTE: changes when we introduce inheritance). %% defined in the main contract name space. (NOTE: changes when we introduce
%% inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) -> is_private(Ann, #{ contract_name := MainContract } = Icode) ->
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode), {_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
not proplists:get_value(entrypoint, Ann, false) orelse proplists:get_value(private, Ann, false) orelse
proplists:get_value(internal, Ann, false) orelse
MainContract /= CurrentNamespace. MainContract /= CurrentNamespace.
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
+17 -94
View File
@@ -44,9 +44,7 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker];
builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> []. builtin_deps1(_) -> [].
dep_closure(Deps) -> dep_closure(Deps) ->
@@ -62,14 +60,12 @@ v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}. option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}. option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }). -define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}). -define(I(X), {integer, X}).
-define(V(X), v(X)). -define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)). -define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}). -define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}). -define(DEREF(Var, Ptr, Body), {switch, v(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)). -define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})). -define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)). -define(BYTE(Ix, Word), op('byte', Ix, Word)).
@@ -95,6 +91,12 @@ operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I}; operand(I) when is_integer(I) -> {integer, I};
operand(T) -> T. operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) -> check_event_type(Icode) ->
case maps:get(event_type, Icode) of case maps:get(event_type, Icode) of
{variant_t, Cons} -> {variant_t, Cons} ->
@@ -115,12 +117,11 @@ check_event_type(EvtName, Ix, Type, Icode) ->
catch _:_ -> catch _:_ ->
error({EvtName, could_not_resolve_type, Type}) error({EvtName, could_not_resolve_type, Type})
end, end,
case {Ix, VMType, Type} of case {Ix, VMType} of
{indexed, word, _} -> ok; {indexed, word} -> ok;
{notindexed, string, _} -> ok; {notindexed, string} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok; {indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType}); {notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end. end.
bfun(B, {IArgs, IExpr, IRet}) -> bfun(B, {IArgs, IExpr, IRet}) ->
@@ -130,8 +131,6 @@ builtin_function(BF) ->
case BF of case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT)); {event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort()); abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type)); {map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put()); map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete()); map_delete -> bfun(BF, builtin_map_delete());
@@ -159,9 +158,6 @@ builtin_function(BF) ->
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_()) string_reverse_ -> bfun(BF, builtin_string_reverse_())
end. end.
@@ -176,22 +172,15 @@ builtin_event(EventT) ->
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end, ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack. Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]}; fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) -> ([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end, end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause = Clause =
fun(_Tag, {con, _, Con}, IxTypes) -> fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ], Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)), {event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end, end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end, Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
@@ -212,17 +201,6 @@ builtin_abort() ->
A(?REVERT)]}, %% Stack: 0,Ptr A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}. {tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives %% Map primitives
builtin_map_lookup(Type) -> builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type), Ret = aeso_icode:option_typerep(Type),
@@ -459,10 +437,6 @@ builtin_baseX_int_pad(X = 10) ->
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]), ?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])}, ?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word}; word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) -> builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}], {[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0), {ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
@@ -497,57 +471,6 @@ builtin_baseX_digits(X) ->
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}), {ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}. word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))),
{seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]},
{inline_asm, []}},
{ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker,
[?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
}
]},
word}.
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]),
{inline_asm, [?A(?POP)]},
?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
Work = fun(I) ->
[?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])),
{inline_asm, [?A(?POP)]}]
end,
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++
[?V(ret)]}),
string}.
builtin_string_reverse() -> builtin_string_reverse() ->
{[{"s", string}], {[{"s", string}],
?DEREF(n, s, ?DEREF(n, s,
+121 -209
View File
@@ -12,16 +12,12 @@
, file/2 , file/2
, from_string/2 , from_string/2
, check_call/4 , check_call/4
, create_calldata/3 %% deprecated , create_calldata/3
, create_calldata/4
, version/0 , version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/4
, to_sophia_value/5 , to_sophia_value/5
, decode_calldata/3 %% deprecated , decode_calldata/3
, decode_calldata/4
, parse/2
, add_include_path/2
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -35,8 +31,6 @@
| pp_icode | pp_icode
| pp_assembler | pp_assembler
| pp_bytecode | pp_bytecode
| no_code
| {backend, aevm | fate}
| {include, {file_system, [string()]} | | {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}} {explicit_files, #{string() => binary()}}}
| {src_file, string()}. | {src_file, string()}.
@@ -67,11 +61,12 @@ version() ->
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). Dir = filename:dirname(Filename),
{ok, Cwd} = file:get_cwd(),
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options0) -> file(File, Options) ->
Options = add_include_path(File, Options0),
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} -> {error, Error} ->
@@ -79,24 +74,24 @@ file(File, Options0) ->
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)} {error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
end. end.
add_include_path(File, Options) ->
case lists:keymember(include, 1, Options) of
true -> Options;
false ->
Dir = filename:dirname(File),
{ok, Cwd} = file:get_cwd(),
[{include, {file_system, [Cwd, Dir]}} | Options]
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(Contract, Options) -> from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) #{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -109,54 +104,18 @@ from_string(Backend, ContractString, Options) ->
%% General programming errors in the compiler just signal error. %% General programming errors in the compiler just signal error.
end. end.
from_string1(aevm, ContractString, Options) -> -spec string_to_icode(string(), [option()]) -> map().
#{icode := Icode} = string_to_code(ContractString, Options), string_to_icode(ContractString, Options) ->
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version()
}};
from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version()
}}.
-spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options), pp_sophia_code(Ast, Options),
pp_ast(Ast, Options), pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(TypedAst, Options), Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options), pp_icode(Icode, Options),
#{ icode => Icode, #{ typed_ast => TypedAst,
typed_ast => TypedAst, type_env => TypeEnv,
type_env => TypeEnv}; icode => Icode }.
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
#{ fcode => Fcode,
typed_ast => TypedAst,
type_env => TypeEnv}
end.
join_errors(Prefix, Errors, Pfun) -> join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
@@ -171,15 +130,14 @@ join_errors(Prefix, Errors, Pfun) ->
%% terms for the arguments. %% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has %% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T) %% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} -spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()}
| {ok, string(), [term()]}
| {error, term()}
when Type :: term(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of PatchFun = fun(T) -> {tuple, [typerep, T]} end,
case check_call(Source, FunName, Args, Options, PatchFun) of
Err = {error, _} when Args == [] -> Err = {error, _} when Args == [] ->
%% Try with default init-function %% Try with default init-function
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of case check_call(insert_init_function(Source, Options), FunName, Args, Options, PatchFun) of
{error, _} -> Err; %% The first error is most likely better... {error, _} -> Err; %% The first error is most likely better...
Res -> Res Res -> Res
end; end;
@@ -187,17 +145,16 @@ check_call(Source, "init" = FunName, Args, Options) ->
Res Res
end; end;
check_call(Source, FunName, Args, Options) -> check_call(Source, FunName, Args, Options) ->
check_call1(Source, FunName, Args, Options). PatchFun = fun(T) -> T end,
check_call(Source, FunName, Args, Options, PatchFun).
check_call1(ContractString0, FunName, Args, Options) -> check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try try
case proplists:get_value(backend, Options, aevm) of
aevm ->
%% First check the contract without the __call function %% First check the contract without the __call function
#{} = string_to_code(ContractString0, Options), #{} = string_to_icode(ContractString0, Options),
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options), ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst, #{typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options), icode := Icode} = string_to_icode(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of RetVMType = case RetType of
@@ -208,25 +165,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
ArgIcode = get_arg_icode(Funs), ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) || ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ], {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 = {ok, FunName, {ArgVMTypes, PatchFun(RetVMType)}, ArgTerms}
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, join_errors("Parse errors", Errors, fun (E) -> E end)};
@@ -240,32 +179,16 @@ check_call1(ContractString0, FunName, Args, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) ->
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
{def, _FName, Args} = Body,
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
first_none_match(_CallName, _Hashes, []) ->
error(unable_to_find_unique_call_name);
first_none_match(CallName, Hashes, [Char|Chars]) ->
case not lists:member(aeb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
true ->
CallName;
false ->
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
end.
%% Add the __call function to a contract. %% Add the __call function to a contract.
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string(). -spec insert_call_function(string(), string(), [string()], options()) -> string().
insert_call_function(Code, Call, FunName, Args, Options) -> insert_call_function(Code, FunName, Args, Options) ->
Ast = parse(Code, Options), Ast = parse(Code, Options),
Ind = last_contract_indent(Ast), Ind = last_contract_indent(Ast),
lists:flatten( lists:flatten(
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), lists:duplicate(Ind, " "),
"stateful entrypoint ", 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().
@@ -275,7 +198,7 @@ insert_init_function(Code, Options) ->
lists:flatten( lists:flatten(
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), "entrypoint init() = ()\n" lists:duplicate(Ind, " "), "function init() = ()\n"
]). ]).
last_contract_indent(Decls) -> last_contract_indent(Decls) ->
@@ -287,39 +210,29 @@ last_contract_indent(Decls) ->
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) -> -spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) -> to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]). to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) -> -spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(_, _, error, Err, _Options) -> to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}}; {ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) -> to_sophia_value(_, _, revert, Data, _Options) ->
case proplists:get_value(backend, Options, aevm) of
aevm ->
case aeb_heap:from_binary(string, Data) of case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}; {ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err {error, _} = Err -> Err
end; end;
fate -> to_sophia_value(ContractString, FunName, ok, Data, Options) ->
Err = aeb_fate_encoding:deserialize(Data),
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), #{ typed_ast := TypedAst,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, Options),
{ok, _, Type0} = get_decode_type(FunName, TypedAst), {ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} -> {ok, VmValue} ->
try try
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)} {ok, translate_vm_value(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", {error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
@@ -329,18 +242,6 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], {error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)} fun(E) -> E end)}
end;
fate ->
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))],
fun (E) -> E end)};
_:R ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))}
end
end end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -355,91 +256,101 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
address_literal(Type, N) -> {Type, [], <<N:256>>}.
%% TODO: somewhere else
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, 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, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
{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({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
none -> {con, [], "None"};
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [translate_vm_value(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
translate_vm_value(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
translate_vm_value(VmTypes, ConType, Args);
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) -> -spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()} {ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}. | {error, term()}.
create_calldata(Code, Fun, Args) -> create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]). case check_call(Code, Fun, Args, []) of
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()}
| {error, term()}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} -> {ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType); aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err {error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end
end. end.
-spec decode_calldata(string(), string(), binary()) -> -spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]} {ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}. | {error, term()}.
decode_calldata(ContractString, FunName, Calldata) -> decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), #{ typed_ast := TypedAst,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, []),
{ok, Args, _} = get_decode_type(FunName, TypedAst), {ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end, DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args), ArgTypes = lists:map(DropArg, Args),
Type0 = {tuple_t, [], ArgTypes}, Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} -> {ok, {_, VmValue}} ->
try try
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue), {tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
%% Values are Sophia expressions in AST format
{ok, ArgTypes, Values} {ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", {error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]))], [VmValue, VmType, Type0Str]))],
fun (E) -> E end)} fun (E) -> E end)}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], {error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)} fun(E) -> E end)}
end;
fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
[FateArgs, Type0Str]))],
fun (E) -> E end)}
end;
{error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"],
fun(E) -> E end)}
end
end end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -454,6 +365,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args; [Args] -> Args;
@@ -479,11 +391,7 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
(_) -> [] end, (_) -> [] end,
case lists:flatmap(GetType, Defs) of case lists:flatmap(GetType, Defs) of
[{Args, Ret}] -> {ok, Args, Ret}; [{Args, Ret}] -> {ok, Args, Ret};
[] -> [] -> {error, missing_function}
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function}
end
end; end;
get_decode_type(FunName, [_ | Contracts]) -> get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract %% The __decode should be in the final contract
@@ -492,10 +400,13 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% Translate an icode value (error if not value) to an Erlang term that can be %% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary(). %% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N; icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) -> icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>, <<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str; Str;
icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 ->
Value;
icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32->
list_to_tuple([W || {integer, W} <- Words]);
icode_to_term({list, T}, {list, Vs}) -> icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ]; [ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) -> icode_to_term({tuple, Ts}, {tuple, Vs}) ->
@@ -539,7 +450,7 @@ to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) -> extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end, ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)), TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep) ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions, || {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name), not is_tuple(Name),
@@ -609,3 +520,4 @@ pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos}); pos_error({Line, Pos});
pos_error({File, Line, Pos}) -> pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]). io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) -> fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]}, ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep), <<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}. {integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top %% Expects two return addresses below N elements on the stack. Picks the top
@@ -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.
+27 -42
View File
@@ -47,7 +47,7 @@ decl() ->
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -60,22 +60,13 @@ decl() ->
, ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5}) , ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5})
%% Function declarations %% Function declarations
, ?RULE(modifiers(), fun_or_entry(), id(), tok(':'), type(), add_modifiers(_1, _2, {fun_decl, get_ann(_2), _3, _5})) , ?RULE(modifiers(), keyword(function), id(), tok(':'), type(), add_modifiers(_1, {fun_decl, _2, _3, _5}))
, ?RULE(modifiers(), fun_or_entry(), fundef(), add_modifiers(_1, _2, set_pos(get_pos(get_ann(_2)), _3))) , ?RULE(modifiers(), keyword(function), fundef(), add_modifiers(_1, set_pos(get_pos(_2), _3)))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])). ])).
fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]).
modifiers() -> modifiers() ->
many(choice([token(stateful), token(private), token(public)])). many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Entry = {entrypoint, _}, Node) ->
add_modifiers(Mods ++ [Entry], Node);
add_modifiers(Mods, {function, _}, Node) ->
add_modifiers(Mods, Node).
add_modifiers([], Node) -> Node; add_modifiers([], Node) -> Node;
add_modifiers(Mods = [Tok | _], Node) -> add_modifiers(Mods = [Tok | _], Node) ->
@@ -108,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()).
@@ -140,8 +133,7 @@ type100() -> type200().
type200() -> type200() ->
?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)). ?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type300() -> type400().
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
type400() -> type400() ->
choice( choice(
@@ -156,18 +148,11 @@ type400() ->
typeAtom() -> typeAtom() ->
?LAZY_P(choice( ?LAZY_P(choice(
[ parens(type()) [ id(), token(con), token(qcon), token(qid), tvar()
, id(), token(con), token(qcon), token(qid), tvar() , ?RULE(keyword('('), comma_sep(type()), tok(')'), tuple_t(_1, _2))
])). ])).
fun_domain() -> ?LAZY_P(choice( fun_domain() -> ?RULE(?LAZY_P(type300()), fun_domain(_1)).
[ ?RULE(tok('('), tok(')'), [])
%% Note avoidance of ambiguity: `(int)` can be treated as:
%% - literally `int`
%% - list of arguments with just one element int. This approach is dropped.
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), [_2|_4])
, ?RULE(type300(), [_1])
])).
%% -- Statements ------------------------------------------------------------- %% -- Statements -------------------------------------------------------------
@@ -228,7 +213,7 @@ exprAtom() ->
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true} , {bool, keyword(true), true}
, {bool, keyword(false), false} , {bool, keyword(false), false}
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, {list, [], bracket_list(Expr)} , {list, [], bracket_list(Expr)}
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
@@ -271,20 +256,14 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []}; record([]) -> {map, [], []};
record(Fs) -> record(Fs) ->
case record_or_map(Fs) of case record_or_map(Fs) of
record -> record -> {record, get_ann(hd(Fs)), Fs};
Fld = fun({field, _, [_], _} = F) -> F;
({field, Ann, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
({field, Ann, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
map -> map ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val}; KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, FAnn, LV, Id, _}) -> ({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id)); bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, FAnn, LV, _}) -> ({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end, bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
{map, Ann, lists:map(KV, Fs)} {map, Ann, lists:map(KV, Fs)}
end. end.
@@ -462,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]);
@@ -478,9 +457,14 @@ 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}.
%% TODO: not nice
fun_domain({tuple_t, _, Args}) -> Args;
fun_domain(T) -> [T].
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({app, Ann, Con = {'::', _}, Es}) -> parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)}; {app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
@@ -494,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;
@@ -510,11 +495,11 @@ return_error({no_file, L, C}, Err) ->
return_error({F, L, C}, Err) -> return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])). fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). return_error(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return().
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E), ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
@@ -526,7 +511,7 @@ expand_includes(AST, Opts) ->
expand_includes([], Acc, _Opts) -> expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)}; {ok, lists:reverse(Acc)};
expand_includes([{include, _, S = {string, _, File}} | AST], Acc, Opts) -> expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
case read_file(File, Opts) of case read_file(File, Opts) of
{ok, Bin} -> {ok, Bin} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
+16 -32
View File
@@ -153,22 +153,15 @@ decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
equals(typedecl(Kind, T, Vars), typedef(Def)); equals(typedecl(Kind, T, Vars), typedef(Def));
decl({fun_decl, Ann, F, T}) -> decl({fun_decl, _, F, T}) ->
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of hsep(text("function"), typed(name(F), T));
true -> text("entrypoint");
false -> text("function")
end,
hsep(Fun, typed(name(F), T));
decl(D = {letfun, Attrs, _, _, _, _}) -> decl(D = {letfun, Attrs, _, _, _, _}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> Mod = fun({Mod, true}) when Mod == private; Mod == internal; Mod == public; Mod == stateful ->
text(atom_to_list(Mod)); text(atom_to_list(Mod));
(_) -> empty() end, (_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
true -> "entrypoint"; decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
false -> "function" decl(D = {letrec, _, _}) -> letdecl("let", D).
end,
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
@@ -191,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) ->
@@ -222,7 +217,7 @@ typedef({variant_t, Constructors}) ->
-spec constructor_t(aeso_syntax:constructor_t()) -> doc(). -spec constructor_t(aeso_syntax:constructor_t()) -> doc().
constructor_t({constr_t, _, C, []}) -> name(C); constructor_t({constr_t, _, C, []}) -> name(C);
constructor_t({constr_t, _, C, Args}) -> beside(name(C), args_type(Args)). constructor_t({constr_t, _, C, Args}) -> beside(name(C), tuple_type(Args)).
-spec field_t(aeso_syntax:field_t()) -> doc(). -spec field_t(aeso_syntax:field_t()) -> doc().
field_t({field_t, _, Name, Type}) -> field_t({field_t, _, Name, Type}) ->
@@ -234,14 +229,13 @@ type(Type, Options) ->
-spec type(aeso_syntax:type()) -> doc(). -spec type(aeso_syntax:type()) -> doc().
type({fun_t, _, Named, Args, Ret}) -> type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret)); follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret));
type({app_t, _, Type, []}) -> type({app_t, _, Type, []}) ->
type(Type); type(Type);
type({app_t, _, Type, Args}) -> type({app_t, _, Type, Args}) ->
beside(type(Type), args_type(Args)); beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) -> type({tuple_t, _, Args}) ->
tuple_type(Args); tuple_type(Args);
type({bytes_t, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) -> type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"])); text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) -> type({named_arg_t, _, Name, Type, _Default}) ->
@@ -256,19 +250,9 @@ type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T); type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T). type(T = {tvar, _, _}) -> name(T).
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
tuple(lists:map(fun type/1, Args)).
-spec tuple_type([aeso_syntax:type()]) -> doc(). -spec tuple_type([aeso_syntax:type()]) -> doc().
tuple_type([]) -> tuple_type(Args) ->
text("unit"); tuple(lists:map(fun type/1, Args)).
tuple_type(Factors) ->
beside(
[ text("(")
, par(punctuate(text(" *"), lists:map(fun type/1, Factors)), 0)
, text(")")
]).
-spec arg_expr(aeso_syntax:arg_expr()) -> doc(). -spec arg_expr(aeso_syntax:arg_expr()) -> doc().
arg_expr({named_arg, _, Name, E}) -> arg_expr({named_arg, _, Name, E}) ->
@@ -348,7 +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(_, {string, _, <<>>}) -> text("\"\""); 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
@@ -381,7 +365,6 @@ stmt_p({else, Else}) ->
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}. -spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('||') -> {200, 300, 200}; bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300}; bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500}; bin_prec('<') -> {400, 500, 500};
@@ -453,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", "entrypoint", "private", "indexed", "namespace"], "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
+11 -8
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()}.
@@ -59,7 +60,7 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()} -type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]} | {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]} | {tuple_t, ann(), [type()]}
| {bytes_t, ann(), integer() | any} | {bytes_t, ann(), integer()}
| id() | qid() | id() | qid()
| con() | qcon() %% contracts | con() | qcon() %% contracts
| tvar(). | tvar().
@@ -69,11 +70,12 @@
-type constant() -type constant()
:: {int, ann(), integer()} :: {int, ann(), integer()}
| {bool, ann(), true | false} | {bool, ann(), true | false}
| {bytes, ann(), binary()} | {hash, ann(), binary()}
| {account_pubkey, ann(), binary()} | {account_pubkey, binary()}
| {contract_pubkey, ann(), binary()} | {contract_pubkey, binary()}
| {oracle_pubkey, ann(), binary()} | {oracle_pubkey, binary()}
| {oracle_query_id, ann(), binary()} | {oracle_query_id, binary()}
| {unit, ann()}
| {string, ann(), binary()} | {string, ann(), binary()}
| {char, ann(), integer()}. | {char, ann(), integer()}.
@@ -148,3 +150,4 @@ get_ann(Key, Node, Default) ->
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X); qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]}; qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}. qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+24 -24
View File
@@ -6,7 +6,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_syntax_utils). -module(aeso_syntax_utils).
-export([used_ids/1, used_types/2, used/1]). -export([used_ids/1, used_types/1, used/1]).
-record(alg, {zero, plus, scoped}). -record(alg, {zero, plus, scoped}).
@@ -39,6 +39,11 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end, BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end, BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X), Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of Rec = case X of
%% lists (bound things in head scope over tail) %% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As)); [A | As] -> Scoped(Same(A), Same(As));
@@ -50,6 +55,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_decl, _, _, T} -> Type(T); {fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]); {letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]); {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef() %% typedef()
{alias_t, T} -> Type(T); {alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs); {record_t, Fs} -> Type(Fs);
@@ -98,35 +104,29 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies %% Name dependencies
used_ids(E) -> used_ids(E) ->
[ X || {{term, [X]}, _} <- used(E) ]. [ X || {term, [X]} <- used(E) ].
used_types([Top] = _CurrentNS, T) -> used_types(T) ->
F = fun({{type, [X]}, _}) -> [X]; [ X || {type, [X]} <- used(T) ].
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]} -type entity() :: {term, [string()]}
| {type, [string()]} | {type, [string()]}
| {namespace, [string()]}. | {namespace, [string()]}.
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}). -spec entity_alg() -> alg([entity()]).
entity_alg() -> entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end, IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end, Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end,
Scoped = fun(Xs, Ys) -> Scoped = fun(Xs, Ys) ->
Bound = [E || E <- maps:keys(Ys), IsBound(E)], {Bound, Others} = lists:partition(IsBound, Ys),
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ], Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
maps:merge(Remove(Bound1, Xs), Others) lists:umerge(Xs -- Bound1, Others)
end, end,
#alg{ zero = #{} #alg{ zero = []
, plus = fun maps:merge/2 , plus = fun lists:umerge/2
, scoped = Scoped }. , scoped = Scoped }.
-spec used(_) -> [{entity(), aeso_syntax:ann()}]. -spec used(_) -> [entity()].
used(D) -> used(D) ->
Kind = fun(expr) -> term; Kind = fun(expr) -> term;
(bind_expr) -> bound_term; (bind_expr) -> bound_term;
@@ -134,14 +134,14 @@ used(D) ->
(bind_type) -> bound_type (bind_type) -> bound_type
end, end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end, NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end, NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs = Xs =
maps:to_list(fold(entity_alg(), fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann}; fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann}; (K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> #{} (_, _) -> []
end, decl, D)), end, decl, D),
lists:filter(NotBound, Xs). lists:filter(NotBound, Xs).
-113
View File
@@ -1,113 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N});
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin});
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N};
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
{list, [], [from_fate(Type, X) || X <- List]};
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, [0, 1], 0, {}} -> {con, [], "None"};
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
end;
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
{tuple, [], []};
from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_fate(KeyType, Key),
from_fate(ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
when length(Cons) > Tag ->
ConType = lists:nth(Tag + 1, Cons),
Arity = lists:nth(Tag + 1, Ar),
case tuple_to_list(Args) of
ArgList when length(ArgList) == Arity ->
from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia)
end;
from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia).
+2 -3
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "3.2.0"}, {vsn, "2.1.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
@@ -8,8 +8,7 @@
jsx, jsx,
syntax_tools, syntax_tools,
getopt, getopt,
aebytecode, aebytecode
eblake2
]}, ]},
{env,[]}, {env,[]},
{modules, []}, {modules, []},
+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]).
+18 -19
View File
@@ -1,7 +1,7 @@
-module(aeso_abi_tests). -module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-compile([export_all, nowarn_export_all]). -compile(export_all).
-define(SANDBOX(Code), sandbox(fun() -> Code end)). -define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123). -define(DUMMY_HASH_WORD, 16#123).
@@ -62,11 +62,10 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
ok = Check("string * list(int) * option(bool)", ok = Check("(string, list(int), option(bool))",
"(\"Hello\", [1, 2, 3], Some(true))"), "(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"), ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"), ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
@@ -76,10 +75,10 @@ encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract MakeCall =\n"
, " type arg_type = ", SophiaType, "\n" , " type arg_type = ", SophiaType, "\n"
, " type an_alias('a) = string * 'a\n" , " type an_alias('a) = (string, 'a)\n"
, " record r = {x : an_alias(int), y : variant}\n" , " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n" , " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ], , " function foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
{ok, _, {[Type], _}, [Arg]} -> {ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]), io:format("Type ~p~n", [Type]),
@@ -120,14 +119,16 @@ calldata_init_test() ->
calldata_indent_test() -> calldata_indent_test() ->
Test = fun(Extra) -> Test = fun(Extra) ->
Code = parameterized_contract(Extra, "foo", ["int"]), encode_decode_calldata_(
encode_decode_calldata_(Code, "foo", ["42"], word) parameterized_contract(Extra, "foo", ["int"]),
"foo", ["42"], word)
end, end,
Test(" stateful entrypoint bla() = ()"), Test(" stateful function bla() = ()"),
Test(" type x = int"), Test(" type x = int"),
Test(" stateful entrypoint bla(x : int) =\n" Test(" private function bla : int => int"),
Test(" public stateful function bla(x : int) =\n"
" x + 1"), " x + 1"),
Test(" stateful entrypoint bla(x : int) : int =\n" Test(" stateful private function bla(x : int) : int =\n"
" x + 1"), " x + 1"),
ok. ok.
@@ -137,18 +138,18 @@ parameterized_contract(FunName, Types) ->
parameterized_contract(ExtraCode, FunName, Types) -> parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten( lists:flatten(
["contract Remote =\n" ["contract Remote =\n"
" entrypoint bla : () => unit\n\n" " function bla : () => ()\n\n"
"contract Dummy =\n", "contract Dummy =\n",
ExtraCode, "\n", ExtraCode, "\n",
" type an_alias('a) = string * 'a\n" " type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n" " record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n" " datatype variant = Red | Blue(map(string, int))\n"
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]). " function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() -> oracle_test() ->
Contract = Contract =
"contract OracleTest =\n" "contract OracleTest =\n"
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" " function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n", " Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
@@ -159,7 +160,7 @@ oracle_test() ->
permissive_literals_fail_test() -> permissive_literals_fail_test() ->
Contract = Contract =
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful entrypoint 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"], []),
@@ -173,10 +174,8 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetType). encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) -> encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args), {ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]), ?assertEqual(RetVMType1, RetVMType),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata), {ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
case FunName of case FunName of
"init" -> "init" ->
+50 -96
View File
@@ -2,121 +2,75 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N), do_test() ->
fun() -> test_contract(N) end} test_contract(1),
|| N <- [1, 2, 3]]. test_contract(2),
test_contract(3).
test_contract(N) -> test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N), {Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract), {ok,JSON} = aeso_aci:encode(Contract),
?assertEqual([MapACI], JSON), ?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual(DecACI, aeso_aci:decode(JSON)).
test_cases(1) -> test_cases(1) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" entrypoint a(i : int) = i+1\n">>, " function a(i : int) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, #{<<"name">> => <<"C">>,
type_defs => [], <<"type_defs">> => [],
functions => <<"functions">> =>
[#{name => <<"a">>, [#{<<"name">> => <<"a">>,
arguments => <<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"int">>}], <<"type">> => <<"int">>}],
returns => <<"int">>, <<"returns">> => <<"int">>,
stateful => false}]}}, <<"stateful">> => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" entrypoint a : (int) => int\n">>, " function a : (int) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(2) -> test_cases(2) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type allan = int\n" " type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>, " function a(i : allan) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, #{<<"name">> => <<"C">>,
type_defs => <<"type_defs">> =>
[#{name => <<"allan">>, [#{<<"name">> => <<"allan">>,
typedef => <<"int">>, <<"typedef">> => <<"int">>,
vars => []}], <<"vars">> => []}],
functions => <<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"C.allan">>}], <<"type">> => <<"int">>}],
name => <<"a">>, <<"name">> => <<"a">>,
returns => <<"int">>, <<"returns">> => <<"int">>,
stateful => false}]}}, <<"stateful">> => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type allan = int\n" " function a : (int) => int\n">>,
" entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(3) -> test_cases(3) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a(i : bert(string)) = 1\n">>, " function a(i : bert(string)) = 1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{functions => #{<<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"type">> =>
#{<<"C.bert">> => [<<"string">>]}}], #{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>, <<"name">> => <<"a">>,<<"returns">> => <<"int">>,
stateful => false}], <<"stateful">> => false}],
name => <<"C">>, <<"name">> => <<"C">>,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, <<"type_defs">> =>
state => <<"unit">>, [#{<<"name">> => <<"bert">>,
type_defs => <<"typedef">> =>
[#{name => <<"bert">>, #{<<"variant">> =>
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]}, [#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}}, <<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>, " function a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}. {Contract,MapACI,DecACI}.
%% Rounttrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
_:R ->
io:format("Error: ~p\n", [R]),
error(R)
end;
{error, E} ->
io:format("Error: ~p\n", [E]),
error({parse_error, E})
end.
-128
View File
@@ -1,128 +0,0 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc Test Sophia language compiler.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_calldata_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)),
Exprs.
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() ->
[
{"identity", "init", []},
{"maps", "init", []},
{"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
"414243444546474849505152535455565758596061626364656667"]},
{"funargs", "trettiotva", ["#0102030405060708091011121314151617181920212223242526272829303132"]},
{"funargs", "find_oracle", ["ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5"]},
{"funargs", "find_query", ["oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY"]},
{"funargs", "traffic_light", ["Green"]},
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
{"bytes_equality", "init", []},
{"address_chain", "init", []},
{"counter", "init",
["-3334353637383940202122232425262728293031323334353637"]},
{"dutch_auction", "init",
["ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt", "200000", "1000"]},
{"maps", "fromlist_i",
["[(1, {x = 1, y = 2}), (2, {x = 3, y = 4}), (3, {x = 4, y = 4})]"]},
{"maps", "get_i", ["1", "{}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
{"strings", "str_concat", ["\"test\"","\"me\""]},
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
{"__call" "init", []},
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
{"stub", "foo", ["42"]},
{"stub", "foo", ["-42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
[].
+54 -106
View File
@@ -16,25 +16,20 @@
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", [ {"Testing the " ++ ContractName ++ " contract",
fun() -> fun() ->
case compile(Backend, ContractName) of case compile(ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
?assertMatch(Code when is_binary(Code), ByteCode);
#{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code});
ErrBin -> ErrBin ->
io:format("\n~s", [ErrBin]), io:format("\n~s", [ErrBin]),
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts() ] ++
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of case compile(ContractName) of
<<"Type errors\n", ErrorString/binary>> -> <<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString); check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> -> <<"Parse errors\n", ErrorString/binary>> ->
@@ -49,14 +44,14 @@ simple_compile_test_() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), {ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin} {File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]), end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), #{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"), #{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2) ?assertMatch(true, Code1 == Code2)
end} ] ++ end} ] ++
[ {"Testing deadcode elimination", [ {"Testing deadcode elimination",
fun() -> fun() ->
#{ byte_code := NoDeadCode } = compile(aevm, "nodeadcode"), #{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile(aevm, "deadcode"), #{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode), SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode), SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}), ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
@@ -72,12 +67,12 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra) {Missing, Extra} -> ?assertEqual(Missing, Extra)
end. end.
compile(Backend, Name) -> compile(Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]). compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name, Options) -> compile(Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} -> ErrorString {error, ErrorString} -> ErrorString
end. end.
@@ -91,7 +86,6 @@ compilable_contracts() ->
"dutch_auction", "dutch_auction",
"environment", "environment",
"factorial", "factorial",
"functions",
"fundme", "fundme",
"identity", "identity",
"maps", "maps",
@@ -112,18 +106,9 @@ compilable_contracts() ->
"include", "include",
"basic_auth", "basic_auth",
"bitcoin_auth", "bitcoin_auth",
"address_literals", "address_literals"
"bytes_equality",
"address_chain",
"namespace_bug",
"bytes_to_x",
"aens",
"tuple_match"
]. ].
not_yet_compilable(fate) -> [];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
failing_contracts() -> failing_contracts() ->
@@ -131,9 +116,6 @@ failing_contracts() ->
[<<"Duplicate definitions of abort at\n" [<<"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
@@ -145,12 +127,12 @@ failing_contracts() ->
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of state at\n" <<"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
[<<"Unbound variable zz at line 17, column 23">>, [<<"Unbound variable zz at line 17, column 21">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
@@ -161,18 +143,18 @@ failing_contracts() ->
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -184,7 +166,7 @@ failing_contracts() ->
" int">>, " int">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -194,51 +176,50 @@ failing_contracts() ->
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 42)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 35)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"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 14).">>, <<"No record type with fields y, z (at line 14, column 22)">>,
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, <<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"No record type with fields y, z (at line 14, column 24)">>, <<"Record type r2 does not have field y (at line 15, column 22)">>]}
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>,
<<"Let binding at line 47, column 5 must be followed by an expression">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>,
<<"Let binding at line 54, column 5 must be followed by an expression">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]}
, {"init_type_error", , {"init_type_error",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and unit\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash", , {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} [<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events", , {"bad_events",
[<<"The indexed type string (at line 9, column 25) is not a word type">>, [<<"The payload type int (at line 10, column 30) should be string">>,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} <<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
, {"bad_events2", , {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, [<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash", , {"type_clash",
[<<"Cannot unify int\n" [<<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the record projection at line 12, column 42\n" "when checking the record projection at line 12, column 40\n"
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>]}
@@ -261,46 +242,46 @@ failing_contracts() ->
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<"Cannot unify oracle_query('1, '2)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('1, '2)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<"Cannot unify oracle_query('3, '4)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('3, '4)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<"Cannot unify oracle_query('5, '6)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('5, '6)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<"Cannot unify oracle('7, '8)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('7, '8)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<"Cannot unify oracle('9, '10)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('9, '10)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<"Cannot unify oracle('11, '12)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('11, '12)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<"Cannot unify address\n"
@@ -321,37 +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 35)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 35)\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 47)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\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 38), which calls\n"
" - put (at line 7, column 39)">>,
<<"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 29)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>]}
, {"field_parse_error",
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
"Cannot use nested fields or keys in record construction: p.x\n">>]}
, {"modifier_checks",
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]}
]. ].
+1 -1
View File
@@ -62,7 +62,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts %% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]), [ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end} fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
}. }.
parse_contract(Name) -> parse_contract(Name) ->
+1 -1
View File
@@ -41,7 +41,7 @@ all_tokens() ->
%% Operators %% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords %% Keywords
lists:map(Lit, [contract, type, 'let', switch]) ++ lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
%% Comment token (not an actual token), just for tests %% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"}, [{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++ {comment, 0, "/* bla /* bla bla */*/"}] ++
-5
View File
@@ -1,5 +0,0 @@
contract Identity =
function main (x:int) = x
function __call() = 12
+1 -1
View File
@@ -8,7 +8,7 @@ contract AbortTest =
{ value = v } { value = v }
// Aborting // Aborting
public function do_abort(v : int, s : string) : unit = public function do_abort(v : int, s : string) : () =
put_value(v) put_value(v)
revert_abort(s) revert_abort(s)
+3 -3
View File
@@ -1,9 +1,9 @@
contract Interface = contract Interface =
function do_abort : (int, string) => unit function do_abort : (int, string) => ()
function get_value : () => int function get_value : () => int
function put_value : (int) => unit function put_value : (int) => ()
function get_values : () => list(int) function get_values : () => list(int)
function put_values : (int) => unit function put_values : (int) => ()
contract AbortTestInt = contract AbortTestInt =
-33
View File
@@ -1,33 +0,0 @@
contract Remote =
entrypoint main : (int) => unit
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
entrypoint is_o(a : address) =
Address.is_oracle(a)
entrypoint is_c(a : address) =
Address.is_contract(a)
// entrypoint get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// entrypoint get_c(a : address) : option(Remote) =
// Address.get_contract(a)
entrypoint check_o(o : o_type) =
Oracle.check(o)
entrypoint check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// entrypoint h_to_i(h : hash) : int =
// Hash.to_int(h)
// entrypoint a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
entrypoint c_creator() : address =
Contract.creator
+5 -5
View File
@@ -1,14 +1,14 @@
contract Remote = contract Remote =
entrypoint foo : () => unit function foo : () => ()
contract AddressLiterals = contract AddressLiterals =
entrypoint addr() : address = function addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle() : oracle(int, bool) = function oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query() : oracle_query(int, bool) = function query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote = function contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+27 -28
View File
@@ -3,54 +3,53 @@ contract AENSTest =
// Name resolution // Name resolution
stateful entrypoint resolve_word(name : string, key : string) : option(address) = function resolve_word(name : string, key : string) : option(address) =
AENS.resolve(name, key) AENS.resolve(name, key)
stateful entrypoint resolve_string(name : string, key : string) : option(string) = function resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key) AENS.resolve(name, key)
// Transactions // Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash chash : hash) : () = // Commitment hash
AENS.preclaim(addr, chash) AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash, // Commitment hash chash : hash, // Commitment hash
sign : signature) : unit = // Signed by addr (if not Contract.address) sign : signature) : () = // Signed by addr (if not Contract.address)
AENS.preclaim(addr, chash, signature = sign) AENS.preclaim(addr, chash, signature = sign)
stateful entrypoint claim(addr : address, function claim(addr : address,
name : string, name : string,
salt : int, salt : int) : () =
name_fee : int) : unit = AENS.claim(addr, name, salt)
AENS.claim(addr, name, salt, name_fee)
stateful entrypoint signedClaim(addr : address, function signedClaim(addr : address,
name : string, name : string,
salt : int, salt : int,
name_fee : int, sign : signature) : () =
sign : signature) : unit = AENS.claim(addr, name, salt, signature = sign)
AENS.claim(addr, name, salt, name_fee, signature = sign)
// TODO: update() -- how to handle pointers? // TODO: update() -- how to handle pointers?
stateful entrypoint transfer(owner : address, function transfer(owner : address,
new_owner : address, new_owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.transfer(owner, new_owner, name) AENS.transfer(owner, new_owner, name_hash)
stateful entrypoint signedTransfer(owner : address, function signedTransfer(owner : address,
new_owner : address, new_owner : address,
name : string, name_hash : hash,
sign : signature) : unit = sign : signature) : () =
AENS.transfer(owner, new_owner, name, signature = sign) AENS.transfer(owner, new_owner, name_hash, signature = sign)
stateful entrypoint revoke(owner : address, function revoke(owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.revoke(owner, name) AENS.revoke(owner, name_hash)
function signedRevoke(owner : address,
name_hash : hash,
sign : signature) : () =
AENS.revoke(owner, name_hash, signature = sign)
stateful entrypoint signedRevoke(owner : address,
name : string,
sign : signature) : unit =
AENS.revoke(owner, name, signature = sign)
+6 -4
View File
@@ -104,10 +104,10 @@ contract AEProof =
proofsByOwner : map(address, array(uint)) } proofsByOwner : map(address, array(uint)) }
function notarize(document:string, comment:string, ipfsHash:hash) = function notarize(document:string, comment:string, ipfsHash:hash) =
let _ = require(aetoken.balanceOf(caller()) > 0, "false") let _ = require(aetoken.balanceOf(caller()) > 0)
let proofHash: uint = calculateHash(document) let proofHash: uint = calculateHash(document)
let proof : proof = Map.get_(proofHash, state().proofs) let proof : proof = Map.get_(proofHash, state().proofs)
let _ = require(proof.owner == #0, "false") let _ = require(proof.owner == #0)
let proof' : proof = proof { owner = caller() let proof' : proof = proof { owner = caller()
, timestamp = block().timestamp , timestamp = block().timestamp
, proofBlock = block().height , proofBlock = block().height
@@ -124,12 +124,12 @@ contract AEProof =
function getProof(document) : proof = function getProof(document) : proof =
let calcHash = calculateHash(document) let calcHash = calculateHash(document)
let proof = Map.get_(calcHash, state().proofs) let proof = Map.get_(calcHash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
function getProofByHash(hash: uint) : proof = function getProofByHash(hash: uint) : proof =
let proof = Map.get_(hash, state().proofs) let proof = Map.get_(hash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
@@ -141,3 +141,5 @@ contract AEProof =
function getProofsByOwner(owner: address): array(uint) = function getProofsByOwner(owner: address): array(uint) =
Map.get(owner, state()) Map.get(owner, state())
function require(x : bool) : unit = if(x) () else abort("false")
+6 -1
View File
@@ -24,7 +24,7 @@ contract AllSyntax =
if(valWithType(Map.empty) == None) if(valWithType(Map.empty) == None)
print(42 mod 10 * 5 / 3) print(42 mod 10 * 5 / 3)
function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ []) function funWithType(x : int, y) : (int, list(int)) = (x, 0 :: [y] ++ [])
function funNoType() = function funNoType() =
let foo = (x, y : bool) => let foo = (x, y : bool) =>
if (! (y && x =< 0x0b || true)) [x] if (! (y && x =< 0x0b || true)) [x]
@@ -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 -13
View File
@@ -1,33 +1,33 @@
contract Remote = contract Remote =
entrypoint foo : () => unit function foo : () => ()
contract AddressLiterals = contract AddressLiterals =
entrypoint addr1() : bytes(32) = function addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr2() : Remote = function addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr3() : oracle(int, bool) = function addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle1() : oracle_query(int, bool) = function oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle2() : bytes(32) = function oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle3() : Remote = function oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query1() : oracle(int, bool) = function query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query2() : bytes(32) = function query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query3() : Remote = function query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr1() : address = function contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr2() : oracle(int, bool) = function contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) = function contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+9 -7
View File
@@ -6,18 +6,20 @@ contract Events =
datatype event = datatype event =
Event1(indexed alias_int, indexed int, string) Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string) | BadEvent1(indexed string, string)
| BadEvent2(indexed alias_string) | BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
entrypoint f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) = function f2(s : string) =
Chain.event(Event2(s, Call.caller)) Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) = function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7))) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i) function i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a) function a2s(a : address) = Address.to_str(a)
+6 -5
View File
@@ -8,16 +8,17 @@ contract Events =
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(string, string) | BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address) | BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
entrypoint f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) = function f2(s : string) =
Chain.event(Event2(s, Call.caller)) Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) = function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7))) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i) function i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a) function a2s(a : address) = Address.to_str(a)
+1 -1
View File
@@ -3,4 +3,4 @@ contract Bad =
namespace Foo = namespace Foo =
function foo() = 42 function foo() = 42
entrypoint foo() = 43 function foo() = 43
-13
View File
@@ -1,13 +0,0 @@
contract BadInit =
type state = int
entrypoint new_state(n) = state + n
stateful entrypoint roundabout(n) = put(n)
stateful entrypoint set_state(n) = roundabout(n)
stateful entrypoint init() =
set_state(4)
new_state(0)
state + state
+5 -3
View File
@@ -2,9 +2,9 @@
contract BasicAuth = contract BasicAuth =
record state = { nonce : int, owner : address } record state = { nonce : int, owner : address }
entrypoint init() = { nonce = 1, owner = Call.caller } function init() = { nonce = 1, owner = Call.caller }
stateful entrypoint 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 })
@@ -12,6 +12,8 @@ contract BasicAuth =
None => abort("Not in Auth context") None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s) Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) = function to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+5 -3
View File
@@ -1,9 +1,9 @@
contract BitcoinAuth = contract BitcoinAuth =
record state = { nonce : int, owner : bytes(64) } record state = { nonce : int, owner : bytes(64) }
entrypoint init(owner' : bytes(64)) = { nonce = 1, owner = owner' } function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
stateful entrypoint 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 })
@@ -11,6 +11,8 @@ contract BitcoinAuth =
None => abort("Not in Auth context") None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s) Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) : hash = function to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+2 -2
View File
@@ -5,8 +5,8 @@ contract BuiltinBug =
record state = {proofs : map(address, list(string))} record state = {proofs : map(address, list(string))}
entrypoint init() = {proofs = {}} public function init() = {proofs = {}}
stateful entrypoint createProof(hash : string) = public stateful function createProof(hash : string) =
put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } ) put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } )
+7 -3
View File
@@ -1,8 +1,12 @@
contract TestContract = contract TestContract =
record state = {_allowed : map(address, map(address, int))} record state = {
_allowed : map(address, map(address, int))}
entrypoint init() = {_allowed = {}} public stateful function init() = {
_allowed = {}}
public stateful function approve(spender: address, value: int) : bool =
stateful entrypoint approve(spender: address, value: int) : bool =
put(state{_allowed[Call.caller][spender] = value}) put(state{_allowed[Call.caller][spender] = value})
true true
-18
View File
@@ -1,18 +0,0 @@
contract BytesEquality =
entrypoint eq16(a : bytes(16), b) = a == b
entrypoint ne16(a : bytes(16), b) = a != b
entrypoint eq32(a : bytes(32), b) = a == b
entrypoint ne32(a : bytes(32), b) = a != b
entrypoint eq47(a : bytes(47), b) = a == b
entrypoint ne47(a : bytes(47), b) = a != b
entrypoint eq64(a : bytes(64), b) = a == b
entrypoint ne64(a : bytes(64), b) = a != b
entrypoint eq65(a : bytes(65), b) = a == b
entrypoint ne65(a : bytes(65), b) = a != b
-8
View File
@@ -1,8 +0,0 @@
contract BytesToX =
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
entrypoint to_str(b : bytes(12)) : string =
String.concat(Bytes.to_str(b), Bytes.to_str(#ffff))
entrypoint to_str_big(b : bytes(65)) : string =
Bytes.to_str(b)
+25 -25
View File
@@ -1,78 +1,78 @@
contract Remote = contract Remote =
entrypoint up_to : (int) => list(int) function up_to : (int) => list(int)
entrypoint sum : (list(int)) => int function sum : (list(int)) => int
entrypoint some_string : () => string function some_string : () => string
entrypoint pair : (int, string) => int * string function pair : (int, string) => (int, string)
entrypoint squares : (int) => list(int * int) function squares : (int) => list((int, int))
entrypoint filter_some : (list(option(int))) => list(int) function filter_some : (list(option(int))) => list(int)
entrypoint all_some : (list(option(int))) => option(list(int)) function all_some : (list(option(int))) => option(list(int))
contract ComplexTypes = contract ComplexTypes =
record state = { worker : Remote } record state = { worker : Remote }
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
entrypoint sum_acc(xs, n) = function sum_acc(xs, n) =
switch(xs) switch(xs)
[] => n [] => n
x :: xs => sum_acc(xs, x + n) x :: xs => sum_acc(xs, x + n)
// Sum a list of integers // Sum a list of integers
entrypoint sum(xs : list(int)) = function sum(xs : list(int)) =
sum_acc(xs, 0) sum_acc(xs, 0)
entrypoint up_to_acc(n, xs) = function up_to_acc(n, xs) =
switch(n) switch(n)
0 => xs 0 => xs
_ => up_to_acc(n - 1, n :: xs) _ => up_to_acc(n - 1, n :: xs)
entrypoint up_to(n) = up_to_acc(n, []) function up_to(n) = up_to_acc(n, [])
record answer('a) = {label : string, result : 'a} record answer('a) = {label : string, result : 'a}
entrypoint remote_triangle(worker, n) : answer(int) = function remote_triangle(worker, n) : answer(int) =
let xs = worker.up_to(gas = 100000, n) let xs = worker.up_to(gas = 100000, n)
let t = worker.sum(xs) let t = worker.sum(xs)
{ label = "answer:", result = t } { label = "answer:", result = t }
entrypoint remote_list(n) : list(int) = function remote_list(n) : list(int) =
state.worker.up_to(n) state.worker.up_to(n)
entrypoint some_string() = "string" function some_string() = "string"
entrypoint remote_string() : string = function remote_string() : string =
state.worker.some_string() state.worker.some_string()
entrypoint pair(x : int, y : string) = (x, y) function pair(x : int, y : string) = (x, y)
entrypoint remote_pair(n : int, s : string) : int * string = function remote_pair(n : int, s : string) : (int, string) =
state.worker.pair(gas = 10000, n, s) state.worker.pair(gas = 10000, n, s)
entrypoint map(f, xs) = function map(f, xs) =
switch(xs) switch(xs)
[] => [] [] => []
x :: xs => f(x) :: map(f, xs) x :: xs => f(x) :: map(f, xs)
entrypoint squares(n) = function squares(n) =
map((i) => (i, i * i), up_to(n)) map((i) => (i, i * i), up_to(n))
entrypoint remote_squares(n) : list(int * int) = function remote_squares(n) : list((int, int)) =
state.worker.squares(n) state.worker.squares(n)
// option types // option types
entrypoint filter_some(xs : list(option(int))) : list(int) = function filter_some(xs : list(option(int))) : list(int) =
switch(xs) switch(xs)
[] => [] [] => []
None :: ys => filter_some(ys) None :: ys => filter_some(ys)
Some(x) :: ys => x :: filter_some(ys) Some(x) :: ys => x :: filter_some(ys)
entrypoint remote_filter_some(xs : list(option(int))) : list(int) = function remote_filter_some(xs : list(option(int))) : list(int) =
state.worker.filter_some(xs) state.worker.filter_some(xs)
entrypoint all_some(xs : list(option(int))) : option(list(int)) = function all_some(xs : list(option(int))) : option(list(int)) =
switch(xs) switch(xs)
[] => Some([]) [] => Some([])
None :: ys => None None :: ys => None
@@ -81,6 +81,6 @@ contract ComplexTypes =
Some(xs) => Some(x :: xs) Some(xs) => Some(x :: xs)
None => None None => None
entrypoint remote_all_some(xs : list(option(int))) : option(list(int)) = function remote_all_some(xs : list(option(int))) : option(list(int)) =
state.worker.all_some(gas = 10000, xs) state.worker.all_some(gas = 10000, xs)
+3 -3
View File
@@ -3,7 +3,7 @@ contract Counter =
record state = { value : int } record state = { value : int }
entrypoint init(val) = { value = val } function init(val) = { value = val }
entrypoint get() = state.value function get() = state.value
stateful entrypoint tick() = put(state{ value = state.value + 1 }) function tick() = put(state{ value = state.value + 1 })
+2 -2
View File
@@ -13,9 +13,9 @@ namespace List =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = function inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
+6 -3
View File
@@ -10,13 +10,16 @@ contract DutchAuction =
sold : bool } sold : bool }
// Add to work around current lack of predefined functions // Add to work around current lack of predefined functions
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
private function require(b : bool, err : string) =
if(!b) abort(err)
// TTL set by user on posting contract, typically (start - end ) div dec // TTL set by user on posting contract, typically (start - end ) div dec
entrypoint init(beneficiary, start, decrease) : state = public function init(beneficiary, start, decrease) : state =
require(start > 0 && decrease > 0, "bad args") require(start > 0 && decrease > 0, "bad args")
{ start_amount = start, { start_amount = start,
start_height = Chain.block_height, start_height = Chain.block_height,
@@ -27,7 +30,7 @@ contract DutchAuction =
// -- API // -- API
// We are the buyer... interesting case to buy for someone else and keep 10% // We are the buyer... interesting case to buy for someone else and keep 10%
stateful entrypoint bid() = public stateful function bid() =
require( !(state.sold), "sold") require( !(state.sold), "sold")
let cost = let cost =
state.start_amount - (Chain.block_height - state.start_height) * state.dec state.start_amount - (Chain.block_height - state.start_height) * state.dec
+23 -23
View File
@@ -1,69 +1,69 @@
// Testing primitives for accessing the block chain environment // Testing primitives for accessing the block chain environment
contract Interface = contract Interface =
entrypoint contract_address : () => address function contract_address : () => address
entrypoint call_origin : () => address function call_origin : () => address
entrypoint call_caller : () => address function call_caller : () => address
entrypoint call_value : () => int function call_value : () => int
contract Environment = contract Environment =
record state = {remote : Interface} record state = {remote : Interface}
entrypoint init(remote) = {remote = remote} function init(remote) = {remote = remote}
stateful entrypoint set_remote(remote) = put({remote = remote}) function set_remote(remote) = put({remote = remote})
// -- Information about the this contract --- // -- Information about the this contract ---
// Address // Address
entrypoint contract_address() : address = Contract.address function contract_address() : address = Contract.address
entrypoint nested_address(who) : address = function nested_address(who) : address =
who.contract_address(gas = 1000) who.contract_address(gas = 1000)
// Balance // Balance
entrypoint contract_balance() : int = Contract.balance function contract_balance() : int = Contract.balance
// -- Information about the current call --- // -- Information about the current call ---
// Origin // Origin
entrypoint call_origin() : address = Call.origin function call_origin() : address = Call.origin
entrypoint nested_origin() : address = function nested_origin() : address =
state.remote.call_origin() state.remote.call_origin()
// Caller // Caller
entrypoint call_caller() : address = Call.caller function call_caller() : address = Call.caller
entrypoint nested_caller() : address = function nested_caller() : address =
state.remote.call_caller() state.remote.call_caller()
// Value // Value
entrypoint call_value() : int = Call.value function call_value() : int = Call.value
stateful entrypoint 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
entrypoint call_gas_price() : int = Call.gas_price function call_gas_price() : int = Call.gas_price
// -- Information about the chain --- // -- Information about the chain ---
// Account balances // Account balances
entrypoint get_balance(acct : address) : int = Chain.balance(acct) function get_balance(acct : address) : int = Chain.balance(acct)
// Block hash // Block hash
entrypoint block_hash(height : int) : option(hash) = Chain.block_hash(height) function block_hash(height : int) : int = Chain.block_hash(height)
// Coinbase // Coinbase
entrypoint coinbase() : address = Chain.coinbase function coinbase() : address = Chain.coinbase
// Block timestamp // Block timestamp
entrypoint timestamp() : int = Chain.timestamp function timestamp() : int = Chain.timestamp
// Block height // Block height
entrypoint block_height() : int = Chain.block_height function block_height() : int = Chain.block_height
// Difficulty // Difficulty
entrypoint difficulty() : int = Chain.difficulty function difficulty() : int = Chain.difficulty
// Gas limit // Gas limit
entrypoint gas_limit() : int = Chain.gas_limit function gas_limit() : int = Chain.gas_limit
+3
View File
@@ -77,6 +77,9 @@ contract ERC20Token =
put( state{approval_log = e :: state.approval_log }) put( state{approval_log = e :: state.approval_log })
e e
private function require(b : bool, err : string) =
if(!b) abort(err)
private function sub(_a : int, _b : int) : int = private function sub(_a : int, _b : int) : int =
require(_b =< _a, "Error") require(_b =< _a, "Error")
_a - _b _a - _b
+16 -34
View File
@@ -1,40 +1,22 @@
contract Remote =
entrypoint dummy : () => unit
contract Events = contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
// Valid index types datatype event =
type ix1 = int Event1(indexed alias_int, indexed int, string)
type ix2 = bool | Event2(alias_string, indexed alias_address)
type ix3 = bits // | BadEvent1(indexed string, string)
type ix4 = bytes(12) // | BadEvent2(indexed int, int)
type ix5 = hash // bytes(32)
type ix6 = address
type ix7 = Remote
type ix8 = oracle(int, int)
type ix9 = oracle_query(int, int)
// Valid payload types function f1(x : int, y : string) =
type data1 = string Chain.event(Event1(x, x+1, y))
type data2 = signature // bytes(64)
type data3 = bytes(65)
datatype event function f2(s : string) =
= Nodata0 Chain.event(Event2(s, Call.caller))
| Nodata1(ix1)
| Nodata2(ix2, ix3)
| Nodata3(ix4, ix5, ix6)
| Data0(data1)
| Data1(data2, ix7)
| Data2(ix8, data3, ix9)
| Data3(ix1, ix2, ix5, data1)
entrypoint nodata0() = Chain.event(Nodata0) function f3(x : int) =
entrypoint nodata1(ix1) = Chain.event(Nodata1(ix1)) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint nodata2(ix2, ix3) = Chain.event(Nodata2(ix2, ix3))
entrypoint nodata3(ix4, ix5, ix6) = Chain.event(Nodata3(ix4, ix5, ix6))
entrypoint data0(data1) = Chain.event(Data0(data1))
entrypoint data1(data2, ix7) = Chain.event(Data1(data2, ix7))
entrypoint data2(ix8, data3, ix9) = Chain.event(Data2(ix8, data3, ix9))
entrypoint data3(ix1, ix2, ix5, data1) = Chain.event(Data3(ix1, ix2, ix5, data1))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+4 -4
View File
@@ -1,17 +1,17 @@
// An implementation of the factorial function where each recursive // An implementation of the factorial function where each recursive
// call is to another contract. Not the cheapest way to compute factorial. // call is to another contract. Not the cheapest way to compute factorial.
contract FactorialServer = contract FactorialServer =
entrypoint fac : (int) => int function fac : (int) => int
contract Factorial = contract Factorial =
record state = {worker : FactorialServer} record state = {worker : FactorialServer}
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
stateful entrypoint set_worker(worker) = put(state{worker = worker}) function set_worker(worker) = put(state{worker = worker})
entrypoint fac(x : int) : int = function fac(x : int) : int =
if(x == 0) 1 if(x == 0) 1
else x * state.worker.fac(x - 1) else x * state.worker.fac(x - 1)
-5
View File
@@ -1,5 +0,0 @@
contract Fail =
record pt = {x : int, y : int}
record r = {p : pt}
function fail() = {p.x = 0, p.y = 0}
-47
View File
@@ -1,47 +0,0 @@
contract FunctionArguments =
entrypoint sum(n : int, m: int) =
n + m
entrypoint append(xs : list(string)) =
switch(xs)
[] => ""
y :: ys => String.concat(y, append(ys))
entrypoint menot(b) =
!b
entrypoint bitsum(b : bits) =
Bits.sum(b)
record answer('a) = {label : string, result : 'a}
entrypoint read(a : answer(int)) =
a.result
entrypoint sjutton(b : bytes(17)) =
b
entrypoint sextiosju(b : bytes(67)) =
b
entrypoint trettiotva(b : bytes(32)) =
b
entrypoint find_oracle(o : oracle(int, bool)) =
true
entrypoint find_query(q : oracle_query(int, bool)) =
true
datatype colour() = Green | Yellow | Red | Pantone(int)
entrypoint traffic_light(c : colour) =
Red
entrypoint tuples(t : unit) =
t
entrypoint due(t : Chain.ttl) =
true
-15
View File
@@ -1,15 +0,0 @@
contract Functions =
function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
function map'() = map
function plus(x, y) = x + y
entrypoint test1(xs : list(int)) = map(curry(plus)(5), xs)
entrypoint test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
entrypoint test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+10 -7
View File
@@ -12,20 +12,23 @@ contract FundMe =
deadline : int, deadline : int,
goal : int } goal : int }
stateful function spend(args : spend_args) = private function require(b : bool, err : string) =
if(!b) abort(err)
private function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount) Chain.spend(args.recipient, args.amount)
entrypoint init(beneficiary, deadline, goal) : state = public function init(beneficiary, deadline, goal) : state =
{ contributions = {}, { contributions = {},
beneficiary = beneficiary, beneficiary = beneficiary,
deadline = deadline, deadline = deadline,
total = 0, total = 0,
goal = goal } goal = goal }
function is_contributor(addr) = private function is_contributor(addr) =
Map.member(addr, state.contributions) Map.member(addr, state.contributions)
stateful entrypoint contribute() = public stateful function contribute() =
if(Chain.block_height >= state.deadline) if(Chain.block_height >= state.deadline)
spend({ recipient = Call.caller, amount = Call.value }) // Refund money spend({ recipient = Call.caller, amount = Call.value }) // Refund money
false false
@@ -36,7 +39,7 @@ contract FundMe =
total @ tot = tot + Call.value }) total @ tot = tot + Call.value })
true true
stateful entrypoint withdraw() = public stateful function withdraw() =
if(Chain.block_height < state.deadline) if(Chain.block_height < state.deadline)
abort("Cannot withdraw before deadline") abort("Cannot withdraw before deadline")
if(Call.caller == state.beneficiary) if(Call.caller == state.beneficiary)
@@ -46,13 +49,13 @@ contract FundMe =
else else
abort("Not a contributor or beneficiary") abort("Not a contributor or beneficiary")
stateful function withdraw_beneficiary() = private stateful function withdraw_beneficiary() =
require(state.total >= state.goal, "Project was not funded") require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary, spend({recipient = state.beneficiary,
amount = Contract.balance }) amount = Contract.balance })
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts }) put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
stateful function withdraw_contributor() = private stateful function withdraw_contributor() =
if(state.total >= state.goal) if(state.total >= state.goal)
abort("Project was funded") abort("Project was funded")
let to = Call.caller let to = Call.caller
+1 -1
View File
@@ -1,3 +1,3 @@
contract Identity = contract Identity =
entrypoint main (x:int) = x function main (x:int) = x
+2 -2
View File
@@ -2,8 +2,8 @@ include "included.aes"
include "../contracts/included2.aes" include "../contracts/included2.aes"
contract Include = contract Include =
entrypoint foo() = function foo() =
Included.foo() < Included2a.bar() Included.foo() < Included2a.bar()
entrypoint bar() = function bar() =
Included2b.foo() > Included.foo() Included2b.foo() > Included.foo()
+2 -2
View File
@@ -3,6 +3,6 @@ contract InitTypeError =
type state = map(int, int) type state = map(int, int)
// Check that the compiler catches ill-typed init entrypoint // Check that the compiler catches ill-typed init function
entrypoint init() = "not the right type!" function init() = "not the right type!"
+56 -56
View File
@@ -4,97 +4,97 @@ contract Maps =
record state = { map_i : map(int, pt), record state = { map_i : map(int, pt),
map_s : map(string, pt) } map_s : map(string, pt) }
entrypoint init() = { map_i = {}, map_s = {} } function init() = { map_i = {}, map_s = {} }
entrypoint get_state() = state function get_state() = state
// {[k] = v} // {[k] = v}
entrypoint map_i() = function map_i() =
{ [1] = {x = 1, y = 2}, { [1] = {x = 1, y = 2},
[2] = {x = 3, y = 4}, [2] = {x = 3, y = 4},
[3] = {x = 5, y = 6} } [3] = {x = 5, y = 6} }
entrypoint map_s() = function map_s() =
{ ["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 entrypoint map_state_i() = put(state{ map_i = map_i() }) function map_state_i() = put(state{ map_i = map_i() })
stateful entrypoint map_state_s() = put(state{ map_s = map_s() }) function map_state_s() = put(state{ map_s = map_s() })
// m[k] // m[k]
entrypoint get_i(k, m : map(int, pt)) = m[k] function get_i(k, m : map(int, pt)) = m[k]
entrypoint get_s(k, m : map(string, pt)) = m[k] function get_s(k, m : map(string, pt)) = m[k]
entrypoint get_state_i(k) = get_i(k, state.map_i) function get_state_i(k) = get_i(k, state.map_i)
entrypoint get_state_s(k) = get_s(k, state.map_s) function get_state_s(k) = get_s(k, state.map_s)
// m[k = v] // m[k = v]
entrypoint get_def_i(k, v, m : map(int, pt)) = m[k = v] function get_def_i(k, v, m : map(int, pt)) = m[k = v]
entrypoint get_def_s(k, v, m : map(string, pt)) = m[k = v] function get_def_s(k, v, m : map(string, pt)) = m[k = v]
entrypoint get_def_state_i(k, v) = get_def_i(k, v, state.map_i) function get_def_state_i(k, v) = get_def_i(k, v, state.map_i)
entrypoint get_def_state_s(k, v) = get_def_s(k, v, state.map_s) function get_def_state_s(k, v) = get_def_s(k, v, state.map_s)
// m{[k] = v} // m{[k] = v}
entrypoint set_i(k, p, m : map(int, pt)) = m{ [k] = p } function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
entrypoint set_s(k, p, m : map(string, pt)) = m{ [k] = p } function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
stateful entrypoint 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 entrypoint 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}
entrypoint 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 }
entrypoint 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 entrypoint 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 entrypoint 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 }
entrypoint 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 }
entrypoint 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 entrypoint 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 entrypoint 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 }
entrypoint 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 }
entrypoint addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d } function addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d }
// Map.member // Map.member
entrypoint member_i(k, m : map(int, pt)) = Map.member(k, m) function member_i(k, m : map(int, pt)) = Map.member(k, m)
entrypoint member_s(k, m : map(string, pt)) = Map.member(k, m) function member_s(k, m : map(string, pt)) = Map.member(k, m)
entrypoint member_state_i(k) = member_i(k, state.map_i) function member_state_i(k) = member_i(k, state.map_i)
entrypoint member_state_s(k) = member_s(k, state.map_s) function member_state_s(k) = member_s(k, state.map_s)
// Map.lookup // Map.lookup
entrypoint lookup_i(k, m : map(int, pt)) = Map.lookup(k, m) function lookup_i(k, m : map(int, pt)) = Map.lookup(k, m)
entrypoint lookup_s(k, m : map(string, pt)) = Map.lookup(k, m) function lookup_s(k, m : map(string, pt)) = Map.lookup(k, m)
entrypoint lookup_state_i(k) = lookup_i(k, state.map_i) function lookup_state_i(k) = lookup_i(k, state.map_i)
entrypoint lookup_state_s(k) = lookup_s(k, state.map_s) function lookup_state_s(k) = lookup_s(k, state.map_s)
// Map.lookup_default // Map.lookup_default
entrypoint lookup_def_i(k, m : map(int, pt), def : pt) = function lookup_def_i(k, m : map(int, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_s(k, m : map(string, pt), def : pt) = function lookup_def_s(k, m : map(string, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def) function lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def)
entrypoint lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def) function lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def)
// Map.delete // Map.delete
entrypoint delete_i(k, m : map(int, pt)) = Map.delete(k, m) function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
entrypoint delete_s(k, m : map(string, pt)) = Map.delete(k, m) function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
stateful entrypoint 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 entrypoint 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
entrypoint size_i(m : map(int, pt)) = Map.size(m) function size_i(m : map(int, pt)) = Map.size(m)
entrypoint size_s(m : map(string, pt)) = Map.size(m) function size_s(m : map(string, pt)) = Map.size(m)
entrypoint size_state_i() = size_i(state.map_i) function size_state_i() = size_i(state.map_i)
entrypoint size_state_s() = size_s(state.map_s) function size_state_s() = size_s(state.map_s)
// Map.to_list // Map.to_list
entrypoint tolist_i(m : map(int, pt)) = Map.to_list(m) function tolist_i(m : map(int, pt)) = Map.to_list(m)
entrypoint tolist_s(m : map(string, pt)) = Map.to_list(m) function tolist_s(m : map(string, pt)) = Map.to_list(m)
entrypoint tolist_state_i() = tolist_i(state.map_i) function tolist_state_i() = tolist_i(state.map_i)
entrypoint tolist_state_s() = tolist_s(state.map_s) function tolist_state_s() = tolist_s(state.map_s)
// Map.from_list // Map.from_list
entrypoint fromlist_i(xs : list(int * pt)) = Map.from_list(xs) function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
entrypoint fromlist_s(xs : list(string * pt)) = Map.from_list(xs) function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
stateful entrypoint fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) }) function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful entrypoint fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) }) function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
@@ -3,6 +3,6 @@ contract MissingFieldsInRecordExpr =
record r('a) = {x : int, y : string, z : 'a} record r('a) = {x : int, y : string, z : 'a}
type alias('a) = r('a) type alias('a) = r('a)
entrypoint fail1() = { x = 0 } function fail1() = { x = 0 }
entrypoint fail2(z : 'a) : r('a) = { y = "string", z = z } function fail2(z : 'a) : r('a) = { y = "string", z = z }
entrypoint fail3() : alias(int) = { x = 0, z = 1 } function fail3() : alias(int) = { x = 0, z = 1 }
+1 -1
View File
@@ -2,5 +2,5 @@
contract MissingStateType = contract MissingStateType =
// Check that we get a type error also for implicit state // Check that we get a type error also for implicit state
entrypoint init() = "should be ()" function init() = "should be ()"
-12
View File
@@ -1,12 +0,0 @@
namespace Lib =
entrypoint foo() = ()
contract Remote =
public function foo : () => unit
function bla() = ()
contract Contract =
public function foo() = ()
public private stateful function all_the_things() = ()
private entrypoint wha() = ()
+10 -11
View File
@@ -1,17 +1,16 @@
contract NameClash = contract NameClash =
entrypoint double_proto : () => int function double_proto : () => int
entrypoint double_proto : () => int function double_proto : () => int
entrypoint proto_and_def : int => int function proto_and_def : int => int
entrypoint proto_and_def(n) = n + 1 function proto_and_def(n) = n + 1
entrypoint double_def(x) = x function double_def(x) = x
entrypoint double_def(y) = 0 function double_def(y) = 0
// abort, require, put and state are builtin // abort, put and state are builtin
entrypoint abort() : int = 0 function abort() : int = 0
entrypoint require(b, err) = if(b) abort(err) function put(x) = x
entrypoint put(x) = x function state(x, y) = x + y
entrypoint state(x, y) = x + y
-18
View File
@@ -1,18 +0,0 @@
namespace Foo =
record bla = {x : int, y : bool}
function bar() : Foo.bla = {x = 17, y = true}
contract Bug =
// Crashed the type checker
entrypoint foo() = Foo.bar()
// Also crashed the type checker
type t = Foo.bla
entrypoint test() =
let x : t = Foo.bar()
x
+1 -1
View File
@@ -2,4 +2,4 @@
// You can't shadow existing contracts or namespaces. // You can't shadow existing contracts or namespaces.
contract Call = contract Call =
entrypoint whatever() = () function whatever() = ()
+2 -2
View File
@@ -13,9 +13,9 @@ namespace List =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = function inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs) List.map2((x) => x + 1, xs)
+23 -21
View File
@@ -9,31 +9,31 @@ 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 entrypoint 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 entrypoint 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 entrypoint 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 entrypoint signedRegisterOracle(acct : address, function signedRegisterOracle(acct : address,
sign : signature, sign : signature,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl, signature = sign) Oracle.register(acct, qfee, ttl, signature = sign)
entrypoint queryFee(o : oracle_id) : fee = function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o) Oracle.query_fee(o)
stateful entrypoint 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 entrypoint 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 entrypoint unsafeCreateQueryThenErr(o : oracle_id, function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -59,52 +59,54 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error") require(qfee >= 100000000000000000, "causing a late error")
res res
stateful entrypoint extendOracle(o : oracle_id, function extendOracle(o : oracle_id,
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful entrypoint signedExtendOracle(o : oracle_id, function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address sign : signature, // Signed oracle address
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, r) Oracle.respond(o, q, r)
stateful entrypoint signedRespond(o : oracle_id, function signedRespond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
entrypoint getQuestion(o : oracle_id, function getQuestion(o : oracle_id,
q : query_id) : query_t = q : query_id) : query_t =
Oracle.get_question(o, q) Oracle.get_question(o, q)
entrypoint hasAnswer(o : oracle_id, function hasAnswer(o : oracle_id,
q : query_id) = q : query_id) =
switch(Oracle.get_answer(o, q)) switch(Oracle.get_answer(o, q))
None => false None => false
Some(_) => true Some(_) => true
entrypoint getAnswer(o : oracle_id, function getAnswer(o : oracle_id,
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
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 entrypoint 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 entrypoint 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)
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
+2
View File
@@ -20,3 +20,5 @@ contract OraclesGas =
Oracle.respond(o, q, answer) Oracle.respond(o, q, answer)
() ()
private function require(b : bool, err : string) =
if(!b) abort(err)
+3 -1
View File
@@ -21,7 +21,7 @@ contract Oracles =
function respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
@@ -33,3 +33,5 @@ contract Oracles =
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
+9 -9
View File
@@ -1,27 +1,27 @@
contract Remote1 = contract Remote1 =
entrypoint main : (int) => int function main : (int) => int
contract Remote2 = contract Remote2 =
entrypoint call : (Remote1, int) => int function call : (Remote1, int) => int
contract Remote3 = contract Remote3 =
entrypoint get : () => int function get : () => int
entrypoint tick : () => unit function tick : () => ()
contract RemoteCall = contract RemoteCall =
stateful entrypoint 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)
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) = function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
r2.call(r1, x) r2.call(r1, x)
entrypoint increment(r3 : Remote3) = function increment(r3 : Remote3) =
r3.tick() r3.tick()
entrypoint get(r3 : Remote3) = function get(r3 : Remote3) =
r3.get() r3.get()
entrypoint plus(x, y) = x + y function plus(x, y) = x + y
+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
View File
@@ -1,4 +1,3 @@
contract Simple = contract Simple =
type t = int => int type t = int => int
entrypoint dummy() = ()
+5 -5
View File
@@ -6,11 +6,11 @@
contract SimpleStorage { contract SimpleStorage {
uint storedData uint storedData
entrypoint set(uint x) { function set(uint x) {
storedData = x storedData = x
} }
entrypoint get() constant returns (uint) { function get() constant returns (uint) {
return storedData return storedData
} }
} }
@@ -20,9 +20,9 @@ contract SimpleStorage =
record state = { data : int } record state = { data : int }
entrypoint init(value : int) : state = { data = value } function init(value : int) : state = { data = value }
entrypoint get() : int = state.data function get() : int = state.data
stateful entrypoint set(value : int) = function set(value : int) =
put(state{data = value}) put(state{data = value})
+7 -7
View File
@@ -1,26 +1,26 @@
contract SpendContract = contract SpendContract =
entrypoint withdraw : (int) => int function withdraw : (int) => int
contract SpendTest = contract SpendTest =
stateful entrypoint 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 entrypoint withdraw(amount) : int = function withdraw(amount) : int =
spend(Call.caller, amount) spend(Call.caller, amount)
stateful entrypoint withdraw_from(account, amount) = function withdraw_from(account, amount) =
account.withdraw(amount) account.withdraw(amount)
withdraw(amount) withdraw(amount)
stateful entrypoint 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)
entrypoint get_balance() = Contract.balance function get_balance() = Contract.balance
entrypoint get_balance_of(a) = Chain.balance(a) function get_balance_of(a) = Chain.balance(a)
+6 -6
View File
@@ -6,24 +6,24 @@ contract Stack =
record state = { stack : stack(string), record state = { stack : stack(string),
size : int } size : int }
entrypoint init(ss : list(string)) = { stack = ss, size = length(ss) } function init(ss : list(string)) = { stack = ss, size = length(ss) }
function length(xs) = private function length(xs) =
switch(xs) switch(xs)
[] => 0 [] => 0
_ :: xs => length(xs) + 1 _ :: xs => length(xs) + 1
stateful entrypoint pop() : string = stateful function pop() : string =
switch(state.stack) switch(state.stack)
s :: ss => s :: ss =>
put(state{ stack = ss, size = state.size - 1 }) put(state{ stack = ss, size = state.size - 1 })
s s
stateful entrypoint push(s) = stateful function push(s) =
put(state{ stack = s :: state.stack, size = state.size + 1 }) put(state{ stack = s :: state.stack, size = state.size + 1 })
state.size state.size
entrypoint all() = state.stack function all() = state.stack
entrypoint size() = state.size function size() = state.size
+44 -44
View File
@@ -1,70 +1,70 @@
contract Remote = contract Remote =
record rstate = { i : int, s : string, m : map(int, int) } record rstate = { i : int, s : string, m : map(int, int) }
entrypoint look_at : (rstate) => unit function look_at : (rstate) => ()
entrypoint return_s : (bool) => string function return_s : (bool) => string
entrypoint return_m : (bool) => map(int, int) function return_m : (bool) => map(int, int)
entrypoint get : (rstate) => rstate function get : (rstate) => rstate
entrypoint get_i : (rstate) => int function get_i : (rstate) => int
entrypoint get_s : (rstate) => string function get_s : (rstate) => string
entrypoint get_m : (rstate) => map(int, int) function get_m : (rstate) => map(int, int)
entrypoint fun_update_i : (rstate, int) => rstate function fun_update_i : (rstate, int) => rstate
entrypoint fun_update_s : (rstate, string) => rstate function fun_update_s : (rstate, string) => rstate
entrypoint fun_update_m : (rstate, map(int, int)) => rstate function fun_update_m : (rstate, map(int, int)) => rstate
entrypoint fun_update_mk : (rstate, int, int) => rstate function fun_update_mk : (rstate, int, int) => rstate
contract StateHandling = contract StateHandling =
type state = Remote.rstate type state = Remote.rstate
entrypoint init(r : Remote, i : int) = function init(r : Remote, i : int) =
let state0 = { i = 0, s = "undefined", m = {} } let state0 = { i = 0, s = "undefined", m = {} }
r.fun_update_i(state0, i) r.fun_update_i(state0, i)
entrypoint read() = state function read() = state
entrypoint read_i() = state.i function read_i() = state.i
entrypoint read_s() = state.s function read_s() = state.s
entrypoint read_m() = state.m function read_m() = state.m
stateful entrypoint update(new_state : state) = put(new_state) function update(new_state : state) = put(new_state)
stateful entrypoint update_i(new_i) = put(state{ i = new_i }) function update_i(new_i) = put(state{ i = new_i })
stateful entrypoint update_s(new_s) = put(state{ s = new_s }) function update_s(new_s) = put(state{ s = new_s })
stateful entrypoint update_m(new_m) = put(state{ m = new_m }) function update_m(new_m) = put(state{ m = new_m })
entrypoint pass_it(r : Remote) = r.look_at(state) function pass_it(r : Remote) = r.look_at(state)
stateful entrypoint nop(r : Remote) = put(state{ i = state.i }) function nop(r : Remote) = put(state{ i = state.i })
entrypoint 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)
entrypoint return_it_m(r : Remote, big : bool) = function return_it_m(r : Remote, big : bool) =
let x = r.return_m(big) let x = r.return_m(big)
Map.size(x) Map.size(x)
entrypoint pass(r : Remote) = r.get(state) function pass(r : Remote) = r.get(state)
entrypoint pass_i(r : Remote) = r.get_i(state) function pass_i(r : Remote) = r.get_i(state)
entrypoint pass_s(r : Remote) = r.get_s(state) function pass_s(r : Remote) = r.get_s(state)
entrypoint pass_m(r : Remote) = r.get_m(state) function pass_m(r : Remote) = r.get_m(state)
entrypoint pass_update_i(r : Remote, i) = r.fun_update_i(state, i) function pass_update_i(r : Remote, i) = r.fun_update_i(state, i)
entrypoint 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)
entrypoint 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 entrypoint 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 entrypoint 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 entrypoint 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 entrypoint 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
entrypoint look_at(s : state) = () function look_at(s : state) = ()
entrypoint get(s : state) = s function get(s : state) = s
entrypoint get_i(s : state) = s.i function get_i(s : state) = s.i
entrypoint get_s(s : state) = s.s function get_s(s : state) = s.s
entrypoint get_m(s : state) = s.m function get_m(s : state) = s.m
entrypoint fun_update_i(st, ni) = st{ i = ni } function fun_update_i(st, ni) = st{ i = ni }
entrypoint fun_update_s(st, ns) = st{ s = ns } function fun_update_s(st, ns) = st{ s = ns }
entrypoint fun_update_m(st, nm) = st{ m = nm } function fun_update_m(st, nm) = st{ m = nm }
entrypoint fun_update_mk(st, k, v) = st{ m = st.m{[k] = v} } function fun_update_mk(st, k, v) = st{ m = st.m{[k] = v} }
-54
View File
@@ -1,54 +0,0 @@
contract Remote =
stateful entrypoint remote_spend : (address, int) => unit
entrypoint remote_pure : int => int
contract Stateful =
function pure(x) = x + 1
stateful function local_spend(a) =
Chain.spend(a, 1000)
// Non-stateful functions cannot mention stateful functions
entrypoint fail1(a : address) = Chain.spend(a, 1000)
entrypoint fail2(a : address) = local_spend(a)
entrypoint 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 entrypoint ok1(a : address) = Chain.spend(a, 1000)
// And pure functions are always allowed
stateful entrypoint ok2(a : address) = pure(5)
stateful entrypoint ok3(a : address) =
let foo = pure
foo(5)
// No error here (fail4 is annotated as not stateful)
entrypoint ok4(a : address) = fail4(a)
// Lamdbas are checked at the construction site
function fail5() : address => unit = (a) => Chain.spend(a, 1000)
// .. so you can pass a stateful lambda to a non-stateful higher-order
// function:
function apply(f : 'a => 'b, x) = f(x)
stateful entrypoint ok5(a : address) =
apply((val) => Chain.spend(a, val), 1000)
// It doesn't matter if remote calls are stateful or not
entrypoint ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
entrypoint ok7(r : Remote) = r.remote_pure(5)
// But you can't send any tokens if not stateful
entrypoint fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
entrypoint fail7(r : Remote) = r.remote_pure(value = 1000, 5)
entrypoint fail8(r : Remote) =
let foo = r.remote_pure
foo(value = 1000, 5)
entrypoint ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
+2 -2
View File
@@ -1,4 +1,4 @@
contract Strings = contract Strings =
entrypoint str_len(s) = String.length(s) function str_len(s) = String.length(s)
entrypoint str_concat(s1, s2) = String.concat(s1, s2) function str_concat(s1, s2) = String.concat(s1, s2)
-2
View File
@@ -1,2 +0,0 @@
contract Stub =
entrypoint foo : (int) => int
+4 -4
View File
@@ -91,10 +91,10 @@ contract Identity =
// } // }
// let id(x) = x // let id(x) = x
// let main(xs) = map(double,xs) // let main(xs) = map(double,xs)
entrypoint z(f,x) = x function z(f,x) = x
function s(n) = (f,x)=>f(n(f,x)) private function s(n) = (f,x)=>f(n(f,x))
function add(m,n) = (f,x)=>m(f,n(f,x)) private function add(m,n) = (f,x)=>m(f,n(f,x))
entrypoint main(_) = function main(_) =
let three=s(s(s(z))) let three=s(s(s(z)))
add(three,three) add(three,three)
(((i)=>i+1),0) (((i)=>i+1),0)
-36
View File
@@ -1,36 +0,0 @@
contract TuplesMatch =
entrypoint tuplify3() = (t) => switch(t)
(x, y, z) => 3
entrypoint fst(p : int * string) =
switch(p)
(x, y) => x
entrypoint fst'(p : int * string) =
switch(p)
(x, _) => x
entrypoint snd(p : int * string) =
switch(p)
(x, y) => y
entrypoint snd'(p : int * string) =
switch(p)
(_, y) => y
entrypoint sum(p) =
switch(p)
(x, y) => x + y
entrypoint swap(p : int * string) =
switch(p)
(x, y) => (y, x)
entrypoint id(p : int * int * string) =
switch(p)
(x, y, z) => (x, y, z)
entrypoint nest(p : (int * int) * string) =
switch(p)
(xy, z) => switch(xy) (x, y) => (x, y, z)
entrypoint deep(p : (int * int) * (int * int)) =
switch(p)
((x, y), (z, w)) => (x, y, z, w)
entrypoint deep_sum(p : (int * int) * (int * int)) =
switch(p)
((x, y), (z, w)) => x + y + z + w
+2 -2
View File
@@ -2,12 +2,12 @@
contract Remote = contract Remote =
type themap = map(int, string) type themap = map(int, string)
entrypoint foo : () => themap function foo : () => themap
contract Main = contract Main =
type themap = map(string, int) type themap = map(string, int)
// Should fail // Should fail
entrypoint foo(r : Remote) : themap = r.foo() function foo(r : Remote) : themap = r.foo()
+15 -32
View File
@@ -6,54 +6,37 @@ contract Test =
record r2 = { z : int, w : int } record r2 = { z : int, w : int }
record r3 = { x : int, z : int } record r3 = { x : int, z : int }
entrypoint set_x(r : r, z) = r{ x["foo"] @ x = x + 1 } function set_x(r : r, z) = r{ x["foo"] @ x = x + 1 }
entrypoint bla(m : map(string, int)) = { [0] = "bla", ["foo"] = "" } function bla(m : map(string, int)) = { [0] = "bla", ["foo"] = "" }
entrypoint foo(r) = r { y = 0 } function foo(r) = r { y = 0 }
entrypoint bar() = { y = "foo", z = 0 } function bar() = { y = "foo", z = 0 }
entrypoint baz() = { y = "foo", w = 0 } function baz() = { y = "foo", w = 0 }
entrypoint foo1() = zz function foo1() = zz
entrypoint test1() : string = { y = 0 } function test1() : string = { y = 0 }
entrypoint test2(x : string) = x { y = 0 } function test2(x : string) = x { y = 0 }
entrypoint test3(x : string) = x { y @ y = y + 1 } function test3(x : string) = x { y @ y = y + 1 }
entrypoint test4(x : string) : int = x.y function test4(x : string) : int = x.y
entrypoint test5(xs) = function test5(xs) =
switch(xs) switch(xs)
x :: x => x x :: x => x
[] => 0 [] => 0
entrypoint case_pat(xs) = function case_pat(xs) =
switch(xs) switch(xs)
[] => 0 [] => 0
x :: xs => "x" x :: xs => "x"
entrypoint foo2(m : map(string, int)) = m{ [1] = "bla" } function foo2(m : map(string, int)) = m{ [1] = "bla" }
entrypoint bad_if(x, y : int, w : int, z : string) = function bad_if(x, y : int, w : int, z : string) =
if(x) y if(x) y
elif(x) w elif(x) w
else z else z
entrypoint type_error(r, x) = function type_error(r, x) =
set_x(set_x(x, r), x) set_x(set_x(x, r), x)
entrypoint repeated_arg(x : int, y, x : string, y : bool) : string = x
entrypoint missing1() =
let x = 0
entrypoint missing_fun1() =
let f(x) = x
entrypoint missing2() =
let x = 0
let y = 0
entrypoint missing_fun2() =
let f() = 0
let g() = f()
+9 -7
View File
@@ -7,21 +7,23 @@ contract VariantTypes =
datatype color = Red | Green | Blue | Grey(int) datatype color = Red | Green | Blue | Grey(int)
entrypoint init() = Stopped function init() = Stopped
stateful entrypoint start(bal : int) = function require(b) = if(!b) abort("required")
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 entrypoint stop() = function stop() =
switch(state) switch(state)
Started(st) => Started(st) =>
require(Call.caller == st.owner, "required") require(Call.caller == st.owner)
put(Stopped) put(Stopped)
st.balance st.balance
entrypoint get_color() = switch(state) Started(st) => st.color function get_color() = switch(state) Started(st) => st.color
stateful entrypoint 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}))
entrypoint get_state() = state function get_state() = state
+1 -1
View File
@@ -8,7 +8,7 @@ contract VotingType =
function delegate : address => unit function delegate : address => unit
function vote : int => unit function vote : int => unit
function winnerName : unit => string function winnerName : unit => string
function currentTally : unit => list(string * int) function currentTally : unit => list((string, int))
/* Contract implementation */ /* Contract implementation */
contract Voting = contract Voting =
+56
View File
@@ -0,0 +1,56 @@
/* Example from Solidity by Example
http://solidity.readthedocs.io/en/develop/common-patterns.html
contract WithdrawalContract {
address public richest
uint public mostSent
mapping (address => uint) pendingWithdrawals
function WithdrawalContract() payable {
richest = msg.sender
mostSent = msg.value
}
function becomeRichest() payable returns (bool) {
if (msg.value > mostSent) {
pendingWithdrawals[richest] += msg.value
richest = msg.sender
mostSent = msg.value
return true
} else {
return false
}
}
function withdraw() {
uint amount = pendingWithdrawals[msg.sender]
// Remember to zero the pending refund before
// sending to prevent re-entrancy attacks
pendingWithdrawals[msg.sender] = 0
msg.sender.transfer(amount)
}
}
*/
contract WithdrawalContract =
record state = { richest : address,
mostSent : uint,
pendingWithdrawals : map(address, uint) }
function becomeRichest() : result(bool) =
if (call().value > state.mostSent)
let totalAmount : uint = Map.get_(state.richest, pendingWithdrawals) + call().value
{state = state{ pendingWithdrawals = Map.insert(state.richest, call().value, state.pendingWithdrawals),
richest = call().sender,
mostSent = call().value },
result = true}
else
{result = false}
function withdraw() =
let amount : uint = Map.get_(call().sender, state.pendingWithdrawals)
{ state.pendingWithdrawals = Map.insert(call().sender, 0, state.pendingWithdrawals),
transactions = spend_tx(amount, call().sender) }