Compare commits

...

63 Commits

Author SHA1 Message Date
Hans Svensson 4b0a3e53d6 Merge pull request #81 from aeternity/PT-166406414-v3.1.0
Prepare v3.1.0
2019-06-03 11:14:20 +02:00
Hans Svensson 07c445082a Prepare v3.1.0 2019-06-03 10:58:28 +02:00
Ulf Norell 4f612650e3 Merge pull request #80 from aeternity/fix-basic-block-bug
Fix bug in basic block generation
2019-06-03 10:35:18 +02:00
Ulf Norell 80ed24a4f6 Fix bug in basic block generation
(JUMPIF ends a basic block)
2019-06-03 10:30:25 +02:00
Hans Svensson 05256eeb60 Merge pull request #79 from aeternity/negative_literals_in_calldata
Allow negative literals in calldata and result
2019-06-03 09:21:22 +02:00
Ulf Norell 7592390059 Merge pull request #78 from radrow/builtins
Popularized use of builtin_call function
2019-06-03 09:19:03 +02:00
Hans Svensson bb4ef61a50 Allow negative literals in calldata and result 2019-06-03 09:08:53 +02:00
radrow bb5a710626 Popularized use of builtin_call function 2019-05-31 12:21:48 +02:00
Hans Svensson 758fecbb9b Merge pull request #75 from aeternity/fix_aci
Restructure and improve ACI
2019-05-31 11:33:07 +02:00
Ulf Norell b1e882b115 Merge pull request #77 from aeternity/fate-compiler-improvements
Update to new TUPLE instruction
2019-05-28 19:00:57 +02:00
Ulf Norell bea524635b Add backend argument (aevm | fate) to aeso_compiler options
and test fate backend on (most) compilable contracts
2019-05-28 16:40:54 +02:00
Ulf Norell e44a890292 Fix bug in compilation of Map.lookup 2019-05-28 16:19:39 +02:00
Hans Svensson d3a13eafed A record should be _one_ object 2019-05-28 14:25:37 +02:00
Ulf Norell 0532c54ca0 Pretty print state variables 2019-05-28 14:18:38 +02:00
Ulf Norell 02d0025fd7 Don't use POP 2019-05-28 14:18:38 +02:00
Ulf Norell 0409a658b0 Update to new TUPLE instruction
... and minor fixes
2019-05-28 14:18:38 +02:00
Ulf Norell c045e5d653 Merge pull request #76 from aeternity/map-update-bug
Fix bug when compiling map updates with default values
2019-05-28 14:03:00 +02:00
Ulf Norell a95913e793 Fix bug when compiling map updates with default values 2019-05-28 13:47:22 +02:00
Hans Svensson ec678878fa Update aeso_aci.md 2019-05-28 13:26:22 +02:00
Hans Svensson 4b0837dc59 Leave state/event blank if not present 2019-05-28 13:07:24 +02:00
Tobias Lindahl ed96dc1d42 Merge pull request #74 from aeternity/PT-166282300-add-missing-fate-instructions
Add missing instructions for FATE
2019-05-28 13:05:59 +02:00
Tobias Lindahl 60d9581fae Add missing instructions for FATE 2019-05-28 11:35:24 +02:00
Hans Svensson 0ded431df8 Fix interface and use atoms instead of binaries 2019-05-28 11:18:21 +02:00
Hans Svensson e7419b79fd Put state and event types at the top level 2019-05-28 11:08:36 +02:00
Hans Svensson c60999edf0 Refactor aeso_aci with dont_unfold, etc. 2019-05-28 11:08:36 +02:00
Hans Svensson ea17dae93e Silence compiler warning 2019-05-28 11:08:36 +02:00
Hans Svensson 8a16bd4fa1 Add dont_unfold option to type inference function 2019-05-28 11:08:36 +02:00
Hans Svensson 1ed40f1cca Pretty print state variables 'a instead of '1 2019-05-28 11:08:36 +02:00
Hans Svensson 5c98317a5a Make 'indexed' keyword optional 2019-05-28 11:08:36 +02:00
Hans Svensson 33dbeeefad Consider namespaces when collecting used_types 2019-05-28 11:08:36 +02:00
Tobias Lindahl 098dac65e2 Merge pull request #73 from aeternity/PT-162805991-fate-state
The state is always live to prevent bad optimizations
2019-05-28 09:59:22 +02:00
Tobias Lindahl 9cf8733f77 The state is always live to prevent bad optimizations 2019-05-28 09:52:50 +02:00
Ulf Norell 98f349f67c Merge pull request #72 from aeternity/bad-record-compiler-crash
Bad record compiler crash
2019-05-27 12:18:24 +02:00
Ulf Norell 96547ea2ec Test for record field parse error 2019-05-27 12:04:38 +02:00
Ulf Norell ee03442ddf Return parse error instead of crashing the type checker 2019-05-27 11:58:18 +02:00
Tobias Lindahl 0fa09467f6 Pt 166148534 refactor fate code (#71)
* Change of module names aeb_fate_code -> aeb_fate_ops

* Add missing call instructions

* Use new adt for fate code

* Add default init function if not present and keep init function if present

* Fix BLOCKHASH function for fate

* Rewrite for clarity and Dialyzer
2019-05-23 14:01:41 +02:00
Hans Svensson dcae96ed21 Merge pull request #70 from aeternity/PT-166147620-prepare_v3
PT-166147620 Prepare for v3.0.0
2019-05-21 10:37:41 +02:00
Hans Svensson 94689dd0e9 Prepare for v3.0.0 2019-05-21 09:55:17 +02:00
Hans Svensson be7c0e1bd4 Remove escript aesophia 2019-05-21 09:54:42 +02:00
Ulf Norell 8eaafa736c Merge pull request #69 from aeternity/PT-162578475-stateful
Check stateful annotations
2019-05-15 15:44:37 +02:00
Ulf Norell cf5a8aeb5f changelog 2019-05-14 10:10:53 +02:00
Ulf Norell 9e555a3121 Fix type definition 2019-05-14 09:42:27 +02:00
Ulf Norell d051fa6c89 Remove bad code 2019-05-14 09:42:17 +02:00
Tobias Lindahl d4238c0bdc Merge pull request #68 from aeternity/PT-165964555-make-remote-calls-to-contract-objects
Keep the contract type in fate code
2019-05-14 09:41:31 +02:00
Ulf Norell 389072fb12 Add stateful to __call 2019-05-14 09:32:52 +02:00
Tobias Lindahl 1760593170 Keep the contract type in fate code 2019-05-14 09:29:52 +02:00
Ulf Norell d8dd6b900f Remove unused test contract 2019-05-13 17:51:56 +02:00
Ulf Norell 74d4048d9f Check that init doesn't read or write the state 2019-05-13 17:51:47 +02:00
Ulf Norell 6bd2b7c483 Remember source location when computing used names 2019-05-13 17:50:34 +02:00
Ulf Norell 5aed8b3ef5 Check stateful annotations
Functions must be annotated as `stateful` in order to
- Update the contract state (using `put`)
- Call `Chain.spend` or other primitive functions that cost tokens
- Call an Oracle or AENS function that requires a signature
- Make a remote call with a non-zero value
- Construct a lambda calling a stateful function

It does not need to be stateful to
- Read the contract state
- Call another contract with value=0, even when the remote function is stateful
2019-05-13 13:39:17 +02:00
Ulf Norell e1a798aef4 Check for repeated argument names to functions
(PT-159592825)
2019-05-10 15:51:42 +02:00
Ulf Norell 23cc8e1132 Letrec and letfun (#65)
* Type check and compile letfuns

* Minor code simplification

* Remove let rec from Sophia
2019-05-10 13:27:57 +02:00
Tobias Lindahl 691ae72fbb Merge pull request #66 from aeternity/PT-165857097-add-call-value-instruction
Add call value instruction in fate
2019-05-10 13:15:22 +02:00
Tobias Lindahl 8830095c7e Add call value instruction in fate 2019-05-10 12:38:58 +02:00
Tobias Lindahl 251b876495 Add value and gas to remote calls (#64)
* Add value and gas to remote calls
2019-05-10 09:00:36 +02:00
Hans Svensson f8ee8f7129 Merge pull request #63 from aeternity/PT-165440601-165713319-sophia_addons
Add Contract.creator and address checking primitives
2019-05-09 10:34:45 +02:00
Hans Svensson 192ec207a7 Add Contract.creator and address checking primitives 2019-05-09 09:54:04 +02:00
Ulf Norell 0aa1c89556 Fate compiler (#62)
* Update to changes in icode format

* Start on new intermediate code for FATE

* Compile `let` to FATE

* Fix and improve broken bytecode optimisations

* Basic tuple patterns

* Compile shallow matching on tuples

* Liveness analysis for local variables

* Fix minor bug

* Use RETURNR when possible

* Nicer debug printing

* Refactor optimization rules

* Compile tuple construction

* Improve instruction analysis and generalize some optimizations

* Compile nested pattern matching to case trees

(Only tuple and variable patterns so far)

* Reannotate and repeat optimization pass once it done

Could try hard to keep annotations more precise, but would be more error prone

* Get rid of unnecessary STORE instructions

* Keep better track of liveness annotations when swapping instructions

* Limit the number of iterations for the optimization loop

Should finish in one iteration, but we shouldn't loop if there are bugs
or corner cases where it doesn't.

* Pattern matching on booleans

* wip: rewrite case tree compiler to handle catch-alls

still with debug printing, and can't compile it yet

* Add missing case in renaming

* Compile case trees all the way to Fate assembly

* Simplify variables bindings in environment

* Shortcut let x = y in ...

* compile list literals

* Fix various bugs in pattern match compilation

* Pretty printer for fcode

* Fix renaming bug

* Another renaming bug

* Handle switch_body in optimizations

* Remove optimization for if-then-else

* Tag instructions in annotated scode

* Remove 'if' from fcode

* Fix dialyzer things

* Remove unused argument

* Compile pattern matching on integer literals

* Compile list patterns

* Use op_view in more places

* allow leaving out fields from record patterns

* compile records (patterns and construction)

* Compile record update

* Use SETELEMENT instruction

* Compile variants

* Remove incorrect push for tuple switches

* Optimize matching on single constructors datatypes

* Use the fact that SWITCH and JUMPIF can use args and vars

* string literals and pattern matching on the same

* Compile character literals

* Minor refactoring of op instruction handling

* compile address literals

* Get rid of unit in AST

* Unary operators

* Compile function calls

(to fully saturated top-level functions only)

* fix breakage after unary operators

* variables are now lists of names in fcode

* pretty printing for function calls

* use STORE ?a instead of PUSH during optimizations

* no-op fcode optimization pass

* some constant propagation optimizations

* Case on constructor optimization

* fix minor bugs

* Compile all the operators

* Compile maps

* Simplify JUMPIF on true/false

* Fixed left-over reference to STR_EQ

* Add compile-time evaluation for more operators

* Distinguish local vars and top-level names already in fcode

* Compile builtins

* Compile bytes(N)

Compile to FATE strings for now

* Improve inlining of PUSH

* Fix name resolution bug

* Change map_get/set to operators in fcode

* Compile lambdas and higher-order functions

* Optimize single variable closure envs

* Handle unapplied builtins and top-level functions

* Missing case in fcode pretty printer

* Fix variable binding bug in fcode compiler

* Compiler side of state updates

No support in FATE yet though

* Compile statements

* Compile events

But no FATE support for events yet

* Compile remote calls

* Clearer distinction between applied and unapplied top-level things (def/builtin) in fcode

* Tag for literals in fcode to make code cleaner

* We now have block hash at height in FATE

* Update aebytecode commit

* Get rid of catchall todos

* Jump some hoops to please Dialyzer
2019-05-07 15:48:47 +02:00
Ulf Norell 71b97cba62 Merge pull request #61 from aeternity/PT-165597438-equality-on-bytes
Support equality on bytes(N)
2019-04-26 08:47:14 +02:00
Ulf Norell 8a381e5ef1 Support equality on bytes(N) 2019-04-25 16:06:50 +02:00
Hans Svensson 386419f112 Merge pull request #60 from aeternity/PT-164629541-generic_hash_and_signature
Revert bytes(N) from icode/vm-types
2019-04-24 08:58:43 +02:00
Hans Svensson 45a62f0807 Simplify ast_typerep 2019-04-24 08:44:05 +02:00
Hans Svensson 3255c62e0e Revert bytes(N) from icode/vm-types 2019-04-23 17:47:50 +02:00
50 changed files with 3702 additions and 1184 deletions
+39 -1
View File
@@ -9,6 +9,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### 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
### Added
- Stubs (not yet wired up) for compilation to FATE
@@ -35,6 +71,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.1.0...HEAD
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+76 -55
View File
@@ -30,28 +30,14 @@ generates the following JSON structure representing the contract interface:
``` json
{
"contract": {
"name": "Answers",
"type_defs": [
{
"name": "state",
"vars": [],
"typedef": "{a : map(string,int)}"
},
{
"name": "answers",
"vars": [],
"typedef": "map(string,int)"
}
],
"functions": [
{
"name": "init",
"arguments": [],
"type": "{a : map(string,int)}",
"name": "init",
"returns": "Answers.state",
"stateful": true
},
{
"name": "new_answer",
"arguments": [
{
"name": "q",
@@ -62,9 +48,36 @@ generates the following JSON structure representing the contract interface:
"type": "int"
}
],
"type": "map(string,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": []
}
]
}
}
@@ -74,62 +87,70 @@ When that encoding is decoded the following include definition is generated:
```
contract Answers =
function new_answer : (string, int) => map(string,int)
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int)
```
### Types
``` erlang
contract_string() = string() | binary()
json_string() = binary()
```erlang
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
```
### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()}
Types
Generate the JSON encoding of the interface to a contract. The type definitions
and non-private functions are included in the JSON string.
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
#### render\_aci\_json(json() | json\_text()) -> string().
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString.
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.
Take a JSON encoding of a contract interface and generate a contract interface
that can be included in another contract.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
This is an example of using the ACI generator from an Erlang shell. The file
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang
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"...>>}
2> {ok,Encoding} = aeso_aci:encode(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>>
3> file:write_file("aci_test.aci", Encoding).
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).
{ok,[#{contract =>
#{functions =>
[#{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
4> Decoded = aeso_aci:decode(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">>
5> file:write_file("aci_test.include", Decoded).
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>}
5> file:write_file("aci_test.include", InterfaceStub).
ok
6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>>
6> jsx:prettify(jsx:encode(JsonACI)).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>>
```
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.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a
more easily readable form. This is what is shown in the description above.
+2 -18
View File
@@ -2,35 +2,19 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "e8253b0"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref, "f129887"}}}
, {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{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, [
{warnings, [unknown]},
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "2.1.0"},
{relx, [{release, {aesophia, "3.1.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+2 -2
View File
@@ -1,11 +1,11 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}},
{ref,"f1298870e526f4e9330447d3a281af5ef4e06e17"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
+212 -269
View File
@@ -9,76 +9,56 @@
-module(aeso_aci).
-export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
-export([ contract_interface/2
, contract_interface/3
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
, render_aci_json/1
%% Top-level
-record(contract, {ann,con,decls}).
%% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
, json_encode_expr/1
, json_encode_type/1]).
%% Types
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-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}).
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
%% Tokens
-record(arg, {ann,id,type}).
-record(id, {ann,name}).
-record(con, {ann,name}).
-record(qid, {ann,names}).
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
%% External API
-spec contract_interface(aci_type(), string()) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString) ->
contract_interface(Type, ContractString, []).
%% Expressions
-record(bool, {ann,bool}).
-record(int, {ann,value}).
-record(string, {ann,bin}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
-spec contract_interface(aci_type(), string(), [term()]) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString, CompilerOpts) ->
do_contract_interface(Type, ContractString, CompilerOpts).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
-spec render_aci_json(json() | json_text()) -> {ok, binary()}.
render_aci_json(Json) ->
do_render_aci_json(Json).
encode(ContractString) -> encode(ContractString, []).
-spec json_encode_expr(aeso_syntax:expr()) -> json().
json_encode_expr(Expr) ->
encode_expr(Expr).
encode(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options);
encode(ContractString, Options) ->
-spec json_encode_type(aeso_syntax:type()) -> json().
json_encode_type(Type) ->
encode_type(Type).
%% Internal functions
do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, binary_to_list(Contract), Options);
do_contract_interface(Type, ContractString, Options) ->
try
Ast = parse(ContractString, Options),
Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]),
%% io:format("~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract.
Contract = lists:last(TypedAst),
Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) ||
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
@@ -95,201 +75,208 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
%% encode_func(Function) -> JSON
encode_contract(Contract) ->
C0 = #{name => encode_name(contract_name(Contract))},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end,
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs},
C2 = case Es of
[] -> C1;
[#{typedef := ET}] -> C1#{event => ET}
end,
C3 = case Ss of
[] -> C2;
[#{typedef := ST}] -> C2#{state => ST}
end,
Fdefs = [ encode_function(F)
|| F <- sort_decls(contract_funcs(Contract)),
not is_private(F) ],
#{contract => C3#{functions => Fdefs}}.
%% Encode a function definition. Currently we are only interested in
%% 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_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, encode_name(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"returns">>, encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_anon_args(Types) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))],
[ #{name => V, type => encode_type(T)}
|| {V, T} <- lists:zip(Anons, Types) ].
%% encode_args(Args) -> [JSON].
%% encode_arg(Args) -> JSON.
encode_args(Args) -> [ encode_arg(A) || A <- Args ].
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_types(Types) -> [JSON].
%% encode_type(Type) -> JSON.
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
encode_type(#tvar{name=N}) -> encode_name(N);
encode_type(#id{name=N}) -> encode_name(N);
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_type(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
[{<<"tuple">>,Eas}];
encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>, Len};
encode_type(#record_t{fields=Fs}) ->
Efs = encode_fields(Fs),
[{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
[{Name,Efs}];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
[{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
[{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) ->
list_to_binary(Name).
%% encode_fields(Fields) -> [JSON].
%% encode_field(Field) -> JSON.
%% Encode a record field.
encode_fields(Fs) ->
[ encode_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_typedef(TypeDef) -> JSON.
encode_arg({arg, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, encode_name(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, encode_alias(Def)}].
Def = typedef_def(Type),
#{name => encode_name(Name),
vars => encode_tvars(Vars),
typedef => encode_type(Def)}.
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
[ #{name => encode_type(V)} || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, encode_name(N)}].
%% Encode type
encode_type({tvar, _, N}) -> 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_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
encode_types(Ts) -> [ encode_type(T) || T <- Ts ].
%% encode_stmt(Stmt) -> JSON.
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ].
encode_stmt(E) ->
encode_expr(E).
encode_type_field({field_t, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
%% encode_exprs(Exprs) -> [JSON].
%% encode_expr(Expr) -> JSON.
encode_name(Name) when is_list(Name) ->
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
encode_exprs(Es) ->
[ encode_expr(E) || E <- Es ].
%% Encode Expr
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ].
encode_expr(#id{name=N}) -> encode_name(N);
encode_expr(#con{name=N}) -> encode_name(N);
encode_expr(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_expr(#typed{expr=E}) ->
encode_expr(E);
encode_expr(#bool{bool=B}) -> B;
encode_expr(#int{value=V}) -> V;
encode_expr(#string{bin=B}) -> B;
encode_expr(#bytes{bin=B}) -> B;
encode_expr(#tuple{args=As}) ->
Eas = encode_exprs(As),
[{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) ->
Eas = encode_exprs(As),
[{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) ->
encode_expr({id, _, N}) -> encode_name(N);
encode_expr({con, _, N}) -> encode_name(N);
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
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),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
#{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}) ->
list_to_binary(atom_to_list(Op)).
error({encode_expr_todo, Op}).
%% decode(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.
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ].
decode(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)).
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) ->
{encode_name(Fld), encode_expr(Val)}.
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
do_render_aci_json(Json) ->
Contracts =
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || #{contract := C} <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{name := Name,
type_defs := Ts0,
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_tdefs(Ts), decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
%% decode_func(#{name := init}) -> [];
decode_func(#{name := Name, arguments := As, returns := T}) ->
[" function", " ", io_lib:format("~s", [Name]), " : ",
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
decode_arg(#{type := T}) -> decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
decode_type(#{<<"tuple">> := Ets}) ->
decode_type(#{tuple := Ets}) ->
Ts = decode_types(Ets),
[$(,lists:join(",", Ts),$)];
decode_type(#{<<"record">> := Efs}) ->
decode_type(#{record := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(",", Fs),$}];
decode_type(#{<<"list">> := [Et]}) ->
decode_type(#{list := [Et]}) ->
T = decode_type(Et),
["list",$(,T,$)];
decode_type(#{<<"map">> := Ets}) ->
decode_type(#{map := Ets}) ->
Ts = decode_types(Ets),
["map",$(,lists:join(",", Ts),$)];
decode_type(#{<<"variant">> := Ets}) ->
decode_type(#{bytes := Len}) ->
["bytes(", integer_to_list(Len), ")"];
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) ->
[decode_type(#{tuple => Args}), " => ", decode_type(R)];
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec),
Ts = decode_types(Ets),
[C,$(,lists:join(",", Ts),$)];
AppName = decode_name(Ec),
AppArgs = decode_types(Ets),
case AppArgs of
[] -> [AppName];
_ -> [AppName,$(,lists:join(", ", AppArgs),$)]
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) ->
binary_to_list(En).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En);
decode_name(En) when is_binary(En) -> binary_to_list(En).
decode_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ].
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
decode_field(#{name := En, type := Et}) ->
Name = decode_name(En),
Type = decode_type(Et),
[Name," : ",Type].
@@ -298,39 +285,41 @@ decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
%% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
" = ",decode_type(T),$\n].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) ->
TypeDef = decode_type(T),
DefType = decode_deftype(T),
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_deftype(#{record := _Efs}) -> "record";
decode_deftype(#{variant := _}) -> "datatype";
decode_deftype(_T) -> "type".
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)].
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]).
%% #contract{Ann, Con, [Declarations]}.
contract_name(#contract{con=#con{name=N}}) -> N.
contract_name({contract, _, {con, _, Name}, _}) -> Name;
contract_name({namespace, _, {con, _, Name}, _}) -> Name.
contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, letfun) ].
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_fun(D)].
contract_types(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, type_def) ].
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_type(D) ].
%% To keep dialyzer happy and quiet.
%% 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) ].
is_fun({letfun, _, _, _, _, _}) -> true;
is_fun({fun_decl, _, _, _}) -> true;
is_fun(_) -> false.
is_type({type_def, _, _, _, _}) -> true;
is_type(_) -> false.
sort_decls(Ds) ->
Sort = fun (D1, D2) ->
@@ -339,58 +328,12 @@ sort_decls(Ds) ->
end,
lists:sort(Sort, Ds).
%% #letfun{Ann, Id, [Arg], Type, Typedef}.
function_name(#letfun{id=#id{name=N}}) -> N.
is_private(Node) -> aeso_syntax:get_ann(private, Node, false).
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
function_args(#letfun{args=Args}) -> Args.
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name.
function_type(#letfun{type=Type}) -> Type.
typedef_vars({type_def, _, _, Vars, _}) -> Vars.
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]).
typedef_def({type_def, _, _, _, Def}) -> Def.
+162 -61
View File
@@ -98,11 +98,14 @@
-type scope() :: #scope{}.
-record(env,
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname()
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname()
, in_pattern = false :: boolean()
, stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id()
}).
-type env() :: #env{}.
@@ -196,7 +199,7 @@ bind_state(Env) ->
false -> {id, Ann, "event"} %% will cause type error if used(?)
end,
Env1 = bind_funs([{"state", State},
{"put", {fun_t, Ann, [], [State], Unit}}], Env),
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
%% We bind Chain.event in a local 'Chain' namespace.
pop_scope(
@@ -356,11 +359,12 @@ global_env() ->
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
Fun1 = fun(S, T) -> Fun([S], T) end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"},
SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
SignDef = {bytes, Ann, <<0:64/unit:8>>},
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end,
TTL = {qid, Ann, ["Chain", "ttl"]},
Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
@@ -378,7 +382,7 @@ global_env() ->
%% Abort
{"abort", Fun1(String, A)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
{"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0},
@@ -389,7 +393,7 @@ global_env() ->
ChainScope = #scope
{ funs = MkDefs(
%% Spend transaction.
[{"spend", Fun([Address, Int], Unit)},
[{"spend", StateFun([Address, Int], Unit)},
%% Chain environment
{"balance", Fun1(Address, Int)},
{"block_hash", Fun1(Int, Int)},
@@ -403,7 +407,7 @@ global_env() ->
ContractScope = #scope
{ funs = MkDefs(
[{"address", Address},
%% {"owner", Int}, %% Not in EVM
{"creator", Address},
{"balance", Int}]) },
CallScope = #scope
@@ -419,11 +423,13 @@ global_env() ->
{ funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], 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
{ funs = MkDefs(
@@ -481,7 +487,9 @@ global_env() ->
%% Conversion
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 =
#{ [] => TopScope
@@ -506,7 +514,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) ->
infer(Contracts, []).
-type option() :: return_env.
-type option() :: return_env | dont_unfold.
-spec init_env(list(option())) -> env().
init_env(_Options) -> global_env().
@@ -518,7 +526,7 @@ infer(Contracts, Options) ->
Env = init_env(Options),
create_options(Options),
ets_new(type_vars, [set]),
{Env1, Decls} = infer1(Env, Contracts, []),
{Env1, Decls} = infer1(Env, Contracts, [], Options),
case proplists:get_value(return_env, Options, false) of
false -> Decls;
true -> {Env1, Decls}
@@ -527,21 +535,22 @@ infer(Contracts, Options) ->
clean_up_ets()
end.
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc) -> {Env, lists:reverse(Acc)};
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) ->
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}.
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options),
Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc]);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) ->
infer1(Env3, Rest, [Contract1 | Acc], Options);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
Namespace1 = {namespace, Ann, Name, Code1},
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]).
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options).
check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of
@@ -552,13 +561,16 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env)
end.
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0) ->
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, Options) ->
Defs = desugar(Defs0),
{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}.
case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Defs1};
false -> Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{Env2, unfold_record_types(Env2, Defs1)}
end.
%% TODO: revisit
infer_constant({letval, Attrs,_Pattern, Type, E}) ->
@@ -597,15 +609,17 @@ infer_contract(Env, What, Defs) ->
SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [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),
{Env4, TypeDefs ++ Decls ++ Defs1}.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env, Defs) ->
check_typedefs(Env = #env{ namespace = Ns }, Defs) ->
create_type_errors(),
GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end,
TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Ns, Def) end, TypeMap),
SCCs = aeso_utils:scc(DepGraph),
{Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []),
destroy_and_report_type_errors(Env),
@@ -728,25 +742,23 @@ check_event(Env, "event", Ann, Def) ->
check_event(_Env, _Name, _Ann, Def) -> Def.
check_event_con(Env, {constr_t, Ann, Con, Args}) ->
IsIndexed = fun(T) -> case aeso_syntax:get_ann(indexed, T, false) of
true -> indexed;
false -> notindexed
end end,
IsIndexed = fun(T) ->
T1 = unfold_types_in_type(Env, T),
%% `indexed` is optional but if used it has to be correctly used
case {is_word_type(T1), is_string_type(T1), aeso_syntax:get_ann(indexed, T, false)} of
{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),
Indexed = [ T || T <- Args, IsIndexed(T) == 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_1_string_values, Con}) || length(NonIndexed) > 1 ],
{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.
is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
@@ -793,7 +805,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
end;
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
Defs = [ maps:get(X, Funs) || X <- Xs ],
{TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
{TypeSigs, Defs1} = infer_letrec(Env, Defs),
Env1 = bind_funs(TypeSigs, Env),
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
@@ -835,7 +847,7 @@ check_special_funs(_, _) -> ok.
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
infer_letrec(Env, {letrec, Attrs, Defs}) ->
infer_letrec(Env, Defs) ->
create_constraints(),
Funs = [{Name, fresh_uvar(A)}
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
@@ -855,9 +867,12 @@ infer_letrec(Env, {letrec, Attrs, Defs}) ->
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
NewDefs = instantiate([D || {_, D} <- Inferred]),
[print_typesig(S) || S <- TypeSigs],
{TypeSigs, {letrec, Attrs, NewDefs}}.
{TypeSigs, NewDefs}.
infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
infer_letfun(Env0, {letfun, Attrib, Fun = {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],
ExpectedType = check_type(Env, arg_type(What)),
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
@@ -868,6 +883,13 @@ infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
{{Name, TypeSig},
{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_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
@@ -891,6 +913,7 @@ lookup_name(Env, As, Id, Options) ->
{Id, fresh_uvar(As)};
{QId, {_, Ty}} ->
Freshen = proplists:get_value(freshen, Options, false),
check_stateful(Env, Id, Ty),
Ty1 = case Ty of
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
_ when Freshen -> freshen_type(Ty);
@@ -899,6 +922,61 @@ lookup_name(Env, As, Id, Options) ->
{set_qname(QId, Id), Ty1}
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) ->
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
@@ -908,6 +986,8 @@ infer_expr(_Env, Body={bool, As, _}) ->
{typed, As, Body, {id, As, "bool"}};
infer_expr(_Env, Body={int, As, _}) ->
{typed, As, Body, {id, As, "int"}};
infer_expr(_Env, Body={char, As, _}) ->
{typed, As, Body, {id, As, "char"}};
infer_expr(_Env, Body={string, As, _}) ->
{typed, As, Body, {id, As, "string"}};
infer_expr(_Env, Body={bytes, As, Bin}) ->
@@ -935,8 +1015,6 @@ infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
{typed, As, QName, Type};
infer_expr(Env, {unit, As}) ->
infer_expr(Env, {tuple, As, []});
infer_expr(Env, {tuple, As, Cpts}) ->
NewCpts = [infer_expr(Env, C) || C <- Cpts],
CptTypes = [T || {typed, _, _, T} <- NewCpts],
@@ -990,7 +1068,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
constrain([ #record_create_constraint{
record_t = RecordType1,
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
context = Attrs } ] ++
context = Attrs } || not Env#env.in_pattern ] ++
[begin
[{proj, _, FieldName}] = LV,
#field_constraint{
@@ -1059,6 +1137,7 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
check_stateful_named_arg(Env, Id, E),
add_named_argument_constraint(
#named_argument_constraint{
args = NamedArgs,
@@ -1119,7 +1198,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
[] -> ok;
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
end,
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }),
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
NewBranch = check_expr(NewEnv, Branch, SwitchType),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
@@ -1128,12 +1207,11 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
infer_block(_Env, Attrs, [], BlockType) ->
error({impossible, empty_block, Attrs, BlockType});
infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
NewDef = infer_letfun(Env, Def),
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
NewDef = infer_letrec(Env, Def),
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
FunT = freshen_type(typesig_to_fun_t(TypeSig)),
NewE = bind_var({id, Ann, Name}, FunT, Env),
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
@@ -1178,6 +1256,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
free_vars({int, _, _}) ->
[];
free_vars({char, _, _}) ->
[];
free_vars({string, _, _}) ->
[];
free_vars({bool, _, _}) ->
@@ -1774,8 +1854,8 @@ instantiate(E) ->
instantiate1(dereference(E)).
instantiate1({uvar, Attr, R}) ->
Next = proplists:get_value(next, ets_lookup(type_vars, next), 1),
TVar = {tvar, Attr, "'" ++ integer_to_list(Next)},
Next = proplists:get_value(next, ets_lookup(type_vars, next), 0),
TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)},
ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]),
TVar;
instantiate1({fun_t, Ann, Named, Args, Ret}) ->
@@ -1795,6 +1875,12 @@ instantiate1([A|B]) ->
instantiate1(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.
cannot_unify(A, B, When) ->
@@ -1807,7 +1893,7 @@ create_type_errors() ->
ets_new(type_errors, [bag]).
destroy_and_report_type_errors(Env) ->
Errors = ets_tab2list(type_errors),
Errors = lists:reverse(ets_tab2list(type_errors)),
%% io:format("Type errors now: ~p\n", [Errors]),
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors),
@@ -1923,6 +2009,21 @@ pp_error({include, {string, Pos, Name}}) ->
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",
[Name, pp_loc(Pos)]);
pp_error({repeated_arg, Fun, Arg}) ->
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
[Arg, pp(Fun), pp_loc(Fun)]);
pp_error({stateful_not_allowed, Id, Fun}) ->
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp(Id), pp_loc(Id), pp(Fun)]);
pp_error({value_arg_not_allowed, Value, Fun}) ->
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
[if Which == put -> "write"; true -> "read" end,
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]);
pp_error(Err) ->
io_lib:format("Unknown error: ~p\n", [Err]).
File diff suppressed because it is too large Load Diff
+43 -23
View File
@@ -102,13 +102,6 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode);
contract_to_icode([{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([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode);
@@ -152,6 +145,7 @@ ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left;
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, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
@@ -179,8 +173,7 @@ ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
%% Abort
ast_body(?id_app("abort", [String], _, _), Icode) ->
#funcall{ function = #var_ref{ name = {builtin, abort} },
args = [ast_body(String, Icode)] };
builtin_call(abort, [ast_body(String, Icode)]);
%% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
@@ -224,6 +217,17 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
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(?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", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
@@ -365,13 +369,11 @@ ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
%% Strings
%% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
#funcall{ function = #var_ref{ name = {builtin, string_length} },
args = [ast_body(String, Icode)] };
builtin_call(string_length, [ast_body(String, Icode)]);
%% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
#funcall{ function = #var_ref{ name = {builtin, string_concat} },
args = [ast_body(String1, Icode), ast_body(String2, Icode)] };
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]);
%% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
@@ -410,6 +412,12 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
%% Other terms
ast_body({id, _, Name}, _Icode) ->
@@ -512,6 +520,8 @@ ast_body({switch,_,A,Cases}, Icode) ->
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
#switch{expr=ast_body(E, 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) ->
#tuple{cpts=[]};
ast_body({block,_,[E]}, Icode) ->
@@ -579,19 +589,30 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
_ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
string ->
OtherType ->
Neg = case Op of
'==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type})
end,
Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
args = [ast_body(A, Icode), ast_body(B, Icode)] });
_ -> gen_error({cant_compare, Ann, Op, Type})
Args = [ast_body(A, Icode), ast_body(B, Icode)],
Builtin =
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})
end;
_ ->
gen_error({cant_compare, Ann, Op, Type})
end,
Neg(Builtin)
end;
ast_binop('++', _, A, B, Icode) ->
#funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
builtin_call(list_concat, [ast_body(A, Icode), ast_body(B, Icode)]);
ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -697,7 +718,7 @@ ast_typerep({qid, _, Name}, Icode) ->
ast_typerep({con, _, _}, _) ->
word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) ->
{bytes, Len};
bytes_t(Len);
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode);
@@ -726,7 +747,8 @@ ttl_t(Icode) ->
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
sign_t() -> bytes_t(64).
bytes_t(Len) -> {bytes, Len}.
bytes_t(Len) when Len =< 32 -> word;
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
get_signature_arg(Args0) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
@@ -760,8 +782,6 @@ type_value({list, A}) ->
type_value({tuple, As}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG },
#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}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG },
#list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] };
+33 -24
View File
@@ -18,6 +18,7 @@
, to_sophia_value/4
, to_sophia_value/5
, decode_calldata/3
, parse/2
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -75,23 +76,14 @@ file(File, Options) ->
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try
#{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
from_string1(Backend, ContractString, Options)
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
@@ -104,6 +96,26 @@ from_string(ContractString, Options) ->
%% General programming errors in the compiler just signal error.
end.
from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}};
from_string1(fate, ContractString, Options) ->
Ast = parse(ContractString, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
FCode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
{ok, aeso_fcode_to_fate:compile(FCode, Options)}.
-spec string_to_icode(string(), [option()]) -> map().
string_to_icode(ContractString, Options) ->
Ast = parse(ContractString, Options),
@@ -188,7 +200,7 @@ insert_call_function(Code, FunName, Args, Options) ->
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
@@ -264,12 +276,12 @@ translate_vm_value(word, {id, _, "address"}, N) -> address_l
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_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, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
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 ->
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
translate_vm_value({tuple, _}, {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) ->
@@ -400,13 +412,10 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
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(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
File diff suppressed because it is too large Load Diff
+2
View File
@@ -343,6 +343,8 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
%% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
-299
View File
@@ -1,299 +0,0 @@
%%%-------------------------------------------------------------------
%%% @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.
+17 -15
View File
@@ -87,7 +87,7 @@ constructors() ->
sep1(constructor(), tok('|')).
constructor() -> %% TODO: format for Con() vs Con
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
con_args() -> paren_list(con_arg()).
@@ -99,9 +99,7 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations -------------------------------------------------------
letdecl() ->
choice(
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
letdef() -> choice(valdef(), fundef()).
@@ -213,7 +211,7 @@ exprAtom() ->
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true}
, {bool, keyword(false), false}
, ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs))
, {list, [], bracket_list(Expr)}
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
@@ -256,14 +254,20 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []};
record(Fs) ->
case record_or_map(Fs) of
record -> {record, get_ann(hd(Fs)), Fs};
record ->
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 ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
({field, FAnn, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id));
({field, FAnn, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end,
{map, Ann, lists:map(KV, Fs)}
end.
@@ -441,7 +445,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
{'if', Ann, Cond, Then, Else};
build_if(Ann, Cond, Then, []) ->
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]);
@@ -457,7 +461,6 @@ fun_t(Domains, Type) ->
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
Type, Domains).
tuple_e(Ann, []) -> {unit, Ann};
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
@@ -478,7 +481,6 @@ parse_pattern({record, Ann, Fs}) ->
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
parse_pattern(E = {con, _, _}) -> E;
parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E;
@@ -495,11 +497,11 @@ return_error({no_file, L, C}, Err) ->
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> no_return().
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return().
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"),
+3 -7
View File
@@ -160,8 +160,7 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
text(atom_to_list(Mod));
(_) -> empty() end,
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
decl(D = {letrec, _, _}) -> letdecl("let", D).
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) ->
@@ -184,9 +183,7 @@ name({typed, _, Name, _}) -> name(Name).
letdecl(Let, {letval, _, F, T, E}) ->
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, 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 ] ])).
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E).
-spec args([aeso_syntax:arg()]) -> doc().
args(Args) ->
@@ -332,7 +329,6 @@ expr_p(_, {Type, _, Bin})
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
case C of
@@ -365,6 +361,7 @@ stmt_p({else, Else}) ->
-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}; %% Only in error messages
bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500};
@@ -436,7 +433,6 @@ statements(Stmts) ->
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
statement(S = {letrec, _, _}) -> letdecl("let", S);
statement(E) -> expr(E).
get_elifs(Expr) -> get_elifs(Expr, []).
+2 -2
View File
@@ -36,8 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"),
Rules =
+2 -4
View File
@@ -25,7 +25,7 @@
-type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -43,8 +43,7 @@
-type letbind()
:: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}
| {letrec, ann(), [letbind()]}.
| {letfun, ann(), id(), [arg()], type(), expr()}.
-type arg() :: {arg, ann(), id(), type()}.
@@ -75,7 +74,6 @@
| {contract_pubkey, binary()}
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {unit, ann()}
| {string, ann(), binary()}
| {char, ann(), integer()}.
+24 -24
View File
@@ -6,7 +6,7 @@
%%%-------------------------------------------------------------------
-module(aeso_syntax_utils).
-export([used_ids/1, used_types/1, used/1]).
-export([used_ids/1, used_types/2, used/1]).
-record(alg, {zero, plus, scoped}).
@@ -39,11 +39,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
@@ -55,7 +50,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
@@ -104,29 +98,35 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies
used_ids(E) ->
[ X || {term, [X]} <- used(E) ].
[ X || {{term, [X]}, _} <- used(E) ].
used_types(T) ->
[ X || {type, [X]} <- used(T) ].
used_types([Top] = _CurrentNS, T) ->
F = fun({{type, [X]}, _}) -> [X];
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg([entity()]).
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_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) ->
{Bound, Others} = lists:partition(IsBound, Ys),
Bound = [E || E <- maps:keys(Ys), IsBound(E)],
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
lists:umerge(Xs -- Bound1, Others)
maps:merge(Remove(Bound1, Xs), Others)
end,
#alg{ zero = []
, plus = fun lists:umerge/2
#alg{ zero = #{}
, plus = fun maps:merge/2
, scoped = Scoped }.
-spec used(_) -> [entity()].
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
@@ -134,14 +134,14 @@ used(D) ->
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
fold(entity_alg(),
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> []
end, decl, D),
maps:to_list(fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Contract Language for aeternity"},
{vsn, "2.1.0"},
{vsn, "3.1.0"},
{registered, []},
{applications,
[kernel,
-78
View File
@@ -1,78 +0,0 @@
-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]).
+3 -2
View File
@@ -1,7 +1,7 @@
-module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile([export_all, nowarn_export_all]).
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123).
@@ -62,6 +62,7 @@ encode_decode_sophia_test() ->
Other -> Other
end end,
ok = Check("int", "42"),
ok = Check("int", "-42"),
ok = Check("bool", "true"),
ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""),
@@ -160,7 +161,7 @@ oracle_test() ->
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" function haxx(o : oracle(list(string), option(int))) =\n"
" stateful function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
+92 -46
View File
@@ -2,31 +2,30 @@
-include_lib("eunit/include/eunit.hrl").
do_test() ->
test_contract(1),
test_contract(2),
test_contract(3).
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N),
fun() -> test_contract(N) end}
|| N <- [1, 2, 3]].
test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:encode(Contract),
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual(DecACI, aeso_aci:decode(JSON)).
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
test_cases(1) ->
Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> => [],
<<"functions">> =>
[#{<<"name">> => <<"a">>,
<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
MapACI = #{contract =>
#{name => <<"C">>,
type_defs => [],
functions =>
[#{name => <<"a">>,
arguments =>
[#{name => <<"i">>,
type => <<"int">>}],
returns => <<"int">>,
stateful => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
{Contract,MapACI,DecACI};
@@ -35,42 +34,89 @@ test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" function a(i : allan) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"allan">>,
<<"typedef">> => <<"int">>,
<<"vars">> => []}],
<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"name">> => <<"a">>,
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
MapACI = #{contract =>
#{name => <<"C">>,
type_defs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
functions =>
[#{arguments =>
[#{name => <<"i">>,
type => <<"C.allan">>}],
name => <<"a">>,
returns => <<"int">>,
stateful => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
" type allan = int\n"
" function a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
" type state = ()\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" function a(i : bert(string)) = 1\n">>,
MapACI = #{<<"contract">> =>
#{<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> =>
[#{<<"C.bert">> => [<<"string">>]}]}],
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
<<"stateful">> => false}],
<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"bert">>,
<<"typedef">> =>
#{<<"variant">> =>
MapACI = #{contract =>
#{functions =>
[#{arguments =>
[#{name => <<"i">>,
type =>
#{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>,
stateful => false}],
name => <<"C">>,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => #{tuple => []},
type_defs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]},
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
" type state = ()\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" function a : (C.bert(string)) => int\n">>,
{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.
+74 -35
View File
@@ -16,20 +16,24 @@
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract",
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
fun() ->
case compile(ContractName) of
case compile(Backend, ContractName) of
#{byte_code := ByteCode,
contract_source := _,
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
type_info := _} when Backend == aevm ->
?assertMatch(Code when is_binary(Code), ByteCode);
Code when Backend == fate, is_tuple(Code) ->
?assertMatch(#{}, aeb_fate_code:functions(Code));
ErrBin ->
io:format("\n~s", [ErrBin]),
error(ErrBin)
end
end} || ContractName <- compilable_contracts() ] ++
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
case compile(ContractName) of
case compile(aevm, ContractName) of
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
@@ -44,14 +48,14 @@ simple_compile_test_() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile("include"),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination",
fun() ->
#{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile("deadcode"),
#{ byte_code := NoDeadCode } = compile(aevm, "nodeadcode"),
#{ byte_code := DeadCode } = compile(aevm, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
@@ -67,12 +71,12 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
compile(Name) ->
compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Name, Options) ->
compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of
{ok, Map} -> Map;
{error, ErrorString} -> ErrorString
end.
@@ -86,6 +90,7 @@ compilable_contracts() ->
"dutch_auction",
"environment",
"factorial",
"functions",
"fundme",
"identity",
"maps",
@@ -106,9 +111,21 @@ compilable_contracts() ->
"include",
"basic_auth",
"bitcoin_auth",
"address_literals"
"address_literals",
"bytes_equality",
"address_chain"
].
not_yet_compilable(fate) ->
["oracles", %% Oracle.register
"events", %% events
"basic_auth", %% auth_tx_hash instruction
"bitcoin_auth", %% auth_tx_hash instruction
"address_literals", %% oracle_query_id literals
"address_chain" %% Oracle.check_query
];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors
failing_contracts() ->
@@ -188,6 +205,8 @@ failing_contracts() ->
" - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 12).">>,
<<"Repeated argument y to function repeated_arg (at line 44, column 12).">>,
<<"No record type with fields y, z (at line 14, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
@@ -202,20 +221,15 @@ failing_contracts() ->
, {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events",
[<<"The payload type int (at line 10, column 30) should be string">>,
<<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
[<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]}
, {"bad_events2",
[<<"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 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">>]}
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]}
, {"type_clash",
[<<"Cannot unify int\n"
" and string\n"
@@ -242,46 +256,46 @@ failing_contracts() ->
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" address">>,
<<"Cannot unify oracle_query('1, '2)\n"
<<"Cannot unify oracle_query('a, 'b)\n"
" and Remote\n"
"when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('1, '2)\n"
" oracle_query('a, 'b)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle_query('3, '4)\n"
<<"Cannot unify oracle_query('c, 'd)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('3, '4)\n"
" oracle_query('c, 'd)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle_query('5, '6)\n"
<<"Cannot unify oracle_query('e, 'f)\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('5, '6)\n"
" oracle_query('e, 'f)\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<"Cannot unify oracle('7, '8)\n"
<<"Cannot unify oracle('g, 'h)\n"
" and Remote\n"
"when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('7, '8)\n"
" oracle('g, 'h)\n"
"against the expected type\n"
" Remote">>,
<<"Cannot unify oracle('9, '10)\n"
<<"Cannot unify oracle('i, 'j)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('9, '10)\n"
" oracle('i, 'j)\n"
"against the expected type\n"
" bytes(32)">>,
<<"Cannot unify oracle('11, '12)\n"
<<"Cannot unify oracle('k, 'l)\n"
" and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('11, '12)\n"
" oracle('k, 'l)\n"
"against the expected type\n"
" oracle_query(int, bool)">>,
<<"Cannot unify address\n"
@@ -302,4 +316,29 @@ failing_contracts() ->
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" bytes(32)">>]}
, {"stateful",
[<<"Cannot reference stateful function Chain.spend (at line 13, column 33)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 33)\nin the definition of non-stateful function fail2.">>,
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<"Cannot reference stateful function Chain.spend (at line 35, column 53)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 55)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 54)\nin the definition of non-stateful function fail7.">>,
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
, {"bad_init_state_access",
[<<"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n"
" - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 36), which calls\n"
" - put (at line 7, column 37)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 27)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>]}
, {"field_parse_error",
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
"Cannot use nested fields or keys in record construction: p.x\n">>]}
].
+1 -1
View File
@@ -62,7 +62,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ]
}.
parse_contract(Name) ->
+1 -1
View File
@@ -41,7 +41,7 @@ all_tokens() ->
%% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
lists:map(Lit, [contract, type, 'let', switch]) ++
%% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++
+33
View File
@@ -0,0 +1,33 @@
contract Remote =
function main : (int) => ()
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
function is_o(a : address) =
Address.is_oracle(a)
function is_c(a : address) =
Address.is_contract(a)
// function get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// function get_c(a : address) : option(Remote) =
// Address.get_contract(a)
function check_o(o : o_type) =
Oracle.check(o)
function check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// function h_to_i(h : hash) : int =
// Hash.to_int(h)
// function a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
function c_creator() : address =
Contract.creator
-5
View File
@@ -36,11 +36,6 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(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 b = false
let qcon = Mod.Con
+2 -4
View File
@@ -6,10 +6,8 @@ contract Events =
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string, string)
| BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
| BadEvent1(indexed string)
| BadEvent2(indexed alias_string)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
-1
View File
@@ -8,7 +8,6 @@ contract Events =
| Event2(alias_string, indexed alias_address)
| BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
+13
View File
@@ -0,0 +1,13 @@
contract BadInit =
type state = int
function new_state(n) = state + n
stateful function roundabout(n) = put(n)
stateful function set_state(n) = roundabout(n)
stateful function init() =
set_state(4)
new_state(0)
state + state
+1 -1
View File
@@ -4,7 +4,7 @@ contract BasicAuth =
function init() = { nonce = 1, owner = Call.caller }
function authorize(n : int, s : signature) : bool =
stateful function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
+1 -1
View File
@@ -3,7 +3,7 @@ contract BitcoinAuth =
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
function authorize(n : int, s : signature) : bool =
stateful function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
+18
View File
@@ -0,0 +1,18 @@
contract BytesEquality =
function eq16(a : bytes(16), b) = a == b
function ne16(a : bytes(16), b) = a != b
function eq32(a : bytes(32), b) = a == b
function ne32(a : bytes(32), b) = a != b
function eq47(a : bytes(47), b) = a == b
function ne47(a : bytes(47), b) = a != b
function eq64(a : bytes(64), b) = a == b
function ne64(a : bytes(64), b) = a != b
function eq65(a : bytes(65), b) = a == b
function ne65(a : bytes(65), b) = a != b
+1 -1
View File
@@ -5,5 +5,5 @@ contract Counter =
function init(val) = { value = val }
function get() = state.value
function tick() = put(state{ value = state.value + 1 })
stateful function tick() = put(state{ value = state.value + 1 })
+1 -1
View File
@@ -10,7 +10,7 @@ contract DutchAuction =
sold : bool }
// Add to work around current lack of predefined functions
private function spend(to, amount) =
private stateful function spend(to, amount) =
let total = Contract.balance
Chain.spend(to, amount)
total - amount
+2 -2
View File
@@ -12,7 +12,7 @@ contract Environment =
function init(remote) = {remote = remote}
function set_remote(remote) = put({remote = remote})
stateful function set_remote(remote) = put({remote = remote})
// -- Information about the this contract ---
@@ -38,7 +38,7 @@ contract Environment =
// Value
function call_value() : int = Call.value
function nested_value(value : int) : int =
stateful function nested_value(value : int) : int =
state.remote.call_value(value = value / 2)
// Gas price
+1 -1
View File
@@ -9,7 +9,7 @@ contract Factorial =
function init(worker) = {worker = worker}
function set_worker(worker) = put(state{worker = worker})
stateful function set_worker(worker) = put(state{worker = worker})
function fac(x : int) : int =
if(x == 0) 1
+5
View File
@@ -0,0 +1,5 @@
contract Fail =
record pt = {x : int, y : int}
record r = {p : pt}
function fail() = {p.x = 0, p.y = 0}
+15
View File
@@ -0,0 +1,15 @@
contract Functions =
private function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
private function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
private function map'() = map
private function plus(x, y) = x + y
function test1(xs : list(int)) = map(curry(plus)(5), xs)
function test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
function test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+1 -1
View File
@@ -15,7 +15,7 @@ contract FundMe =
private function require(b : bool, err : string) =
if(!b) abort(err)
private function spend(args : spend_args) =
private stateful function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount)
public function init(beneficiary, deadline, goal) : state =
+12 -12
View File
@@ -17,8 +17,8 @@ contract Maps =
{ ["one"] = {x = 1, y = 2},
["two"] = {x = 3, y = 4},
["three"] = {x = 5, y = 6} }
function map_state_i() = put(state{ map_i = map_i() })
function map_state_s() = put(state{ map_s = map_s() })
stateful function map_state_i() = put(state{ map_i = map_i() })
stateful function map_state_s() = put(state{ map_s = map_s() })
// m[k]
function get_i(k, m : map(int, pt)) = m[k]
@@ -35,20 +35,20 @@ contract Maps =
// m{[k] = v}
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
stateful function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
// m{f[k].x = v}
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
function setx_state_s(k, x) = put(state{ map_s[k].x = x })
stateful function setx_state_i(k, x) = put(state{ map_i[k].x = x })
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x })
// m{[k] @ x = v }
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
stateful function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
// m{[k = def] @ x = v }
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
@@ -77,8 +77,8 @@ contract Maps =
// Map.delete
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
stateful function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
// Map.size
function size_i(m : map(int, pt)) = Map.size(m)
@@ -95,6 +95,6 @@ contract Maps =
// Map.from_list
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
+13 -13
View File
@@ -9,22 +9,22 @@ contract Oracles =
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
function registerOracle(acct : address,
stateful function registerOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl)
function registerIntIntOracle(acct : address,
stateful function registerIntIntOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle(int, int) =
Oracle.register(acct, qfee, ttl)
function registerStringStringOracle(acct : address,
stateful function registerStringStringOracle(acct : address,
qfee : fee,
ttl : ttl) : oracle(string, string) =
Oracle.register(acct, qfee, ttl)
function signedRegisterOracle(acct : address,
stateful function signedRegisterOracle(acct : address,
sign : signature,
qfee : fee,
ttl : ttl) : oracle_id =
@@ -33,7 +33,7 @@ contract Oracles =
function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o)
function createQuery(o : oracle_id,
stateful function createQuery(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -42,7 +42,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production!
function unsafeCreateQuery(o : oracle_id,
stateful function unsafeCreateQuery(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -50,7 +50,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production!
function unsafeCreateQueryThenErr(o : oracle_id,
stateful function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t,
qfee : fee,
qttl : ttl,
@@ -59,21 +59,21 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error")
res
function extendOracle(o : oracle_id,
stateful function extendOracle(o : oracle_id,
ttl : ttl) : () =
Oracle.extend(o, ttl)
function signedExtendOracle(o : oracle_id,
stateful function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address
ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl)
function respond(o : oracle_id,
stateful function respond(o : oracle_id,
q : query_id,
r : answer_t) : () =
Oracle.respond(o, q, r)
function signedRespond(o : oracle_id,
stateful function signedRespond(o : oracle_id,
q : query_id,
sign : signature,
r : answer_t) : () =
@@ -96,13 +96,13 @@ contract Oracles =
datatype complexQuestion = Why(int) | How(string)
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
function complexOracle(question) =
stateful function complexOracle(question) =
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337))
Oracle.get_answer(o, q)
function signedComplexOracle(question, sig) =
stateful function signedComplexOracle(question, sig) =
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))
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
+1 -1
View File
@@ -11,7 +11,7 @@ contract Remote3 =
contract RemoteCall =
function call(r : Remote1, x : int) : int =
stateful function call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x)
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
-99
View File
@@ -1,99 +0,0 @@
contract Oracles =
function registerOracle :
(address,
int,
Chain.ttl) => oracle(string, int)
function createQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function unsafeCreateQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function respond :
(oracle(string, int),
oracle_query(string, int),
int) => ()
contract OraclesErr =
function unsafeCreateQueryThenErr :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
contract RemoteOracles =
public function callRegisterOracle(
r : Oracles,
acct : address,
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
r.registerOracle(acct, qfee, ttl)
public function callCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
require(value =< Call.value, "insufficient value")
r.createQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryThenErr(
r : OraclesErr,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryAndThenErr(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
public function callRespond(
r : Oracles,
o : oracle(string, int),
q : oracle_query(string, int),
qr : int) =
r.respond(o, q, qr)
private function require(b : bool, err : string) =
if(!b) abort(err)
+1 -1
View File
@@ -24,5 +24,5 @@ contract SimpleStorage =
function get() : int = state.data
function set(value : int) =
stateful function set(value : int) =
put(state{data = value})
+4 -4
View File
@@ -4,19 +4,19 @@ contract SpendContract =
contract SpendTest =
function spend(to, amount) =
stateful function spend(to, amount) =
let total = Contract.balance
Chain.spend(to, amount)
total - amount
function withdraw(amount) : int =
stateful function withdraw(amount) : int =
spend(Call.caller, amount)
function withdraw_from(account, amount) =
stateful function withdraw_from(account, amount) =
account.withdraw(amount)
withdraw(amount)
function spend_from(from, to, amount) =
stateful function spend_from(from, to, amount) =
from.withdraw(amount)
Chain.spend(to, amount)
Chain.balance(to)
+9 -9
View File
@@ -27,13 +27,13 @@ contract StateHandling =
function read_s() = state.s
function read_m() = state.m
function update(new_state : state) = put(new_state)
function update_i(new_i) = put(state{ i = new_i })
function update_s(new_s) = put(state{ s = new_s })
function update_m(new_m) = put(state{ m = new_m })
stateful function update(new_state : state) = put(new_state)
stateful function update_i(new_i) = put(state{ i = new_i })
stateful function update_s(new_s) = put(state{ s = new_s })
stateful function update_m(new_m) = put(state{ m = new_m })
function pass_it(r : Remote) = r.look_at(state)
function nop(r : Remote) = put(state{ i = state.i })
stateful function nop(r : Remote) = put(state{ i = state.i })
function return_it_s(r : Remote, big : bool) =
let x = r.return_s(big)
String.length(x)
@@ -50,10 +50,10 @@ contract StateHandling =
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
stateful function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
stateful function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
stateful function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
// remote called
function look_at(s : state) = ()
+54
View File
@@ -0,0 +1,54 @@
contract Remote =
stateful function remote_spend : (address, int) => ()
function remote_pure : int => int
contract Stateful =
private function pure(x) = x + 1
private stateful function local_spend(a) =
Chain.spend(a, 1000)
// Non-stateful functions cannot mention stateful functions
function fail1(a : address) = Chain.spend(a, 1000)
function fail2(a : address) = local_spend(a)
function fail3(a : address) =
let foo = Chain.spend
foo(a, 1000)
// Private functions must also be annotated
private function fail4(a) = Chain.spend(a, 1000)
// If annotated, stateful functions are allowed
stateful function ok1(a : address) = Chain.spend(a, 1000)
// And pure functions are always allowed
stateful function ok2(a : address) = pure(5)
stateful function ok3(a : address) =
let foo = pure
foo(5)
// No error here (fail4 is annotated as not stateful)
function ok4(a : address) = fail4(a)
// Lamdbas are checked at the construction site
private function fail5() : address => () = (a) => Chain.spend(a, 1000)
// .. so you can pass a stateful lambda to a non-stateful higher-order
// function:
private function apply(f : 'a => 'b, x) = f(x)
stateful function ok5(a : address) =
apply((val) => Chain.spend(a, val), 1000)
// It doesn't matter if remote calls are stateful or not
function ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
function ok7(r : Remote) = r.remote_pure(5)
// But you can't send any tokens if not stateful
function fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
function fail7(r : Remote) = r.remote_pure(value = 1000, 5)
function fail8(r : Remote) =
let foo = r.remote_pure
foo(value = 1000, 5)
function ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
+2
View File
@@ -40,3 +40,5 @@ contract Test =
function type_error(r, x) =
set_x(set_x(x, r), x)
function repeated_arg(x : int, y, x : string, y : bool) : string = x
+3 -3
View File
@@ -11,11 +11,11 @@ contract VariantTypes =
function require(b) = if(!b) abort("required")
function start(bal : int) =
stateful function start(bal : int) =
switch(state)
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
function stop() =
stateful function stop() =
switch(state)
Started(st) =>
require(Call.caller == st.owner)
@@ -23,7 +23,7 @@ contract VariantTypes =
st.balance
function get_color() = switch(state) Started(st) => st.color
function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
function get_state() = state
-56
View File
@@ -1,56 +0,0 @@
/* 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) }