Compare commits

..

9 Commits

Author SHA1 Message Date
Robert Virding d16fb82e25 Break out state and event from typedefs and update docs 2019-05-08 16:06:58 +02:00
Robert Virding d2cd97def7 Add handling of creating/updating maps and records, definitely WIP 2019-04-29 00:56:31 +02:00
Robert Virding 5455d0fcd7 Fixed a type error and test, definitely WIP 2019-04-25 12:19:49 +02:00
Robert Virding 2d3e6ab6e0 Refactor internal code and more add statements, definitely WIP 2019-04-25 11:56:21 +02:00
Robert Virding 70a0f77793 Replace hash with bytes, definitely WIP 2019-04-23 11:56:54 +02:00
Robert Virding 04b3227317 Update documentation, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding d9be8b2fca Saving even more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding a38afe7693 Saving more stuff, definitely WIP 2019-04-23 11:56:08 +02:00
Robert Virding 5719730d8c Saving stuff, definitely WIP 2019-04-23 11:56:08 +02:00
113 changed files with 2152 additions and 6403 deletions
+1 -3
View File
@@ -1,5 +1,5 @@
.rebar3 .rebar3
_[^_]* _*
.eunit .eunit
*.o *.o
*.beam *.beam
@@ -19,5 +19,3 @@ rebar3.crashdump
*.erl~ *.erl~
*.aes~ *.aes~
aesophia aesophia
.qcci
current_counterexample.eqc
+1 -106
View File
@@ -6,110 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added the `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
### Changed ### Changed
### Removed ### Removed
## [4.0.0-rc1] - 2019-08-22
### Added
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
for.
- New builtin functions `Crypto.ecrecover_secp256k1: (hash, bytes(65)) => option(bytes(20))`
and `Crypto.ecverify_secp256k1 : (hash, bytes(20), bytes(65)) => bool` for recovering
and verifying an Ethereum address for a message hash and a signature.
- Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
Example syntax:
```
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
// yields [12,13,14,20,21,22,30,31,32]
```
- A new contract, and endpoint, modifier `payable` is introduced. Contracts, and enpoints,
that shall be able to receive funds should be marked as payable. `Address.is_payable(a)`
can be used to check if an (contract) address is payable or not.
### Changed
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and
regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`.
Parens are not necessary. Note it only affects the types, values remain as their were before,
so `(1, "a") : int * string`
- The `AENS.transfer` and `AENS.revoke` functions have been updated to take a name `string`
instead of a name `hash`.
- Fixed a bug where the `AEVM` backend complained about a missing `init` function when
trying to generate calldata from an ACI-generated interface.
- Compiler now returns the ABI-version in the compiler result map.
- Renamed `Crypto.ecverify` and `Crypto.ecverify_secp256k1` into `Crypto.verify_sig` and
`Crypto.verify_sig_secp256k1` respectively.
### Removed
## [3.2.0] - 2019-06-28
### Added
- New builtin function `require : (bool, string) => ()`. Defined as
```
function require(b, err) = if(!b) abort(err)
```
- New builtin functions
```
Bytes.to_str : bytes(_) => string
Bytes.to_int : bytes(_) => int
```
for converting a byte array to a hex string and interpreting it as a
big-endian encoded integer respectively.
### Changed
- Public contract functions must now be declared as *entrypoints*:
```
contract Example =
// Exported
entrypoint exported_fun(x) = local_fun(x)
// Not exported
function local_fun(x) = x
```
Functions in namespaces still use `function` (and `private function` for
private functions).
- The return type of `Chain.block_hash(height)` has changed, it used to
be `int`, where `0` denoted an incorrect height. New return type is
`option(hash)`, where `None` represents an incorrect height.
- Event name hashes now use BLAKE2b instead of Keccak256.
- Fixed bugs when defining record types in namespaces.
- Fixed a bug in include path handling when passing options to the compiler.
### Removed
## [3.1.0] - 2019-06-03
### Added
### Changed
- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`,
...) event arguments.
- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`.
- ACI is restructured and improved:
- `state` and `event` types (if present) now appear at the top level.
- Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are `contract_interface`
and `render_aci_json`.
- Fixed a bug in `create_calldata`/`to_sophia_value` - it can now handle negative
literals.
### Removed
## [3.0.0] - 2019-05-21
### Added
- `stateful` annotations are now properly enforced. Functions must be marked stateful
in order to update the state or spend tokens.
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
- `Crypto.ecverify_secp256k1` has been added.
### Changed
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
not working properly and has been fixed.
### Removed
- `let rec` has been removed from the language, it has never worked.
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
been completely removed from `aesophia`.
## [2.1.0] - 2019-04-11 ## [2.1.0] - 2019-04-11
### Added ### Added
- Stubs (not yet wired up) for compilation to FATE - Stubs (not yet wired up) for compilation to FATE
@@ -136,10 +35,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0 [2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0 [2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+139 -74
View File
@@ -30,14 +30,53 @@ generates the following JSON structure representing the contract interface:
``` json ``` json
{ {
"contract": { "contract": {
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"type_defs": [
{
"name": "answers",
"vars": [],
"typedef": {
"map": {
"key": "string",
"value": "int"
}
}
}
],
"functions": [ "functions": [
{ {
"arguments": [],
"name": "init", "name": "init",
"returns": "Answers.state", "arguments": [],
"returns": {
"record": [
{
"name": "a",
"type": {
"map": {
"key": "string",
"value": "int"
}
}
}
]
},
"stateful": true "stateful": true
}, },
{ {
"name": "new_answer",
"arguments": [ "arguments": [
{ {
"name": "q", "name": "q",
@@ -48,36 +87,14 @@ generates the following JSON structure representing the contract interface:
"type": "int" "type": "int"
} }
], ],
"name": "new_answer",
"returns": { "returns": {
"map": [ "map": {
"string", "key": "string",
"int" "value": "int"
] }
}, },
"stateful": false "stateful": false
} }
],
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": "Answers.answers"
}
]
},
"type_defs": [
{
"name": "answers",
"typedef": {
"map": [
"string",
"int"
]
},
"vars": []
}
] ]
} }
} }
@@ -87,70 +104,118 @@ When that encoding is decoded the following include definition is generated:
``` ```
contract Answers = contract Answers =
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int) function new_answer : (string, int) => map(string, int)
``` ```
### Types ### Types
```erlang ``` erlang
-type aci_type() :: json | string. contract_string() = string() | binary()
-type json() :: jsx:json_term(). json_string() = binary()
-type json_text() :: binary().
``` ```
### Exports ### Exports
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()} #### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Generate the JSON encoding of the interface to a contract. The type definitions Types
and non-private functions are included in the JSON string.
#### render\_aci\_json(json() | json\_text()) -> string(). ``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate a contract interface This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
that can be included in another contract.
#### encode_contract(ContractString, Options) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
Options = [option()]
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode_contract(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
#### encode_type(TypeAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a type from the AST of the type.
#### encode_arg(ArgAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a function argument from the AST of the argument.
#### encode_stmt(StmtAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of a statement from the AST of the statement.
#### encode_expr(ExprAST) -> JSONstring.
Types
``` erlang
JSONstring = json_string()
```
Generate the JSON encoding of an expression from the AST of the expression.
### Notes
The deprecated functions `aseo_aci:encode/2` and `aeso_aci:decode/1` are still available but should not be used.
### Example run ### Example run
This is an example of using the ACI generator from an Erlang shell. The file This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang ``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes"). 1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>} {ok,<<"contract Answers =\n\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful functio"...>>}
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract). 2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
{ok,[#{contract => {ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
#{functions => 3> file:write_file("aci_test.aci", Encoding).
[#{arguments => [],name => <<"init">>,
returns => <<"Answers.state">>,stateful => true},
#{arguments =>
[#{name => <<"q">>,type => <<"string">>},
#{name => <<"a">>,type => <<"int">>}],
name => <<"new_answer">>,
returns => #{<<"map">> => [<<"string">>,<<"int">>]},
stateful => false}],
name => <<"Answers">>,
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok ok
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI). 4> Decoded = aeso_aci:decode_contract(Encoding).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>} <<"contract Answers =\n function new_answer : (string, int) => map(string, int)\n">>
5> file:write_file("aci_test.include", InterfaceStub). 5> file:write_file("aci_test.include", Decoded).
ok ok
6> jsx:prettify(jsx:encode(JsonACI)). 6> jsx:prettify(Encoding).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>> <<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
``` ```
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description above. more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
-46
View File
@@ -1,46 +0,0 @@
namespace Func =
function id(x : 'a) : 'a = x
function const(x : 'a) : 'b => 'a = (y) => x
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
function comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c = (x) => f(g(x))
function pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c = (x) => g(f(x))
function rapply(x : 'a, f : 'a => 'b) : 'b = f(x)
/* The Z combinator - replacement for local and anonymous recursion.
*/
function recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res =
(x) => f(recur(f), x)
function iter(n : int, f : 'a => 'a) : 'a => 'a = iter_(n, f, (x) => x)
private function iter_(n : int, f : 'a => 'a, acc : 'a => 'a) : 'a => 'a =
if(n == 0) acc
elif(n == 1) comp(f, acc)
else iter_(n / 2, comp(f, f), if(n mod 2 == 0) acc else comp(f, acc))
function curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) =
(x) => (y) => f(x, y)
function curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) =
(x) => (y) => (z) => f(x, y, z)
function uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c =
(x, y) => f(x)(y)
function uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd =
(x, y, z) => f(x)(y)(z)
function tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c =
(t) => switch(t)
(x, y) => f(x, y)
function tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd =
(t) => switch(t)
(x, y, z) => f(x, y, z)
function untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c =
(x, y) => f((x, y))
function untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd =
(x, y, z) => f((x, y, z))
-202
View File
@@ -1,202 +0,0 @@
include "ListInternal.aes"
namespace List =
function is_empty(l : list('a)) : bool = switch(l)
[] => true
_ => false
function first(l : list('a)) : option('a) = switch(l)
[] => None
h::_ => Some(h)
function tail(l : list('a)) : option(list('a)) = switch(l)
[] => None
_::t => Some(t)
function last(l : list('a)) : option('a) = switch(l)
[] => None
[x] => Some(x)
_::t => last(t)
function find(p : 'a => bool, l : list('a)) : option('a) = switch(l)
[] => None
h::t => if(p(h)) Some(h) else find(p, t)
function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0, [])
private function find_indices_( p : 'a => bool
, l : list('a)
, n : int
, acc : list(int)
) : list(int) = switch(l)
[] => reverse(acc)
h::t => find_indices_(p, t, n+1, if(p(h)) n::acc else acc)
function nth(n : int, l : list('a)) : option('a) = switch(l)
[] => None
h::t => if(n == 0) Some(h) else nth(n-1, t)
/* Unsafe version of `nth` */
function get(n : int, l : list('a)) : 'a = switch(l)
[] => abort("Out of index get")
h::t => if(n == 0) h else get(n-1, t)
function length(l : list('a)) : int = length_(l, 0)
private function length_(l : list('a), acc : int) : int = switch(l)
[] => acc
_::t => length_(t, acc + 1)
/* Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow */
function replace_at(n : int, e : 'a, l : list('a)) : list('a) =
if(n<0) abort("insert_at underflow") else replace_at_(n, e, l, [])
private function replace_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
switch(l)
[] => abort("replace_at overflow")
h::t => if (n == 0) reverse(e::acc) ++ t
else replace_at_(n-1, e, t, h::acc)
/* Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow */
function insert_at(n : int, e : 'a, l : list('a)) : list('a) =
if(n<0) abort("insert_at underflow") else insert_at_(n, e, l, [])
private function insert_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
if (n == 0) reverse(e::acc) ++ l
else switch(l)
[] => abort("insert_at overflow")
h::t => insert_at_(n-1, e, t, h::acc)
function insert_by(f : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
switch(l)
[] => [x]
(e :: l') =>
if(f(x, e))
e :: insert_by(f, x, l')
else
x :: l
function foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b = switch(l)
[] => nil
h::t => cons(h, foldr(cons, nil, t))
function foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b = switch(l)
[] => acc
h::t => foldl(rcons, rcons(acc, h), t)
function foreach(f : 'a => unit, l : list('a)) : unit =
switch(l)
[] => ()
e :: l' =>
f(e)
foreach(f, l')
function reverse(l : list('a)) : list('a) = foldl((lst, el) => el :: lst, [], l)
function map(f : 'a => 'b, l : list('a)) : list('b) = map_(f, l, [])
private function map_(f : 'a => 'b, l : list('a), acc : list('b)) : list('b) = switch(l)
[] => reverse(acc)
h::t => map_(f, t, f(h)::acc)
function flat_map(f : 'a => list('b), l : list('a)) : list('b) =
ListInternal.flat_map(f, l)
function filter(p : 'a => bool, l : list('a)) : list('a) = filter_(p, l, [])
private function filter_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => filter_(p, t, if(p(h)) h::acc else acc)
/* Take `n` first elements */
function take(n : int, l : list('a)) : list('a) =
if(n < 0) abort("Take negative number of elements") else take_(n, l, [])
private function take_(n : int, l : list('a), acc : list('a)) : list('a) =
if(n == 0) reverse(acc)
else switch(l)
[] => reverse(acc)
h::t => take_(n-1, t, h::acc)
/* Drop `n` first elements */
function drop(n : int, l : list('a)) : list('a) =
if(n < 0) abort("Drop negative number of elements")
elif (n == 0) l
else switch(l)
[] => []
h::t => drop(n-1, t)
/* Get the longest prefix of a list in which every element matches predicate `p` */
function take_while(p : 'a => bool, l : list('a)) : list('a) = take_while_(p, l, [])
private function take_while_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => if(p(h)) take_while_(p, t, h::acc) else reverse(acc)
/* Drop elements from `l` until `p` holds */
function drop_while(p : 'a => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => if(p(h)) drop_while(p, t) else l
/* Splits list into two lists of elements that respectively match and don't match predicate `p` */
function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = partition_(p, l, [], [])
private function partition_( p : 'a => bool
, l : list('a)
, acc_t : list('a)
, acc_f : list('a)
) : (list('a) * list('a)) = switch(l)
[] => (reverse(acc_t), reverse(acc_f))
h::t => if(p(h)) partition_(p, t, h::acc_t, acc_f) else partition_(p, t, acc_t, h::acc_f)
function concats(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll)
function all(p : 'a => bool, l : list('a)) : bool = switch(l)
[] => true
h::t => if(p(h)) all(p, t) else false
function any(p : 'a => bool, l : list('a)) : bool = switch(l)
[] => false
h::t => if(p(h)) true else any(p, t)
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l)
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l)
/* Zips two list by applying bimapping function on respective elements. Drops longer tail. */
function zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) = zip_with_(f, l1, l2, [])
private function zip_with_( f : ('a, 'b) => 'c
, l1 : list('a)
, l2 : list('b)
, acc : list('c)
) : list('c) = switch ((l1, l2))
(h1::t1, h2::t2) => zip_with_(f, t1, t2, f(h1, h2)::acc)
_ => reverse(acc)
/* Zips two lists into list of pairs. Drops longer tail. */
function zip(l1 : list('a), l2 : list('b)) : list('a * 'b) = zip_with((a, b) => (a, b), l1, l2)
function unzip(l : list('a * 'b)) : list('a) * list('b) = unzip_(l, [], [])
private function unzip_( l : list('a * 'b)
, acc_l : list('a)
, acc_r : list('b)
) : (list('a) * list('b)) = switch(l)
[] => (reverse(acc_l), reverse(acc_r))
(left, right)::t => unzip_(t, left::acc_l, right::acc_r)
// TODO: Improve?
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => switch (partition((x) => lesser_cmp(x, h), t))
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, [])
private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
[e] => reverse(e::acc)
h::t => intersperse_(delim, t, delim::h::acc)
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0, [])
private function enumerate_(l : list('a), n : int, acc : list(int * 'a)) : list(int * 'a) = switch(l)
[] => reverse(acc)
h::t => enumerate_(t, n + 1, (n, h)::acc)
-16
View File
@@ -1,16 +0,0 @@
namespace ListInternal =
// -- Flatmap ----------------------------------------------------------------
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
switch(xs)
[] => []
x :: xs => f(x) ++ flat_map(f, xs)
// -- From..to ---------------------------------------------------------------
function from_to(a : int, b : int) : list(int) = from_to_(a, b, [])
private function from_to_(a, b, acc) =
if (a > b) acc else from_to_(a, b - 1, b :: acc)
-76
View File
@@ -1,76 +0,0 @@
include "List.aes"
namespace Option =
function is_none(o : option('a)) : bool = switch(o)
None => true
Some(_) => false
function is_some(o : option('a)) : bool = switch(o)
None => false
Some(_) => true
function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o)
None => n
Some(x) => s(x)
function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o)
function force(o : option('a)) : 'a = default(abort("Forced None value"), o)
function on_elem(f : 'a => unit, o : option('a)) : unit = match((), f, o)
function map(f : 'a => 'b, o : option('a)) : option('b) = switch(o)
None => None
Some(x) => Some(f(x))
function map2(f : ('a, 'b) => 'c
, o1 : option('a)
, o2 : option('b)
) : option('c) = switch((o1, o2))
(Some(x1), Some(x2)) => Some(f(x1, x2))
_ => None
function map3( f : ('a, 'b, 'c) => 'd
, o1 : option('a)
, o2 : option('b)
, o3 : option('c)
) : option('d) = switch((o1, o2, o3))
(Some(x1), Some(x2), Some(x3)) => Some(f(x1, x2, x3))
_ => None
function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o))
(Some(ff), Some(xx)) => Some(ff(xx))
_ => None
function flat_map(f : 'a => option('b), o : option('a)) : option('b) = switch(o)
None => None
Some(x) => f(x)
function to_list(o : option('a)) : list('a) = switch(o)
None => []
Some(x) => [x]
function filter_options(l : list(option('a))) : list('a) = filter_options_(l, [])
private function filter_options_(l : list (option('a)), acc : list('a)) : list('a) = switch(l)
[] => List.reverse(acc)
None::t => filter_options_(t, acc)
Some(x)::t => filter_options_(t, x::acc)
function seq_options(l : list (option('a))) : option (list('a)) = seq_options_(l, [])
private function seq_options_(l : list (option('a)), acc : list('a)) : option(list('a)) = switch(l)
[] => Some(List.reverse(acc))
None::t => None
Some(x)::t => seq_options_(t, x::acc)
function choose(o1 : option('a), o2 : option('a)) : option('a) =
if(is_some(o1)) o1 else o2
function choose_first(l : list(option('a))) : option('a) = switch(l)
[] => None
None::t => choose_first(t)
Some(x)::_ => Some(x)
-20
View File
@@ -1,20 +0,0 @@
namespace Pair =
function fst(t : ('a * 'b)) : 'a = switch(t)
(x, _) => x
function snd(t : ('a * 'b)) : 'b = switch(t)
(_, y) => y
function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t)
(x, y) => (f(x), y)
function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t)
(x, y) => (x, f(y))
function bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) = switch(t)
(x, y) => (f(x), g(y))
function swap(t : ('a * 'b)) : ('b * 'a) = switch(t)
(x, y) => (y, x)
-37
View File
@@ -1,37 +0,0 @@
namespace Triple =
function fst(t : ('a * 'b * 'c)) : 'a = switch(t)
(x, _, _) => x
function snd(t : ('a * 'b * 'c)) : 'b = switch(t)
(_, y, _) => y
function thd(t : ('a * 'b * 'c)) : 'c = switch(t)
(_, _, z) => z
function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t)
(x, y, z) => (f(x), y, z)
function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t)
(x, y, z) => (x, f(y), z)
function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t)
(x, y, z) => (x, y, f(z))
function trimap( f : 'a => 'x
, g : 'b => 'y
, h : 'c => 'z
, t : ('a * 'b * 'c)
) : ('x * 'y * 'z) = switch(t)
(x, y, z) => (f(x), g(y), h(z))
function swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) = switch(t)
(x, y, z) => (z, y, x)
function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t)
(x, y, z) => (z, x, y)
function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t)
(x, y, z) => (y, z, x)
+18 -3
View File
@@ -2,20 +2,35 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"3f85375"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "e8253b0"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}} {tag, "2.8.0"}}}
]}. ]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! \n"}.
{provider_hooks, [{post, [{compile, escriptize}]}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
escriptize,
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
{"win32",
escriptize,
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
]}.
{dialyzer, [ {dialyzer, [
{warnings, [unknown]}, {warnings, [unknown]},
{plt_apps, all_deps}, {plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "4.0.0-rc1"}, {relx, [{release, {aesophia, "2.1.0"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+3 -3
View File
@@ -1,17 +1,17 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"3f85375cb2288083e442541d5b53f9705f22053a"}}, {ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}}, {ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
1}, 1},
{<<"base58">>, {<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git", {git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}}, {ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2}, 2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, {<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>, {<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git", {git,"https://github.com/talentdeficit/jsx.git",
BIN
View File
Binary file not shown.
+467 -262
View File
@@ -9,70 +9,100 @@
-module(aeso_aci). -module(aeso_aci).
-export([ file/2 %% Old deprecated interface.
, file/3 -export([encode/1,encode/2,decode/1]).
, contract_interface/2
, contract_interface/3
, render_aci_json/1 -export([encode_contract/1,encode_contract/2,decode_contract/1]).
-export([encode_func/1,encode_type/1,encode_arg/1,
encode_stmt/1,encode_expr/1]).
, json_encode_expr/1 %% Define records for the various typed syntactic forms. These make
, json_encode_type/1]). %% the code easier but don't seem to exist elsewhere. Unfortunately
%% sometimes the same typename is used with different fields.
-type aci_type() :: json | string. %% Top-level
-type json() :: jsx:json_term(). -record(contract, {ann,con,decls}).
-type json_text() :: binary(). %% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
%% External API %% Types
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}. -record(app_t, {ann,id,fields}).
file(Type, File) -> -record(tuple_t, {ann,args}).
file(Type, File, []). -record(bytes_t, {ann,len}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
file(Type, File, Options0) -> %% Tokens
Options = aeso_compiler:add_include_path(File, Options0), -record(arg, {ann,id,type}).
case file:read_file(File) of -record(id, {ann,name}).
{ok, BinCode} -> -record(con, {ann,name}).
do_contract_interface(Type, binary_to_list(BinCode), Options); -record(qid, {ann,names}).
{error, _} = Err -> Err -record(qcon, {ann,names}).
end. -record(tvar, {ann,name}).
-spec contract_interface(aci_type(), string()) -> %% Statements
{ok, json() | string()} | {error, term()}. -record(block, {ann,body}).
contract_interface(Type, ContractString) -> -record('if', {ann,test,then,else}). %Both statement and expression
contract_interface(Type, ContractString, []). -record(letval, {ann,pat,type,exp}).
-record(switch, {ann,arg,cases}).
-record('case', {ann,pat,body}).
-spec contract_interface(aci_type(), string(), [term()]) -> %% Expressions
{ok, json() | string()} | {error, term()}. -record(bool, {ann,bool}).
contract_interface(Type, ContractString, CompilerOpts) -> -record(int, {ann,value}).
do_contract_interface(Type, ContractString, CompilerOpts). -record(string, {ann,bin}).
-record(bytes, {ann,bin}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(record, {ann,fields}). %Create a record
-record(field, {ann,name,value}). %A record field
-record(proj, {ann,value}). %?
-record(map, {ann,fields}). %Create a map
-record(map_get, {ann,field}).
-record(lam, {ann,args,body}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
-spec render_aci_json(json() | json_text()) -> {ok, binary()}. %% The old deprecated interface.
render_aci_json(Json) ->
do_render_aci_json(Json).
-spec json_encode_expr(aeso_syntax:expr()) -> json(). encode(C) -> encode_contract(C).
json_encode_expr(Expr) -> encode(C, Os) -> encode_contract(C, Os).
encode_expr(Expr). decode(J) -> decode_contract(J).
-spec json_encode_type(aeso_syntax:type()) -> json(). %% encode_contract(ContractString) -> {ok,JSON} | {error,String}.
json_encode_type(Type) -> %% encode_contract(ContractString, Options) -> {ok,JSON} | {error,String}.
encode_type(Type). %% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
%% Internal functions encode_contract(ContractString) ->
do_contract_interface(Type, Contract, Options) when is_binary(Contract) -> encode_contract(ContractString, []).
do_contract_interface(Type, binary_to_list(Contract), Options); encode_contract(ContractString, Options) when is_binary(ContractString) ->
do_contract_interface(Type, ContractString, Options) -> encode_contract(binary_to_list(ContractString), Options);
encode_contract(ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("Ast\n~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), %% aeso_ast:pp(Ast),
%% io:format("~p\n", [TypedAst]), TypedAst = aeso_ast_infer_types:infer(Ast, Options),
JArray = [ encode_contract(C) || C <- TypedAst ], %% io:format("Typed ast\n~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
case Type of %% We find and look at the last contract.
json -> {ok, JArray}; Contract = lists:last(TypedAst),
string -> do_render_aci_json(JArray) Cname = contract_name(Contract),
end Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>,
[{<<"name">>, do_encode_name(Cname)}] ++
Tdefs ++
[{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -89,265 +119,394 @@ join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")). list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> %% do_encode_contract_typedefs(TypeDefs) -> [JSON].
C0 = #{name => encode_name(Name)}, %% Return a list of typedefs and state and event if they occur.
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ], do_encode_contract_typedefs(Tdefs) ->
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end, Fun = fun(T, {Ts,Ss,Es}) ->
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0), %% Only one state and event.
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1), case typedef_name(T) of
"state" -> {Ts,[do_encode_state_typedef(T)],Es};
"event" -> {Ts,Ss,[do_encode_event_typedef(T)]};
_Name -> {Ts ++ [do_encode_typedef(T)],Ss,Es}
end
end,
{Ts,Ss,Es} = lists:foldl(Fun, {[],[],[]}, Tdefs),
Ss ++ [{<<"type_defs">>, Ts}] ++ Es.
C1 = C0#{type_defs => Tdefs}, %% do_encode_state_typedef(StateTdef) -> JSON.
%% do_encode_event_typedef(EventTdef) -> JSON.
C2 = case Es of do_encode_state_typedef(State) ->
[] -> C1; Def = typedef_def(State),
[#{typedef := ET}] -> C1#{event => ET} {<<"state">>,do_encode_alias(Def)}.
end,
C3 = case Ss of do_encode_event_typedef(State) ->
[] -> C2; Def = typedef_def(State),
[#{typedef := ST}] -> C2#{state => ST} {<<"event">>,do_encode_alias(Def)}.
end,
Fdefs = [ encode_function(F) %% encode_func(TypedAST) -> JSON.
|| F <- sort_decls(contract_funcs(Contract)), %% Encode a function AST into a JSON structure.
is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}}; encode_func(AST) ->
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> jsx:encode(do_encode_func(AST)).
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
%% do_encode_func(Function) -> JSONmap
%% Encode a function definition. Currently we are only interested in %% Encode a function definition. Currently we are only interested in
%% the interface and type. %% the interface and type.
encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name),
arguments => encode_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDef),
payable => is_payable(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),
payable => is_payable(FDecl)}.
encode_anon_args(Types) -> do_encode_func(Fdef) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], Name = function_name(Fdef),
[ #{name => V, type => encode_type(T)} Args = function_args(Fdef),
|| {V, T} <- lists:zip(Anons, Types) ]. Type = function_type(Fdef),
[{<<"name">>, do_encode_name(Name)},
{<<"arguments">>, do_encode_args(Args)},
{<<"returns">>, do_encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_args(Args) -> [ encode_arg(A) || A <- Args ]. %% encode_arg(TypedAST) -> JSON.
%% Encode an argument AST into a JSON structure.
encode_arg({arg, _, Id, T}) -> encode_arg(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_arg(AST)).
type => encode_type(T)}.
encode_typedef(Type) -> %% do_encode_args(ArgASTs) -> [JSONmap].
%% do_encode_arg(ArgAST) -> JSONmap.
do_encode_args(Args) ->
[ do_encode_arg(A) || A <- Args ].
do_encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% encode_type(TypedAST) -> JSON.
%% Encode a type AST into a JSON structure.
encode_type(AST) ->
jsx:encode(do_encode_type(AST)).
%% do_encode_types([TypeAST]) -> [JSONmap].
%% do_encode_type(TypeAST) -> JsonMap.
do_encode_types(Types) ->
[ do_encode_type(T) || T <- Types ].
do_encode_type(#tvar{name=N}) -> do_encode_name(N);
do_encode_type(#id{name=N}) -> do_encode_name(N);
do_encode_type(#con{name=N}) -> do_encode_name(N);
do_encode_type(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_type(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_type(#tuple_t{args=As}) ->
Eas = do_encode_types(As),
[{<<"tuple">>,Eas}];
do_encode_type(#bytes_t{len=Len}) ->
{<<"bytes">>,Len};
do_encode_type(#record_t{fields=Fs}) ->
Efs = do_encode_type_rec_fields(Fs),
[{<<"record">>,Efs}];
%% Special case lists and maps as they are built-in types.
do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
Ef = do_encode_type(F),
[{<<"list">>,Ef}];
do_encode_type(#app_t{id=#id{name="map"},fields=Fs}) ->
Ef = do_encode_type_mapo_field(Fs),
[{<<"map">>,Ef}];
%% Other applications.
do_encode_type(#app_t{id=Id,fields=Fs}) ->
Name = do_encode_type(Id),
Efs = do_encode_types(Fs),
[{Name,Efs}];
do_encode_type(#variant_t{cons=Cs}) ->
Ecs = do_encode_types(Cs),
[{<<"variant">>,Ecs}];
do_encode_type(#constr_t{con=C,args=As}) ->
Ec = do_encode_type(C),
Eas = do_encode_types(As),
[{Ec,Eas}];
do_encode_type(#fun_t{args=As,type=T}) ->
Eas = do_encode_types(As),
Et = do_encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
do_encode_name(Name) ->
list_to_binary(Name).
%% do_encode_type_rec_fields(Fields) -> [JSONmap].
%% do_encode_type_rec_field(Field) -> JSONmap.
%% Encode a record field type.
do_encode_type_rec_fields(Fs) ->
[ do_encode_type_rec_field(F) || F <- Fs ].
do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,do_encode_type(Id)},
{<<"type">>,do_encode_type(T)}].
%% do_encode_type_mapo_field(Field) -> JSONmap.
%% Two fields for one map type.
do_encode_type_mapo_field([K,V]) ->
[{<<"key">>,do_encode_type(K)},
{<<"value">>,do_encode_type(V)}].
%% do_encode_typedef(TypeDefAST) -> JSON.
do_encode_typedef(Type) ->
Name = typedef_name(Type), Name = typedef_name(Type),
Vars = typedef_vars(Type), Vars = typedef_vars(Type),
Def = typedef_def(Type), Def = typedef_def(Type),
#{name => encode_name(Name), [{<<"name">>, do_encode_name(Name)},
vars => encode_tvars(Vars), {<<"vars">>, do_encode_tvars(Vars)},
typedef => encode_type(Def)}. {<<"typedef">>, do_encode_alias(Def)}].
encode_tvars(Vars) -> do_encode_tvars(Vars) ->
[ #{name => encode_type(V)} || V <- Vars ]. [ do_encode_tvar(V) || V <- Vars ].
%% Encode type do_encode_tvar(#tvar{name=N}) ->
encode_type({tvar, _, N}) -> encode_name(N); [{<<"name">>, do_encode_name(N)}].
encode_type({id, _, N}) -> encode_name(N);
encode_type({con, _, N}) -> encode_name(N);
encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)};
encode_type({bytes_t, _, Len}) -> #{bytes => Len};
encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)};
encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)};
encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)};
encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
encode_type({alias_t, Type}) -> encode_type(Type);
encode_type({fun_t, _, _, As, T}) -> #{function =>
#{arguments => encode_types(As),
returns => encode_type(T)}}.
encode_types(Ts) -> [ encode_type(T) || T <- Ts ]. do_encode_alias(#alias_t{type=T}) ->
do_encode_type(T);
do_encode_alias(A) -> do_encode_type(A).
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ]. %% encode_stmt(StmtAST) -> JSON.
%% Encode a statement AST into a JSON structure.
encode_type_field({field_t, _, Id, T}) -> encode_stmt(AST) ->
#{name => encode_type(Id), jsx:encode(do_encode_stmt(AST)).
type => encode_type(T)}.
encode_name(Name) when is_list(Name) -> %% do_encode_stmt(StmtAST) -> JSONmap.
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
%% Encode Expr do_encode_stmt(#typed{expr=E}) -> %Ignore the type
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ]. do_encode_stmt(E);
do_encode_stmt(#block{body=Body}) ->
Eblock = [ do_encode_stmt(B) || B <- Body ],
[{<<"block">>,Eblock}];
do_encode_stmt(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_stmt(Then),
Eelse = do_encode_stmt(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_stmt(#letval{pat=Pat,exp=Exp}) ->
Epat = do_encode_expr(Pat),
Eexp = do_encode_expr(Exp),
[{<<"let">>,[{<<"pattern">>,Epat},{<<"expression">>,Eexp}]}];
do_encode_stmt(#switch{arg=Arg,cases=Cases}) ->
Earg = do_encode_expr(Arg),
Ecases = [ do_encode_stmt_case(Case) || Case <- Cases ],
[{<<"switch">>,[{<<"arg">>,Earg},{<<"cases">>,Ecases}]}];
do_encode_stmt(E) ->
do_encode_expr(E).
encode_expr({id, _, N}) -> encode_name(N); do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
encode_expr({con, _, N}) -> encode_name(N); Epat = do_encode_expr(Pat), %Patterns are expessions
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns)); Ebody = do_encode_stmt(Body),
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns)); [{<<"pattern">>,Epat},{<<"body">>,Ebody}].
encode_expr({typed, _, E}) -> encode_expr(E);
encode_expr({bool, _, B}) -> B;
encode_expr({int, _, V}) -> V;
encode_expr({string, _, S}) -> S;
encode_expr({tuple, _, As}) -> encode_exprs(As);
encode_expr({list, _, As}) -> encode_exprs(As);
encode_expr({bytes, _, B}) ->
Digits = byte_size(B),
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
#{Ef => Eas};
encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds));
encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ];
encode_expr({Op,_Ann}) ->
error({encode_expr_todo, Op}).
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ]. %% encode_expr(ExprAST) -> JSON.
%% Encode an expression AST into a JSON structure.
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) -> encode_expr(AST) ->
{encode_name(Fld), encode_expr(Val)}. jsx:encode(do_encode_expr(AST)).
do_render_aci_json(Json) -> %% do_encode_exprs(ExprASTs) -> [JSONmap].
Contracts = %% do_encode_expr(ExprAST) -> JSONmap.
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || C <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name, do_encode_exprs(Es) ->
payable := Payable, [ do_encode_expr(E) || E <- Es ].
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,
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. do_encode_expr(#typed{expr=E}) -> %Ignore the type
do_encode_expr(E);
do_encode_expr(#id{name=N}) -> do_encode_name(N);
do_encode_expr(#con{name=N}) -> do_encode_name(N);
do_encode_expr(#qid{names=Ns}) ->
do_encode_name(lists:join(".", Ns));
do_encode_expr(#qcon{names=Ns}) ->
do_encode_name(lists:join(".", Ns)); %?
do_encode_expr(#bool{bool=B}) -> B;
do_encode_expr(#int{value=V}) -> V;
do_encode_expr(#string{bin=B}) ->
[{<<"string">>,B}];
do_encode_expr(#bytes{bin=B}) -> B;
do_encode_expr(#tuple{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"tuple">>,Eas}];
do_encode_expr(#list{args=As}) ->
Eas = do_encode_exprs(As),
[{<<"list">>,Eas}];
do_encode_expr(#record{fields=Fs}) -> %Create a record
Efs = do_encode_expr_rec_fields(Fs),
[{<<"create_record">>,Efs}];
do_encode_expr({record,_Ann,Rec,Fs}) -> %Update a record
Erec = do_encode_expr(Rec),
Efs = do_encode_expr_rec_fields(Fs),
[{<<"update_record">>,[Erec,Efs]}];
do_encode_expr(#lam{args=As,body=B}) ->
Eas = do_encode_args(As),
Eb = do_encode_stmt(B),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"body">>,Eb}]}];
do_encode_expr(#map{fields=Fs}) -> %Create a map
Efs = do_encode_expr_map_fields(Fs),
[{<<"create_map">>,Efs}];
do_encode_expr({map,_Ann,Map,Fs}) -> %Update a map
Emap = do_encode_expr(Map),
Efs = do_encode_expr_map_fields(Fs),
[{<<"update_map">>,[Emap,Efs]}];
do_encode_expr(#map_get{field=F}) ->
do_encode_expr(F);
do_encode_expr(#proj{value=V}) ->
do_encode_expr(V);
do_encode_expr(#app{func=F,args=As}) ->
Ef = do_encode_expr(F),
Eas = do_encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
do_encode_expr(#'if'{test=Test,then=Then,else=Else}) ->
%% This is both a statement and en expression.
Etest = do_encode_expr(Test),
Ethen = do_encode_expr(Then),
Eelse = do_encode_expr(Else),
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
do_encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode_func(#{name := init}) -> []; %% do_encode_expr_rec_fields(Fields) -> [JSON].
decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) -> %% do_encode_expr_rec_field(Field) -> JSON.
[" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ", %% Encode a record field expression.
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) -> do_encode_expr_rec_fields(Fs) ->
Das = [ decode_arg(A) || A <- As ], [ do_encode_expr_rec_field(F) || F <- Fs ].
do_encode_expr_rec_field(#field{name=[N],value=V}) ->
[{<<"name">>,do_encode_expr(N)},
{<<"value">>,do_encode_expr(V)}].
%% do_encode_expr_map_fields(Fields) -> [JSON].
%% do_encode_expr_map_field(Field) -> JSON.
%% Encode a map field expression.
do_encode_expr_map_fields(Fs) ->
[ do_encode_expr_map_field(F) || F <- Fs ].
do_encode_expr_map_field({K,V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}];
do_encode_expr_map_field(#field{name=[K],value=V}) ->
[{<<"key">>,do_encode_expr(K)},
{<<"value">>,do_encode_expr(V)}].
%% decode_contract(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode_contract(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
#{<<"name">> := Name, <<"type_defs">> := Ts, <<"functions">> := Fs} = C,
CS = ["contract"," ",io_lib:format("~s", [Name])," =\n",
do_decode_tdefs(Ts),
do_decode_funcs(Fs)],
list_to_binary(CS).
do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
do_decode_func(#{<<"name">> := <<"init">>}) -> [];
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
do_decode_args(As)," => ",do_decode_type(T),$\n].
do_decode_args(As) ->
Das = [ do_decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)]. [$(,lists:join(", ", Das),$)].
decode_arg(#{type := T}) -> decode_type(T). do_decode_arg(#{<<"type">> := T}) -> do_decode_type(T).
decode_types(Ets) -> do_decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ]. [ do_decode_type(Et) || Et <- Ets ].
decode_type(#{tuple := Ets}) -> do_decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets), Ts = do_decode_types(Ets),
case Ts of [$(,lists:join(", ", Ts),$)];
[] -> ["unit"]; do_decode_type(#{<<"record">> := Efs}) ->
_ -> [$(,lists:join(" * ", Ts),$)] Fs = do_decode_type_rec_fields(Efs),
end; [${,lists:join(", ", Fs),$}];
decode_type(#{record := Efs}) -> do_decode_type(#{<<"list">> := Et}) ->
Fs = decode_fields(Efs), T = do_decode_type(Et),
[${,lists:join(",", Fs),$}];
decode_type(#{list := [Et]}) ->
T = decode_type(Et),
["list",$(,T,$)]; ["list",$(,T,$)];
decode_type(#{map := Ets}) -> do_decode_type(#{<<"map">> := Et}) ->
Ts = decode_types(Ets), T = do_decode_type_map(Et),
["map",$(,lists:join(",", Ts),$)]; ["map",$(,T,$)];
decode_type(#{bytes := Len}) -> do_decode_type(#{<<"variant">> := Ets}) ->
["bytes(", integer_to_list(Len), ")"]; Ts = do_decode_types(Ets),
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts); lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) -> do_decode_type(Econs) when is_map(Econs) -> %General constructor
[decode_type(#{tuple => Args}), " => ", decode_type(R)]; %% io:format("~p\n", [Econs]),
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs), [{Ec,Ets}] = maps:to_list(Econs),
AppName = decode_name(Ec), C = do_decode_name(Ec),
AppArgs = decode_types(Ets), Ts = do_decode_types(Ets),
case AppArgs of [C,$(,lists:join(", ", Ts),$)];
[] -> [AppName]; do_decode_type(T) -> %Just raw names.
_ -> [AppName,$(,lists:join(", ", AppArgs),$)] do_decode_name(T).
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En); do_decode_name(En) ->
decode_name(En) when is_binary(En) -> binary_to_list(En). binary_to_list(En).
decode_fields(Efs) -> do_decode_type_rec_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ]. [ do_decode_type_rec_field(Ef) || Ef <- Efs ].
decode_field(#{name := En, type := Et}) -> do_decode_type_rec_field(#{<<"name">> := En,<<"type">> := Et}) ->
Name = decode_name(En), Name = do_decode_name(En),
Type = decode_type(Et), Type = do_decode_type(Et),
[Name," : ",Type]. [Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString]. do_decode_type_map(#{<<"key">> := Ek,<<"value">> := Ev}) ->
Key = do_decode_type(Ek),
Value = do_decode_type(Ev),
[Key,", ",Value].
%% do_decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the %% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants. %% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ]. do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) -> do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
TypeDef = decode_type(T), [" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
DefType = decode_deftype(T), " = ",do_decode_type(T),$\n].
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_deftype(#{record := _Efs}) -> "record"; do_decode_tvars([]) -> []; %No tvars, no parentheses
decode_deftype(#{variant := _}) -> "datatype"; do_decode_tvars(Vs) ->
decode_deftype(_T) -> "type". Dvs = [ do_decode_tvar(V) || V <- Vs ],
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)]. [$(,lists:join(", ", Dvs),$)].
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). do_decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
payable(true) -> "payable ";
payable(false) -> "".
%% #contract{Ann, Con, [Declarations]}. %% #contract{Ann, Con, [Declarations]}.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace -> contract_name(#contract{con=#con{name=N}}) -> N.
[ D || D <- Decls, is_fun(D)].
contract_types({C, _, _, Decls}) when C == contract; C == namespace -> contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_type(D) ]. [ D || D <- Decls, is_record(D, letfun) ].
is_fun({letfun, _, _, _, _, _}) -> true; contract_types(#contract{decls=Decls}) ->
is_fun({fun_decl, _, _, _}) -> true; [ D || D <- Decls, is_record(D, type_def) ].
is_fun(_) -> false.
is_type({type_def, _, _, _, _}) -> true; %% To keep dialyzer happy and quiet.
is_type(_) -> false. %% namespace_name(#namespace{con=#con{name=N}}) -> N.
%%
%% namespace_funcs(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, letfun) ].
%%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
sort_decls(Ds) -> sort_decls(Ds) ->
Sort = fun (D1, D2) -> Sort = fun (D1, D2) ->
@@ -356,12 +515,58 @@ sort_decls(Ds) ->
end, end,
lists:sort(Sort, Ds). lists:sort(Sort, Ds).
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false). %% #letfun{Ann, Id, [Arg], Type, Typedef}.
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
is_payable(Node) -> aeso_syntax:get_ann(payable, Node, false).
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name. function_name(#letfun{id=#id{name=N}}) -> N.
typedef_vars({type_def, _, _, Vars, _}) -> Vars. function_args(#letfun{args=Args}) -> Args.
typedef_def({type_def, _, _, _, Def}) -> Def. function_type(#letfun{type=Type}) -> Type.
is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error(Pos, ErrorString) ->
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
+95 -419
View File
@@ -83,7 +83,7 @@
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}. -type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}. -type type_info() :: {aeso_syntax:ann(), typedef()}.
-type var_info() :: {aeso_syntax:ann(), utype()}. -type var_info() :: {aeso_syntax:ann(), type()}.
-type fun_env() :: [{name(), fun_info()}]. -type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}]. -type type_env() :: [{name(), type_info()}].
@@ -98,14 +98,11 @@
-type scope() :: #scope{}. -type scope() :: #scope{}.
-record(env, -record(env,
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() } { scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}] , vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()] , typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global , fields = #{} :: #{ name() => [field_info()] } %% fields are global
, namespace = [] :: qname() , namespace = [] :: qname()
, in_pattern = false :: boolean()
, stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id()
}). }).
-type env() :: #env{}. -type env() :: #env{}.
@@ -135,15 +132,11 @@ on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
Scope = maps:get(NS, Scopes), Scope = maps:get(NS, Scopes),
Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }. Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }.
-spec on_scopes(env(), fun((scope()) -> scope())) -> env(). -spec bind_var(aeso_syntax:id(), type(), env()) -> env().
on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), utype(), env()) -> env().
bind_var({id, Ann, X}, T, Env) -> bind_var({id, Ann, X}, T, Env) ->
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }. Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
-spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env(). -spec bind_vars([{aeso_syntax:id(), type()}], env()) -> env().
bind_vars([], Env) -> Env; bind_vars([], Env) -> Env;
bind_vars([{X, T} | Vars], Env) -> bind_vars([{X, T} | Vars], Env) ->
bind_vars(Vars, bind_var(X, T, Env)). bind_vars(Vars, bind_var(X, T, Env)).
@@ -156,7 +149,7 @@ bind_tvars(Xs, Env) ->
check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) -> check_tvar(#env{ typevars = TVars}, T = {tvar, _, X}) ->
case TVars == unrestricted orelse lists:member(X, TVars) of case TVars == unrestricted orelse lists:member(X, TVars) of
true -> ok; true -> ok;
false -> type_error({unbound_type, T}) false -> type_error({unbound_type_variable, T})
end, end,
T. T.
@@ -203,7 +196,7 @@ bind_state(Env) ->
false -> {id, Ann, "event"} %% will cause type error if used(?) false -> {id, Ann, "event"} %% will cause type error if used(?)
end, end,
Env1 = bind_funs([{"state", State}, Env1 = bind_funs([{"state", State},
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env), {"put", {fun_t, Ann, [], [State], Unit}}], Env),
%% We bind Chain.event in a local 'Chain' namespace. %% We bind Chain.event in a local 'Chain' namespace.
pop_scope( pop_scope(
@@ -363,14 +356,11 @@ global_env() ->
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end, Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
Fun1 = fun(S, T) -> Fun([S], T) end, Fun1 = fun(S, T) -> Fun([S], T) end,
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end, TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"}, SignId = {id, Ann, "signature"},
SignDef = {bytes, Ann, <<0:64/unit:8>>}, SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}}, Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end, SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
TTL = {qid, Ann, ["Chain", "ttl"]}, TTL = {qid, Ann, ["Chain", "ttl"]},
Fee = Int, Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
@@ -386,11 +376,9 @@ global_env() ->
{"RelativeTTL", Fun1(Int, TTL)}, {"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)}, {"FixedTTL", Fun1(Int, TTL)},
%% Abort %% Abort
{"abort", Fun1(String, A)}, {"abort", Fun1(String, A)}])
{"require", Fun([Bool, String], Unit)}])
, types = MkDefs( , types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0}, [{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
{"unit", {[], {alias_t, Unit}}},
{"hash", {[], {alias_t, Bytes(32)}}}, {"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}}, {"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0}, {"bits", 0},
@@ -401,10 +389,10 @@ global_env() ->
ChainScope = #scope ChainScope = #scope
{ funs = MkDefs( { funs = MkDefs(
%% Spend transaction. %% Spend transaction.
[{"spend", StateFun([Address, Int], Unit)}, [{"spend", Fun([Address, Int], Unit)},
%% Chain environment %% Chain environment
{"balance", Fun1(Address, Int)}, {"balance", Fun1(Address, Int)},
{"block_hash", Fun1(Int, Option(Hash))}, {"block_hash", Fun1(Int, Int)},
{"coinbase", Address}, {"coinbase", Address},
{"timestamp", Int}, {"timestamp", Int},
{"block_height", Int}, {"block_height", Int},
@@ -415,7 +403,7 @@ global_env() ->
ContractScope = #scope ContractScope = #scope
{ funs = MkDefs( { funs = MkDefs(
[{"address", Address}, [{"address", Address},
{"creator", Address}, %% {"owner", Int}, %% Not in EVM
{"balance", Int}]) }, {"balance", Int}]) },
CallScope = #scope CallScope = #scope
@@ -431,22 +419,19 @@ global_env() ->
{ funs = MkDefs( { funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))}, [{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"query_fee", Fun([Oracle(Q, R)], Fee)}, {"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))}, {"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)}, {"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)}, {"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
{"extend", SignFun([Oracle(Q, R), TTL], Unit)}, {"extend", SignFun([Oracle(Q, R), TTL], Unit)},
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}, {"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}]) },
{"check", Fun([Oracle(Q, R)], Bool)},
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
AENSScope = #scope AENSScope = #scope
{ funs = MkDefs( { funs = MkDefs(
[{"resolve", Fun([String, String], option_t(Ann, A))}, [{"resolve", Fun([String, String], option_t(Ann, A))},
{"preclaim", SignFun([Address, Hash], Unit)}, {"preclaim", SignFun([Address, Hash], Unit)},
{"claim", SignFun([Address, String, Int], Unit)}, {"claim", SignFun([Address, String, Int], Unit)},
{"transfer", SignFun([Address, Address, String], Unit)}, {"transfer", SignFun([Address, Address, Hash], Unit)},
{"revoke", SignFun([Address, String], Unit)}, {"revoke", SignFun([Address, Hash], Unit)}]) },
{"subname", SignFun([Address, String, Map(Q, Map(K, A))], Unit)}]) },
MapScope = #scope MapScope = #scope
{ funs = MkDefs( { funs = MkDefs(
@@ -461,10 +446,8 @@ global_env() ->
%% Crypto/Curve operations %% Crypto/Curve operations
CryptoScope = #scope CryptoScope = #scope
{ funs = MkDefs( { funs = MkDefs(
[{"verify_sig", Fun([Hash, Address, SignId], Bool)}, [{"ecverify", Fun([Hash, Address, SignId], Bool)},
{"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)}, {"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
{"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)},
{"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))},
{"sha3", Fun1(A, Hash)}, {"sha3", Fun1(A, Hash)},
{"sha256", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)},
{"blake2b", Fun1(A, Hash)}]) }, {"blake2b", Fun1(A, Hash)}]) },
@@ -496,18 +479,9 @@ global_env() ->
{"none", Bits}, {"none", Bits},
{"all", Bits}]) }, {"all", Bits}]) },
%% Bytes
BytesScope = #scope
{ funs = MkDefs(
[{"to_int", Fun1(Bytes(any), Int)},
{"to_str", Fun1(Bytes(any), String)}]) },
%% Conversion %% Conversion
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}]) },
{"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)},
{"is_payable", Fun1(Address, Bool)}]) },
#env{ scopes = #env{ scopes =
#{ [] => TopScope #{ [] => TopScope
@@ -521,7 +495,6 @@ global_env() ->
, ["Crypto"] => CryptoScope , ["Crypto"] => CryptoScope
, ["String"] => StringScope , ["String"] => StringScope
, ["Bits"] => BitsScope , ["Bits"] => BitsScope
, ["Bytes"] => BytesScope
, ["Int"] => IntScope , ["Int"] => IntScope
, ["Address"] => AddressScope , ["Address"] => AddressScope
} }. } }.
@@ -533,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
infer(Contracts) -> infer(Contracts) ->
infer(Contracts, []). infer(Contracts, []).
-type option() :: return_env | dont_unfold. -type option() :: return_env.
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_env(). init_env(_Options) -> global_env().
@@ -545,38 +518,30 @@ infer(Contracts, Options) ->
Env = init_env(Options), Env = init_env(Options),
create_options(Options), create_options(Options),
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
check_modifiers(Env, Contracts), {Env1, Decls} = infer1(Env, Contracts, []),
{Env1, Decls} = infer1(Env, Contracts, [], Options),
{Env2, Decls2} =
case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls};
false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{E, unfold_record_types(E, Decls)}
end,
case proplists:get_value(return_env, Options, false) of case proplists:get_value(return_env, Options, false) of
false -> Decls2; false -> Decls;
true -> {Env2, Decls2} true -> {Env1, Decls}
end end
after after
clean_up_ets() clean_up_ets()
end. end.
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) -> -spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer1(Env, [], Acc) -> {Env, lists:reverse(Acc)};
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)}; infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc) ->
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), check_scope_name_clash(Env, contract, ConName),
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code),
Contract1 = {contract, Ann, ConName, Code1}, Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), Env3 = bind_contract(Contract1, Env2),
infer1(Env3, Rest, [Contract1 | Acc], Options); infer1(Env3, Rest, [Contract1 | Acc]);
infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) -> infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc) ->
check_scope_name_clash(Env, namespace, Name), check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code),
Namespace1 = {namespace, Ann, Name, Code1}, Namespace1 = {namespace, Ann, Name, Code1},
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options). infer1(pop_scope(Env1), Rest, [Namespace1 | Acc]).
check_scope_name_clash(Env, Kind, Name) -> check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of case get_scope(Env, qname(Name)) of
@@ -587,11 +552,13 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. end.
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
{env(), [aeso_syntax:decl()]}. infer_contract_top(Env, Kind, Defs0) ->
infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0), Defs = desugar(Defs0),
infer_contract(Env, Kind, Defs). {Env1, Defs1} = infer_contract(Env, Kind, Defs),
Env2 = on_current_scope(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
Defs2 = unfold_record_types(Env2, Defs1),
{Env2, Defs2}.
%% TODO: revisit %% TODO: revisit
infer_constant({letval, Attrs,_Pattern, Type, E}) -> infer_constant({letval, Attrs,_Pattern, Type, E}) ->
@@ -630,17 +597,15 @@ infer_contract(Env, What, Defs) ->
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]), %% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []), {Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env4, Defs1),
destroy_and_report_type_errors(Env4), destroy_and_report_type_errors(Env4),
{Env4, TypeDefs ++ Decls ++ Defs1}. {Env4, TypeDefs ++ Decls ++ Defs1}.
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env = #env{ namespace = Ns }, Defs) -> check_typedefs(Env, Defs) ->
create_type_errors(), create_type_errors(),
GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end, GetName = fun({type_def, _, {id, _, Name}, _, _}) -> Name end,
TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]), TypeMap = maps:from_list([ {GetName(Def), Def} || Def <- Defs ]),
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Ns, Def) end, TypeMap), DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_types(Def) end, TypeMap),
SCCs = aeso_utils:scc(DepGraph), SCCs = aeso_utils:scc(DepGraph),
{Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []), {Env1, Defs1} = check_typedef_sccs(Env, TypeMap, SCCs, []),
destroy_and_report_type_errors(Env), destroy_and_report_type_errors(Env),
@@ -693,40 +658,6 @@ check_typedef(Env, {variant_t, Cons}) ->
check_unexpected(Xs) -> check_unexpected(Xs) ->
[ type_error(X) || X <- Xs ]. [ type_error(X) || X <- Xs ].
check_modifiers(Env, Contracts) ->
create_type_errors(),
[ case C of
{contract, _, Con, Decls} ->
check_modifiers1(contract, Decls),
case {lists:keymember(letfun, 1, Decls),
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
{true, []} -> type_error({contract_has_no_entrypoints, Con});
_ -> ok
end;
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls)
end || C <- Contracts ],
destroy_and_report_type_errors(Env).
-spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok.
check_modifiers1(What, Decls) when is_list(Decls) ->
_ = [ check_modifiers1(What, Decl) || Decl <- Decls ],
ok;
check_modifiers1(What, Decl) when element(1, Decl) == letfun; element(1, Decl) == fun_decl ->
Public = aeso_syntax:get_ann(public, Decl, false),
Private = aeso_syntax:get_ann(private, Decl, false),
Entrypoint = aeso_syntax:get_ann(entrypoint, Decl, false),
FunDecl = element(1, Decl) == fun_decl,
{id, _, Name} = element(3, Decl),
_ = [ type_error({proto_must_be_entrypoint, Decl}) || FunDecl, Private orelse not Entrypoint, What == contract ],
_ = [ type_error({proto_in_namespace, Decl}) || FunDecl, What == namespace ],
_ = [ type_error({init_must_be_an_entrypoint, Decl}) || not Entrypoint, Name == "init", What == contract ],
_ = [ type_error({public_modifier_in_contract, Decl}) || Public, not Private, not Entrypoint, What == contract ],
_ = [ type_error({entrypoint_in_namespace, Decl}) || Entrypoint, What == namespace ],
_ = [ type_error({private_entrypoint, Decl}) || Private, Entrypoint ],
_ = [ type_error({private_and_public, Decl}) || Private, Public ],
ok;
check_modifiers1(_, _) -> ok.
-spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type(). -spec check_type(env(), aeso_syntax:type()) -> aeso_syntax:type().
check_type(Env, T) -> check_type(Env, T) ->
check_type(Env, T, 0). check_type(Env, T, 0).
@@ -768,9 +699,7 @@ check_type(Env, Type = {fun_t, Ann, NamedArgs, Args, Ret}, Arity) ->
{fun_t, Ann, NamedArgs1, Args1, Ret1}; {fun_t, Ann, NamedArgs1, Args1, Ret1};
check_type(_Env, Type = {uvar, _, _}, Arity) -> check_type(_Env, Type = {uvar, _, _}, Arity) ->
ensure_base_type(Type, Arity), ensure_base_type(Type, Arity),
Type; Type.
check_type(_Env, {args_t, Ann, Ts}, _) ->
type_error({new_tuple_syntax, Ann, Ts}).
ensure_base_type(Type, Arity) -> ensure_base_type(Type, Arity) ->
[ type_error({wrong_type_arguments, Type, Arity, 0}) || Arity /= 0 ], [ type_error({wrong_type_arguments, Type, Arity, 0}) || Arity /= 0 ],
@@ -799,35 +728,35 @@ check_event(Env, "event", Ann, Def) ->
check_event(_Env, _Name, _Ann, Def) -> Def. check_event(_Env, _Name, _Ann, Def) -> Def.
check_event_con(Env, {constr_t, Ann, Con, Args}) -> check_event_con(Env, {constr_t, Ann, Con, Args}) ->
IsIndexed = fun(T) -> IsIndexed = fun(T) -> case aeso_syntax:get_ann(indexed, T, false) of
T1 = unfold_types_in_type(Env, T), true -> indexed;
%% `indexed` is optional but if used it has to be correctly used false -> notindexed
case {is_word_type(T1), is_string_type(T1), aeso_syntax:get_ann(indexed, T, false)} of end end,
{true, _, _} -> indexed;
{false, true, true} -> type_error({indexed_type_must_be_word, T, T1});
{false, true, _} -> notindexed;
{false, false, _} -> type_error({event_arg_type_word_or_string, T, T1}), error
end
end,
Indices = lists:map(IsIndexed, Args), Indices = lists:map(IsIndexed, Args),
Indexed = [ T || T <- Args, IsIndexed(T) == indexed ], Indexed = [ T || T <- Args, IsIndexed(T) == indexed ],
NonIndexed = Args -- Indexed, NonIndexed = Args -- Indexed,
[ check_event_arg_type(Env, Ix, Type) || {Ix, Type} <- lists:zip(Indices, Args) ],
[ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ], [ type_error({event_0_to_3_indexed_values, Con}) || length(Indexed) > 3 ],
[ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ], [ type_error({event_0_to_1_string_values, Con}) || length(NonIndexed) > 1 ],
{constr_t, [{indices, Indices} | Ann], Con, Args}. {constr_t, [{indices, Indices} | Ann], Con, Args}.
check_event_arg_type(Env, Ix, Type0) ->
Type = unfold_types_in_type(Env, Type0),
case Ix of
indexed -> [ type_error({indexed_type_must_be_word, Type0, Type}) || not is_word_type(Type) ];
notindexed -> [ type_error({payload_type_must_be_string, Type0, Type}) || not is_string_type(Type) ]
end.
%% Not so nice. %% Not so nice.
is_word_type({id, _, Name}) -> is_word_type({id, _, Name}) ->
lists:member(Name, ["int", "address", "hash", "bits", "bool"]); lists:member(Name, ["int", "address", "hash", "bits", "bool"]);
is_word_type({app_t, _, {id, _, Name}, [_, _]}) -> is_word_type({app_t, _, {id, _, Name}, [_, _]}) ->
lists:member(Name, ["oracle", "oracle_query"]); lists:member(Name, ["oracle", "oracle_query"]);
is_word_type({bytes_t, _, N}) -> N =< 32;
is_word_type({con, _, _}) -> true; is_word_type({con, _, _}) -> true;
is_word_type({qcon, _, _}) -> true; is_word_type({qcon, _, _}) -> true;
is_word_type(_) -> false. is_word_type(_) -> false.
is_string_type({id, _, "string"}) -> true; is_string_type({id, _, "string"}) -> true;
is_string_type({bytes_t, _, N}) -> N > 32;
is_string_type(_) -> false. is_string_type(_) -> false.
-spec check_constructor_overlap(env(), aeso_syntax:con(), type()) -> ok | no_return(). -spec check_constructor_overlap(env(), aeso_syntax:con(), type()) -> ok | no_return().
@@ -864,7 +793,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
end; end;
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) -> check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
Defs = [ maps:get(X, Funs) || X <- Xs ], Defs = [ maps:get(X, Funs) || X <- Xs ],
{TypeSigs, Defs1} = infer_letrec(Env, Defs), {TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
Env1 = bind_funs(TypeSigs, Env), Env1 = bind_funs(TypeSigs, Env),
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc). check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
@@ -906,7 +835,7 @@ check_special_funs(_, _) -> ok.
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}. typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
infer_letrec(Env, Defs) -> infer_letrec(Env, {letrec, Attrs, Defs}) ->
create_constraints(), create_constraints(),
Funs = [{Name, fresh_uvar(A)} Funs = [{Name, fresh_uvar(A)}
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs], || {letfun, _, {id, A, Name}, _, _, _} <- Defs],
@@ -926,12 +855,9 @@ infer_letrec(Env, Defs) ->
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]), TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
NewDefs = instantiate([D || {_, D} <- Inferred]), NewDefs = instantiate([D || {_, D} <- Inferred]),
[print_typesig(S) || S <- TypeSigs], [print_typesig(S) || S <- TypeSigs],
{TypeSigs, NewDefs}. {TypeSigs, {letrec, Attrs, NewDefs}}.
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) -> infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
current_function = Fun },
check_unique_arg_names(Fun, Args),
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args], ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
ExpectedType = check_type(Env, arg_type(What)), ExpectedType = check_type(Env, arg_type(What)),
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType), NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
@@ -942,13 +868,6 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo
{{Name, TypeSig}, {{Name, TypeSig},
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}. {letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
check_unique_arg_names(Fun, Args) ->
Name = fun({arg, _, {id, _, X}, _}) -> X end,
Names = lists:map(Name, Args),
Dups = lists:usort(Names -- lists:usort(Names)),
[ type_error({repeated_arg, Fun, Arg}) || Arg <- Dups ],
ok.
print_typesig({Name, TypeSig}) -> print_typesig({Name, TypeSig}) ->
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]). ?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
@@ -972,70 +891,14 @@ lookup_name(Env, As, Id, Options) ->
{Id, fresh_uvar(As)}; {Id, fresh_uvar(As)};
{QId, {_, Ty}} -> {QId, {_, Ty}} ->
Freshen = proplists:get_value(freshen, Options, false), Freshen = proplists:get_value(freshen, Options, false),
check_stateful(Env, Id, Ty),
Ty1 = case Ty of Ty1 = case Ty of
{type_sig, _, _, _, _} -> freshen_type(As, typesig_to_fun_t(Ty)); {type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
_ when Freshen -> freshen_type(As, Ty); _ when Freshen -> freshen_type(Ty);
_ -> Ty _ -> Ty
end, end,
{set_qname(QId, Id), Ty1} {set_qname(QId, Id), Ty1}
end. end.
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
case aeso_syntax:get_ann(stateful, Type, false) of
false -> ok;
true ->
type_error({stateful_not_allowed, Id, Fun})
end;
check_stateful(_Env, _Id, _Type) -> ok.
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
%% works since the user can't create functions with named arguments.
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
case Default of
{int, _, 0} -> ok;
_ -> type_error({value_arg_not_allowed, Default, Fun})
end;
check_stateful_named_arg(_, _, _) -> ok.
%% Check that `init` doesn't read or write the state
check_state_dependencies(Env, Defs) ->
Top = Env#env.namespace,
GetState = Top ++ ["state"],
SetState = Top ++ ["put"],
Init = Top ++ ["init"],
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
case maps:get(Init, Deps, false) of
false -> ok; %% No init, so nothing to check
_ ->
[ type_error({init_depends_on_state, state, Chain})
|| Chain <- get_call_chains(Deps, Init, GetState) ],
[ type_error({init_depends_on_state, put, Chain})
|| Chain <- get_call_chains(Deps, Init, SetState) ],
ok
end.
%% Compute all paths (not sharing intermediate nodes) from Start to Stop in Graph.
get_call_chains(Graph, Start, Stop) ->
get_call_chains(Graph, #{}, queue:from_list([{Start, [], []}]), Stop, []).
get_call_chains(Graph, Visited, Queue, Stop, Acc) ->
case queue:out(Queue) of
{empty, _} -> lists:reverse(Acc);
{{value, {Stop, Ann, Path}}, Queue1} ->
get_call_chains(Graph, Visited, Queue1, Stop, [lists:reverse([{Stop, Ann} | Path]) | Acc]);
{{value, {Node, Ann, Path}}, Queue1} ->
case maps:is_key(Node, Visited) of
true -> get_call_chains(Graph, Visited, Queue1, Stop, Acc);
false ->
Calls = maps:get(Node, Graph, []),
NewQ = queue:from_list([{New, Ann1, [{Node, Ann} | Path]} || {New, Ann1} <- Calls]),
get_call_chains(Graph, Visited#{Node => true}, queue:join(Queue1, NewQ), Stop, Acc)
end
end.
check_expr(Env, Expr, Type) -> check_expr(Env, Expr, Type) ->
E = {typed, _, _, Type1} = infer_expr(Env, Expr), E = {typed, _, _, Type1} = infer_expr(Env, Expr),
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}), unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
@@ -1045,8 +908,6 @@ infer_expr(_Env, Body={bool, As, _}) ->
{typed, As, Body, {id, As, "bool"}}; {typed, As, Body, {id, As, "bool"}};
infer_expr(_Env, Body={int, As, _}) -> infer_expr(_Env, Body={int, As, _}) ->
{typed, As, Body, {id, As, "int"}}; {typed, As, Body, {id, As, "int"}};
infer_expr(_Env, Body={char, As, _}) ->
{typed, As, Body, {id, As, "char"}};
infer_expr(_Env, Body={string, As, _}) -> infer_expr(_Env, Body={string, As, _}) ->
{typed, As, Body, {id, As, "string"}}; {typed, As, Body, {id, As, "string"}};
infer_expr(_Env, Body={bytes, As, Bin}) -> infer_expr(_Env, Body={bytes, As, Bin}) ->
@@ -1074,6 +935,8 @@ infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon -> infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
{QName, Type} = lookup_name(Env, As, Id, [freshen]), {QName, Type} = lookup_name(Env, As, Id, [freshen]),
{typed, As, QName, Type}; {typed, As, QName, Type};
infer_expr(Env, {unit, As}) ->
infer_expr(Env, {tuple, As, []});
infer_expr(Env, {tuple, As, Cpts}) -> infer_expr(Env, {tuple, As, Cpts}) ->
NewCpts = [infer_expr(Env, C) || C <- Cpts], NewCpts = [infer_expr(Env, C) || C <- Cpts],
CptTypes = [T || {typed, _, _, T} <- NewCpts], CptTypes = [T || {typed, _, _, T} <- NewCpts],
@@ -1082,58 +945,6 @@ infer_expr(Env, {list, As, Elems}) ->
ElemType = fresh_uvar(As), ElemType = fresh_uvar(As),
NewElems = [check_expr(Env, X, ElemType) || X <- Elems], NewElems = [check_expr(Env, X, ElemType) || X <- Elems],
{typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}}; {typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}};
infer_expr(Env, {list_comp, As, Yield, []}) ->
{typed, _, TypedYield, Type} = infer_expr(Env, Yield),
{typed, As, {list_comp, As, TypedYield, []}, {app_t, As, {id, As, "list"}, [Type]}};
infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Arg, BExpr}|Rest]}) ->
BindVarType = fresh_uvar(As),
TypedBind = {typed, As2, _, TypeBExpr} = infer_expr(Env, BExpr),
unify( Env
, TypeBExpr
, {app_t, As, {id, As, "list"}, [BindVarType]}
, {list_comp, TypedBind, TypeBExpr, {app_t, As2, {id, As, "list"}, [BindVarType]}}),
NewE = bind_var(Arg, BindVarType, Env),
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} =
infer_expr(NewE, {list_comp, As, Yield, Rest}),
{ typed
, As
, {list_comp, As, TypedYield, [{comprehension_bind, {typed, Arg, BindVarType}, TypedBind}|TypedRest]}
, ResType};
infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Rest]}) ->
NewCond = check_expr(Env, Cond, {id, AttrsIF, "bool"}),
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} =
infer_expr(Env, {list_comp, AttrsL, Yield, Rest}),
{ typed
, AttrsL
, {list_comp, AttrsL, TypedYield, [{comprehension_if, AttrsIF, NewCond}|TypedRest]}
, ResType};
infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, Type, E}|Rest]}) ->
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, AsLV, E, arg_type(Type)}),
BlockType = fresh_uvar(AsLV),
{'case', _, NewPattern, NewRest} =
infer_case( Env
, AsLC
, Pattern
, PatType
, {list_comp, AsLC, Yield, Rest}
, BlockType),
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = NewRest,
{ typed
, AsLC
, {list_comp, AsLC, TypedYield, [{letval, AsLV, NewPattern, Type, NewE}|TypedRest]}
, ResType
};
infer_expr(Env, {list_comp, AsLC, Yield, [Def={letfun, AsLF, _, _, _, _}|Rest]}) ->
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
FunT = freshen_type(AsLF, typesig_to_fun_t(TypeSig)),
NewE = bind_var({id, AsLF, Name}, FunT, Env),
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} =
infer_expr(NewE, {list_comp, AsLC, Yield, Rest}),
{ typed
, AsLC
, {list_comp, AsLC, TypedYield, [LetFun|TypedRest]}
, ResType
};
infer_expr(Env, {typed, As, Body, Type}) -> infer_expr(Env, {typed, As, Body, Type}) ->
Type1 = check_type(Env, Type), Type1 = check_type(Env, Type),
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
@@ -1179,7 +990,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
constrain([ #record_create_constraint{ constrain([ #record_create_constraint{
record_t = RecordType1, record_t = RecordType1,
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ], fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
context = Attrs } || not Env#env.in_pattern ] ++ context = Attrs } ] ++
[begin [begin
[{proj, _, FieldName}] = LV, [{proj, _, FieldName}] = LV,
#field_constraint{ #field_constraint{
@@ -1244,17 +1055,10 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} = {'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} =
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType), infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns], NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}; {typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}}.
infer_expr(Env, Let = {letval, Attrs, _, _, _}) ->
type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
type_error({missing_body_for_let, Attrs}),
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) -> infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E), CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
check_stateful_named_arg(Env, Id, E),
add_named_argument_constraint( add_named_argument_constraint(
#named_argument_constraint{ #named_argument_constraint{
args = NamedArgs, args = NamedArgs,
@@ -1315,7 +1119,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
[] -> ok; [] -> ok;
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)}) Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
end, end,
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }), NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern), NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
NewBranch = check_expr(NewEnv, Branch, SwitchType), NewBranch = check_expr(NewEnv, Branch, SwitchType),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
@@ -1324,18 +1128,19 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType) %% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
infer_block(_Env, Attrs, [], BlockType) -> infer_block(_Env, Attrs, [], BlockType) ->
error({impossible, empty_block, Attrs, BlockType}); error({impossible, empty_block, Attrs, BlockType});
infer_block(Env, _, [E], BlockType) -> infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
[check_expr(Env, E, BlockType)]; NewDef = infer_letfun(Env, Def),
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> [NewDef|infer_block(Env, Attrs, Rest, BlockType)];
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), NewDef = infer_letrec(Env, Def),
NewE = bind_var({id, Ann, Name}, FunT, Env), [NewDef|infer_block(Env, Attrs, Rest, BlockType)];
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) -> infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}), NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} = {'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType), infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
[{letval, Attrs, NewPattern, Type, NewE}|NewRest]; [{letval, Attrs, NewPattern, Type, NewE}|NewRest];
infer_block(Env, _, [E], BlockType) ->
[check_expr(Env, E, BlockType)];
infer_block(Env, Attrs, [E|Rest], BlockType) -> infer_block(Env, Attrs, [E|Rest], BlockType) ->
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)]. [infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
@@ -1355,9 +1160,6 @@ infer_infix({RelOp, As})
T = fresh_uvar(As), %% allow any type here, check in ast_to_icode that we have comparison for it T = fresh_uvar(As), %% allow any type here, check in ast_to_icode that we have comparison for it
Bool = {id, As, "bool"}, Bool = {id, As, "bool"},
{fun_t, As, [], [T, T], Bool}; {fun_t, As, [], [T, T], Bool};
infer_infix({'..', As}) ->
Int = {id, As, "int"},
{fun_t, As, [], [Int, Int], {app_t, As, {id, As, "list"}, [Int]}};
infer_infix({'::', As}) -> infer_infix({'::', As}) ->
ElemType = fresh_uvar(As), ElemType = fresh_uvar(As),
ListType = {app_t, As, {id, As, "list"}, [ElemType]}, ListType = {app_t, As, {id, As, "list"}, [ElemType]},
@@ -1374,13 +1176,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
Int = {id, As, "int"}, Int = {id, As, "int"},
{fun_t, As, [], [Int], Int}. {fun_t, As, [], [Int], Int}.
abort_expr(Ann, Str) ->
{app, Ann, {id, Ann, "abort"}, [{string, Ann, Str}]}.
free_vars({int, _, _}) -> free_vars({int, _, _}) ->
[]; [];
free_vars({char, _, _}) ->
[];
free_vars({string, _, _}) -> free_vars({string, _, _}) ->
[]; [];
free_vars({bool, _, _}) -> free_vars({bool, _, _}) ->
@@ -1471,17 +1268,14 @@ when_option(Opt, Do) ->
create_constraints() -> create_constraints() ->
create_named_argument_constraints(), create_named_argument_constraints(),
create_bytes_constraints(),
create_field_constraints(). create_field_constraints().
solve_constraints(Env) -> solve_constraints(Env) ->
solve_named_argument_constraints(Env), solve_named_argument_constraints(Env),
solve_bytes_constraints(Env),
solve_field_constraints(Env). solve_field_constraints(Env).
destroy_and_report_unsolved_constraints(Env) -> destroy_and_report_unsolved_constraints(Env) ->
destroy_and_report_unsolved_field_constraints(Env), destroy_and_report_unsolved_field_constraints(Env),
destroy_and_report_unsolved_bytes_constraints(Env),
destroy_and_report_unsolved_named_argument_constraints(Env). destroy_and_report_unsolved_named_argument_constraints(Env).
%% -- Named argument constraints -- %% -- Named argument constraints --
@@ -1530,37 +1324,6 @@ destroy_and_report_unsolved_named_argument_constraints(Env) ->
destroy_named_argument_constraints(), destroy_named_argument_constraints(),
ok. ok.
%% -- Bytes constraints --
-type byte_constraint() :: {is_bytes, utype()}.
create_bytes_constraints() ->
ets_new(bytes_constraints, [bag]).
get_bytes_constraints() ->
ets_tab2list(bytes_constraints).
-spec add_bytes_constraint(byte_constraint()) -> true.
add_bytes_constraint(Constraint) ->
ets_insert(bytes_constraints, Constraint).
solve_bytes_constraints(_Env) ->
ok.
destroy_bytes_constraints() ->
ets_delete(bytes_constraints).
destroy_and_report_unsolved_bytes_constraints(Env) ->
[ check_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
destroy_bytes_constraints().
check_bytes_constraint(Env, {is_bytes, Type}) ->
Type1 = unfold_types_in_type(Env, instantiate(Type)),
case Type1 of
{bytes_t, _, _} -> ok;
_ -> type_error({cannot_unify, Type1, {bytes_t, [], any}, {at, Type}})
end.
%% -- Field constraints -- %% -- Field constraints --
create_field_constraints() -> create_field_constraints() ->
@@ -1970,10 +1733,6 @@ occurs_check1(R, {tuple_t, _, Ts}) ->
occurs_check(R, Ts); occurs_check(R, Ts);
occurs_check1(R, {named_arg_t, _, _, T, _}) -> occurs_check1(R, {named_arg_t, _, _, T, _}) ->
occurs_check(R, T); occurs_check(R, T);
occurs_check1(R, {record_t, Fields}) ->
occurs_check(R, Fields);
occurs_check1(R, {field_t, _, _, T}) ->
occurs_check(R, T);
occurs_check1(R, [H | T]) -> occurs_check1(R, [H | T]) ->
occurs_check(R, H) orelse occurs_check(R, T); occurs_check(R, H) orelse occurs_check(R, T);
occurs_check1(_, []) -> false. occurs_check1(_, []) -> false.
@@ -1987,31 +1746,26 @@ create_freshen_tvars() ->
destroy_freshen_tvars() -> destroy_freshen_tvars() ->
ets_delete(freshen_tvars). ets_delete(freshen_tvars).
freshen_type(Ann, Type) -> freshen_type(Type) ->
create_freshen_tvars(), create_freshen_tvars(),
Type1 = freshen(Ann, Type), Type1 = freshen(Type),
destroy_freshen_tvars(), destroy_freshen_tvars(),
Type1. Type1.
freshen(Type) -> freshen({tvar, As, Name}) ->
freshen(aeso_syntax:get_ann(Type), Type).
freshen(Ann, {tvar, _, Name}) ->
NewT = case ets_lookup(freshen_tvars, Name) of NewT = case ets_lookup(freshen_tvars, Name) of
[] -> fresh_uvar(Ann); [] ->
[{Name, T}] -> T fresh_uvar(As);
[{Name, T}] ->
T
end, end,
ets_insert(freshen_tvars, {Name, NewT}), ets_insert(freshen_tvars, {Name, NewT}),
NewT; NewT;
freshen(Ann, {bytes_t, _, any}) -> freshen(T) when is_tuple(T) ->
X = fresh_uvar(Ann), list_to_tuple(freshen(tuple_to_list(T)));
add_bytes_constraint({is_bytes, X}), freshen([A|B]) ->
X; [freshen(A)|freshen(B)];
freshen(Ann, T) when is_tuple(T) -> freshen(X) ->
list_to_tuple(freshen(Ann, tuple_to_list(T)));
freshen(Ann, [A | B]) ->
[freshen(Ann, A) | freshen(Ann, B)];
freshen(_, X) ->
X. X.
%% Dereferences all uvars and replaces the uninstantiated ones with a %% Dereferences all uvars and replaces the uninstantiated ones with a
@@ -2020,8 +1774,8 @@ instantiate(E) ->
instantiate1(dereference(E)). instantiate1(dereference(E)).
instantiate1({uvar, Attr, R}) -> instantiate1({uvar, Attr, R}) ->
Next = proplists:get_value(next, ets_lookup(type_vars, next), 0), Next = proplists:get_value(next, ets_lookup(type_vars, next), 1),
TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)}, TVar = {tvar, Attr, "'" ++ integer_to_list(Next)},
ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]), ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]),
TVar; TVar;
instantiate1({fun_t, Ann, Named, Args, Ret}) -> instantiate1({fun_t, Ann, Named, Args, Ret}) ->
@@ -2041,12 +1795,6 @@ instantiate1([A|B]) ->
instantiate1(X) -> instantiate1(X) ->
X. X.
integer_to_tvar(X) when X < 26 ->
[$a + X];
integer_to_tvar(X) ->
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
%% Save unification failures for error messages. %% Save unification failures for error messages.
cannot_unify(A, B, When) -> cannot_unify(A, B, When) ->
@@ -2059,7 +1807,7 @@ create_type_errors() ->
ets_new(type_errors, [bag]). ets_new(type_errors, [bag]).
destroy_and_report_type_errors(Env) -> destroy_and_report_type_errors(Env) ->
Errors = lists:reverse(ets_tab2list(type_errors)), Errors = ets_tab2list(type_errors),
%% io:format("Type errors now: ~p\n", [Errors]), %% io:format("Type errors now: ~p\n", [Errors]),
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ], PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
ets_delete(type_errors), ets_delete(type_errors),
@@ -2169,76 +1917,16 @@ pp_error({duplicate_definition, Name, Locs}) ->
pp_error({duplicate_scope, Kind, Name, OtherKind, L}) -> pp_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n", io_lib:format("The ~p ~s (at ~s) has the same name as a ~p at ~s\n",
[Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]); [Kind, pp(Name), pp_loc(Name), OtherKind, pp_loc(L)]);
pp_error({include, _, {string, Pos, Name}}) -> pp_error({include, {string, Pos, Name}}) ->
io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n", io_lib:format("Include of '~s' at ~s\nnot allowed, include only allowed at top level.\n",
[binary_to_list(Name), pp_loc(Pos)]); [binary_to_list(Name), pp_loc(Pos)]);
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]); [Name, pp_loc(Pos)]);
pp_error({repeated_arg, Fun, Arg}) ->
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
[Arg, pp(Fun), pp_loc(Fun)]);
pp_error({stateful_not_allowed, Id, Fun}) ->
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp(Id), pp_loc(Id), pp(Fun)]);
pp_error({value_arg_not_allowed, Value, Fun}) ->
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
[if Which == put -> "write"; true -> "read" end,
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]);
pp_error({missing_body_for_let, Ann}) ->
io_lib:format("Let binding at ~s must be followed by an expression\n", [pp_loc(Ann)]);
pp_error({public_modifier_in_contract, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' instead of 'function' for public function ~s (at ~s):\n~s\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({init_must_be_an_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("The init function (at ~s) must be an entrypoint:\n~s\n",
[pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({proto_must_be_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
io_lib:format("Use 'entrypoint' for declaration of ~s (at ~s):\n~s\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl),
prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]);
pp_error({proto_in_namespace, Decl}) ->
io_lib:format("Namespaces cannot contain function prototypes (at ~s).\n",
[pp_loc(Decl)]);
pp_error({entrypoint_in_namespace, Decl}) ->
io_lib:format("Namespaces cannot contain entrypoints (at ~s). Use 'function' instead.\n",
[pp_loc(Decl)]);
pp_error({private_entrypoint, Decl}) ->
io_lib:format("The entrypoint ~s (at ~s) cannot be private. Use 'function' instead.\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
pp_error({private_and_public, Decl}) ->
io_lib:format("The function ~s (at ~s) cannot be both public and private.\n",
[pp_expr("", element(3, Decl)), pp_loc(Decl)]);
pp_error({contract_has_no_entrypoints, Con}) ->
io_lib:format("The contract ~s (at ~s) has no entrypoints. Since Sophia version 3.2, public\n"
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]);
pp_error({unbound_type, Type}) ->
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]);
pp_error({new_tuple_syntax, Ann, Ts}) ->
io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n",
[pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]);
pp_error(Err) -> pp_error(Err) ->
io_lib:format("Unknown error: ~p\n", [Err]). io_lib:format("Unknown error: ~p\n", [Err]).
mk_entrypoint(Decl) ->
Ann = [entrypoint | lists:keydelete(public, 1,
lists:keydelete(private, 1,
aeso_syntax:get_ann(Decl))) -- [public, private]],
aeso_syntax:set_ann(Ann, Decl).
pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]); pp_when({todo, What}) -> io_lib:format("[TODO] ~p\n", [What]);
pp_when({at, Ann}) -> io_lib:format("at ~s\n", [pp_loc(Ann)]);
pp_when({check_typesig, Name, Inferred, Given}) -> pp_when({check_typesig, Name, Inferred, Given}) ->
io_lib:format("when checking the definition of ~s\n" io_lib:format("when checking the definition of ~s\n"
" inferred type: ~s\n" " inferred type: ~s\n"
@@ -2319,13 +2007,6 @@ pp_when({check_expr, Expr, Inferred0, Expected0}) ->
pp_when({checking_init_type, Ann}) -> pp_when({checking_init_type, Ann}) ->
io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n",
[pp_loc(Ann)]); [pp_loc(Ann)]);
pp_when({list_comp, BindExpr, Inferred0, Expected0}) ->
{Inferred, Expected} = instantiate({Inferred0, Expected0}),
io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n"
"against type \n~s\n",
[pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)]
);
pp_when(unknown) -> "". pp_when(unknown) -> "".
-spec pp_why_record(why_record()) -> iolist(). -spec pp_why_record(why_record()) -> iolist().
@@ -2396,11 +2077,8 @@ pp({uvar, _, Ref}) ->
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ]; ["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
pp({tvar, _, Name}) -> pp({tvar, _, Name}) ->
Name; Name;
pp({tuple_t, _, []}) ->
"unit";
pp({tuple_t, _, Cpts}) -> pp({tuple_t, _, Cpts}) ->
["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"]; ["(", pp(Cpts), ")"];
pp({bytes_t, _, any}) -> "bytes(_)";
pp({bytes_t, _, Len}) -> pp({bytes_t, _, Len}) ->
["bytes(", integer_to_list(Len), ")"]; ["bytes(", integer_to_list(Len), ")"];
pp({app_t, _, T, []}) -> pp({app_t, _, T, []}) ->
@@ -2412,9 +2090,7 @@ pp({named_arg_t, _, Name, Type, Default}) ->
pp({fun_t, _, Named = {uvar, _, _}, As, B}) -> pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
["(", pp(Named), " | ", pp(As), ") => ", pp(B)]; ["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
pp({fun_t, _, Named, As, B}) when is_list(Named) -> pp({fun_t, _, Named, As, B}) when is_list(Named) ->
["(", pp(Named ++ As), ") => ", pp(B)]; ["(", pp(Named ++ As), ") => ", pp(B)].
pp(Other) ->
io_lib:format("~p", [Other]).
%% -- Pre-type checking desugaring ------------------------------------------- %% -- Pre-type checking desugaring -------------------------------------------
File diff suppressed because it is too large Load Diff
+52 -129
View File
@@ -17,27 +17,22 @@
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode(). -spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
convert_typed(TypedTree, Options) -> convert_typed(TypedTree, Options) ->
{Payable, Name} = Name = case lists:last(TypedTree) of
case lists:last(TypedTree) of {contract, _, {con, _, Con}, _} -> Con;
{contract, Attrs, {con, _, Con}, _} -> _ -> gen_error(last_declaration_must_be_contract)
{proplists:get_value(payable, Attrs, false), Con};
_ ->
gen_error(last_declaration_must_be_contract)
end, end,
NewIcode = aeso_icode:set_payable(Payable, Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))),
aeso_icode:set_name(Name, aeso_icode:new(Options))),
Icode = code(TypedTree, NewIcode, Options),
deadcode_elimination(Icode). deadcode_elimination(Icode).
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) -> code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
code(Rest, NewIcode, Options); code(Rest, NewIcode);
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) -> code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
%% TODO: nested namespaces %% TODO: nested namespaces
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)), NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)),
code(Rest, NewIcode, Options); code(Rest, NewIcode);
code([], Icode, Options) -> code([], Icode) ->
add_default_init_function(add_builtins(Icode), Options). add_default_init_function(add_builtins(Icode)).
%% Generate error on correct format. %% Generate error on correct format.
@@ -45,12 +40,10 @@ gen_error(Error) ->
error({code_errors, [Error]}). error({code_errors, [Error]}).
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
NoCode = proplists:get_value(no_code, Options, false),
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode), {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when NoCode -> Icode;
false when State /= {tuple, []} -> false when State /= {tuple, []} ->
gen_error(missing_init_function); gen_error(missing_init_function);
false -> false ->
@@ -91,7 +84,6 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
contract_to_icode(Rest, Icode2); contract_to_icode(Rest, Icode2);
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) -> contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++ FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
[ payable || proplists:get_value(payable, Attrib, false) ] ++
[ private || is_private(Attrib, Icode) ], [ private || is_private(Attrib, Icode) ],
%% TODO: Handle types %% TODO: Handle types
FunName = ast_id(Name), FunName = ast_id(Name),
@@ -110,6 +102,13 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
QName = aeso_icode:qualify(Name, Icode), QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode), NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode); contract_to_icode(Rest, NewIcode);
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% OBS! This code ignores the letrec structure of the source,
%% because the back end treats ALL declarations as recursive! We
%% need to decide whether to (a) modify the back end to respect
%% the letrec structure, or (b) (preferably) modify the front end
%% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode; contract_to_icode([], Icode) -> Icode;
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) -> contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode); contract_to_icode(Code, Icode);
@@ -149,11 +148,10 @@ ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_n
ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) -> ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) ->
#prim_balance{ address = ast_body(Address, Icode) }; #prim_balance{ address = ast_body(Address, Icode) };
ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) -> ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]); #prim_block_hash{ height = ast_body(Height, Icode) };
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) -> ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left; prim_gas_left;
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address; ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address }; ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin; ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller; ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
@@ -181,9 +179,8 @@ ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
%% Abort %% Abort
ast_body(?id_app("abort", [String], _, _), Icode) -> ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, abort} },
ast_body(?id_app("require", [Bool, String], _, _), Icode) -> args = [ast_body(String, Icode)] };
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Authentication %% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
@@ -227,17 +224,6 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0}, prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode))); [ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'}); ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'}); ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'}); ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
@@ -274,29 +260,22 @@ ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
[word, string, word, sign_t()], {tuple, []}); [word, string, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args), {Sign, [FromAddr, ToAddr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []}); [word, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args), {Sign, [Addr, NameHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [ast_body(Addr, Icode), ast_body(NameHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []}); [word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "subname"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Subnames]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_SUBNAME, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Subnames, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []});
ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'}); ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'});
ast_body({qid, _, ["AENS", "preclaim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.preclaim'}); ast_body({qid, _, ["AENS", "preclaim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.preclaim'});
ast_body({qid, _, ["AENS", "claim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.claim'}); ast_body({qid, _, ["AENS", "claim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.claim'});
ast_body({qid, _, ["AENS", "transfer"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.transfer'}); ast_body({qid, _, ["AENS", "transfer"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.transfer'});
ast_body({qid, _, ["AENS", "revoke"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.revoke'}); ast_body({qid, _, ["AENS", "revoke"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.revoke'});
ast_body({qid, _, ["AENS", "subname"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.subname'});
%% Maps %% Maps
@@ -362,26 +341,16 @@ ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto %% Crypto
ast_body(?qid_app(["Crypto", "verify_sig"], [Msg, PK, Sig], _, _), Icode) -> ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0}, prim_call(?PRIM_CALL_CRYPTO_ECVERIFY, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word); [word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "verify_sig_secp256k1"], [Msg, PK, Sig], _, _), Icode) -> ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0}, prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word); [bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, Addr, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)],
[word, bytes_t(20), bytes_t(65)], word);
ast_body(?qid_app(["Crypto", "ecrecover_secp256k1"], [Msg, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Sig, Icode)],
[word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20)));
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) -> ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode); generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) -> ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
@@ -396,11 +365,13 @@ ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
%% Strings %% Strings
%% -- String length %% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_length} },
args = [ast_body(String, Icode)] };
%% -- String concat %% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]); #funcall{ function = #var_ref{ name = {builtin, string_concat} },
args = [ast_body(String1, Icode), ast_body(String2, Icode)] };
%% -- String hash (sha3) %% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
@@ -439,22 +410,6 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) -> ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]); builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_payable"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
@@ -502,7 +457,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
Gas = proplists:get_value("gas", ArgOpts ++ Defaults), Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
Value = proplists:get_value("value", ArgOpts ++ Defaults), Value = proplists:get_value("value", ArgOpts ++ Defaults),
OutType = ast_typerep(OutT, Icode), OutType = ast_typerep(OutT, Icode),
<<TypeHash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType), <<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
%% The function is represented by its type hash (which includes the name) %% The function is represented by its type hash (which includes the name)
Fun = #integer{value = TypeHash}, Fun = #integer{value = TypeHash},
#prim_call_contract{ #prim_call_contract{
@@ -530,10 +485,6 @@ ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) -> ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]}; #tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app, _, {'..', _}, [A, B]}, Icode) ->
#funcall
{ function = #var_ref{ name = ["ListInternal", "from_to"] }
, args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app,As,Fun,Args}, Icode) ->
case aeso_syntax:get_ann(format, As) of case aeso_syntax:get_ann(format, As) of
infix -> infix ->
@@ -548,24 +499,6 @@ ast_body({app,As,Fun,Args}, Icode) ->
#funcall{function=ast_body(Fun, Icode), #funcall{function=ast_body(Fun, Icode),
args=[ast_body(A, Icode) || A <- Args]} args=[ast_body(A, Icode) || A <- Args]}
end; end;
ast_body({list_comp, _, Yield, []}, Icode) ->
#list{elems = [ast_body(Yield, Icode)]};
ast_body({list_comp, As, Yield, [{comprehension_bind, {typed, Arg, ArgType}, BindExpr}|Rest]}, Icode) ->
#funcall
{ function = #var_ref{ name = ["ListInternal", "flat_map"] }
, args =
[ #lambda{ args=[#arg{name = ast_id(Arg), type = ast_type(ArgType, Icode)}]
, body = ast_body({list_comp, As, Yield, Rest}, Icode)
}
, ast_body(BindExpr, Icode)
]
};
ast_body({list_comp, As, Yield, [{comprehension_if, AsIF, Cond}|Rest]}, Icode) ->
ast_body({'if', AsIF, Cond, {list_comp, As, Yield, Rest}, {list, As, []}}, Icode);
ast_body({list_comp, As, Yield, [LV = {letval, _, _, _, _}|Rest]}, Icode) ->
ast_body({block, As, [LV, {list_comp, As, Yield, Rest}]}, Icode);
ast_body({list_comp, As, Yield, [LF = {letfun, _, _, _, _, _}|Rest]}, Icode) ->
ast_body({block, As, [LF, {list_comp, As, Yield, Rest}]}, Icode);
ast_body({'if',_,Dec,Then,Else}, Icode) -> ast_body({'if',_,Dec,Then,Else}, Icode) ->
#ifte{decision = ast_body(Dec, Icode) #ifte{decision = ast_body(Dec, Icode)
,then = ast_body(Then, Icode) ,then = ast_body(Then, Icode)
@@ -579,8 +512,6 @@ ast_body({switch,_,A,Cases}, Icode) ->
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) -> ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode), #switch{expr=ast_body(E, Icode),
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]}; cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
ast_body({block,_,[]}, _Icode) -> ast_body({block,_,[]}, _Icode) ->
#tuple{cpts=[]}; #tuple{cpts=[]};
ast_body({block,_,[E]}, Icode) -> ast_body({block,_,[E]}, Icode) ->
@@ -648,30 +579,19 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
_ when not Monomorphic -> _ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type}); gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
OtherType -> string ->
Neg = case Op of Neg = case Op of
'==' -> fun(X) -> X end; '==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare, Ann, Op, Type})
end, end,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
Builtin = args = [ast_body(A, Icode), ast_body(B, Icode)] });
case OtherType of _ -> gen_error({cant_compare, Ann, Op, Type})
string ->
builtin_call(str_equal, Args);
{tuple, Types} ->
case lists:usort(Types) of
[word] ->
builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]);
_ -> gen_error({cant_compare, Ann, Op, Type})
end;
_ ->
gen_error({cant_compare, Ann, Op, Type})
end,
Neg(Builtin)
end; end;
ast_binop('++', _, A, B, Icode) -> ast_binop('++', _, A, B, Icode) ->
builtin_call(list_concat, [ast_body(A, Icode), ast_body(B, Icode)]); #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -738,7 +658,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
true -> true ->
PrimBin = binary:encode_unsigned(Prim), PrimBin = binary:encode_unsigned(Prim),
ArgType = {tuple, ArgTypes}, ArgType = {tuple, ArgTypes},
<<TH:256>> = aeb_aevm_abi:function_type_hash(PrimBin, ArgType, OutType), <<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
TH; TH;
false -> false ->
0 0
@@ -777,8 +697,8 @@ ast_typerep({qid, _, Name}, Icode) ->
ast_typerep({con, _, _}, _) -> ast_typerep({con, _, _}, _) ->
word; %% Contract type word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) -> ast_typerep({bytes_t, _, Len}, _) ->
bytes_t(Len); {bytes, Len};
ast_typerep({app_t, _, {I, _, Name}, Args}, Icode) when I =:= id; I =:= qid -> ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ], ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode); lookup_type_id(Name, ArgReps, Icode);
ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) -> ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) ->
@@ -806,8 +726,7 @@ ttl_t(Icode) ->
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode). ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
sign_t() -> bytes_t(64). sign_t() -> bytes_t(64).
bytes_t(Len) when Len =< 32 -> word; bytes_t(Len) -> {bytes, Len}.
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
get_signature_arg(Args0) -> get_signature_arg(Args0) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
@@ -841,6 +760,8 @@ type_value({list, A}) ->
type_value({tuple, As}) -> type_value({tuple, As}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG }, #tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG },
#list{ elems = [ type_value(A) || A <- As ] }] }; #list{ elems = [ type_value(A) || A <- As ] }] };
type_value({bytes, Len}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_BYTES_TAG }, #integer{ value = Len }] };
type_value({variant, Cs}) -> type_value({variant, Cs}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG }, #tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG },
#list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] }; #list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] };
@@ -867,11 +788,13 @@ has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if not an 'entrypoint', or if it's not defined in the %% A function is private if marked 'private' or 'internal', or if it's not
%% main contract name space. (NOTE: changes when we introduce inheritance). %% defined in the main contract name space. (NOTE: changes when we introduce
%% inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) -> is_private(Ann, #{ contract_name := MainContract } = Icode) ->
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode), {_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
not proplists:get_value(entrypoint, Ann, false) orelse proplists:get_value(private, Ann, false) orelse
proplists:get_value(internal, Ann, false) orelse
MainContract /= CurrentNamespace. MainContract /= CurrentNamespace.
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
+18 -95
View File
@@ -44,9 +44,7 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker];
builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> []. builtin_deps1(_) -> [].
dep_closure(Deps) -> dep_closure(Deps) ->
@@ -62,14 +60,12 @@ v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}. option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}. option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }). -define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}). -define(I(X), {integer, X}).
-define(V(X), v(X)). -define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)). -define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}). -define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}). -define(DEREF(Var, Ptr, Body), {switch, v(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)). -define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})). -define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)). -define(BYTE(Ix, Word), op('byte', Ix, Word)).
@@ -95,6 +91,12 @@ operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I}; operand(I) when is_integer(I) -> {integer, I};
operand(T) -> T. operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) -> check_event_type(Icode) ->
case maps:get(event_type, Icode) of case maps:get(event_type, Icode) of
{variant_t, Cons} -> {variant_t, Cons} ->
@@ -115,12 +117,11 @@ check_event_type(EvtName, Ix, Type, Icode) ->
catch _:_ -> catch _:_ ->
error({EvtName, could_not_resolve_type, Type}) error({EvtName, could_not_resolve_type, Type})
end, end,
case {Ix, VMType, Type} of case {Ix, VMType} of
{indexed, word, _} -> ok; {indexed, word} -> ok;
{notindexed, string, _} -> ok; {notindexed, string} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok; {indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType}); {notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end. end.
bfun(B, {IArgs, IExpr, IRet}) -> bfun(B, {IArgs, IExpr, IRet}) ->
@@ -130,8 +131,6 @@ builtin_function(BF) ->
case BF of case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT)); {event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort()); abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type)); {map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put()); map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete()); map_delete -> bfun(BF, builtin_map_delete());
@@ -159,9 +158,6 @@ builtin_function(BF) ->
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_()) string_reverse_ -> bfun(BF, builtin_string_reverse_())
end. end.
@@ -175,23 +171,16 @@ builtin_event(EventT) ->
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end, VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end, ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack. Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]}; fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) -> ([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end, end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause = Clause =
fun(_Tag, {con, _, Con}, IxTypes) -> fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ], Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ], EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)), {event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end, end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end, Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
@@ -212,17 +201,6 @@ builtin_abort() ->
A(?REVERT)]}, %% Stack: 0,Ptr A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}. {tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives %% Map primitives
builtin_map_lookup(Type) -> builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type), Ret = aeso_icode:option_typerep(Type),
@@ -459,10 +437,6 @@ builtin_baseX_int_pad(X = 10) ->
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]), ?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])}, ?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word}; word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) -> builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}], {[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0), {ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
@@ -497,57 +471,6 @@ builtin_baseX_digits(X) ->
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}), {ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}. word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))),
{seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]},
{inline_asm, []}},
{ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker,
[?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
}
]},
word}.
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]),
{inline_asm, [?A(?POP)]},
?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
Work = fun(I) ->
[?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])),
{inline_asm, [?A(?POP)]}]
end,
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++
[?V(ret)]}),
string}.
builtin_string_reverse() -> builtin_string_reverse() ->
{[{"s", string}], {[{"s", string}],
?DEREF(n, s, ?DEREF(n, s,
+169 -264
View File
@@ -12,16 +12,12 @@
, file/2 , file/2
, from_string/2 , from_string/2
, check_call/4 , check_call/4
, create_calldata/3 %% deprecated , create_calldata/3
, create_calldata/4
, version/0 , version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/4
, to_sophia_value/5 , to_sophia_value/5
, decode_calldata/3 %% deprecated , decode_calldata/3
, decode_calldata/4
, parse/2
, add_include_path/2
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -35,8 +31,6 @@
| pp_icode | pp_icode
| pp_assembler | pp_assembler
| pp_bytecode | pp_bytecode
| no_code
| {backend, aevm | fate}
| {include, {file_system, [string()]} | | {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}} {explicit_files, #{string() => binary()}}}
| {src_file, string()}. | {src_file, string()}.
@@ -67,11 +61,12 @@ version() ->
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). Dir = filename:dirname(Filename),
{ok, Cwd} = file:get_cwd(),
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options0) -> file(File, Options) ->
Options = add_include_path(File, Options0),
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} -> {error, Error} ->
@@ -79,24 +74,24 @@ file(File, Options0) ->
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)} {error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
end. end.
add_include_path(File, Options) ->
case lists:keymember(include, 1, Options) of
true -> Options;
false ->
Dir = filename:dirname(File),
{ok, Cwd} = file:get_cwd(),
[{include, {file_system, [Cwd, Dir]}} | Options]
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(Contract, Options) -> from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) #{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
catch catch
%% The compiler errors. %% The compiler errors.
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -109,56 +104,18 @@ from_string(Backend, ContractString, Options) ->
%% General programming errors in the compiler just signal error. %% General programming errors in the compiler just signal error.
end. end.
from_string1(aevm, ContractString, Options) -> -spec string_to_icode(string(), [option()]) -> map().
#{icode := Icode} = string_to_code(ContractString, Options), string_to_icode(ContractString, Options) ->
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode)
}};
from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
}}.
-spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options), pp_sophia_code(Ast, Options),
pp_ast(Ast, Options), pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of Icode = ast_to_icode(TypedAst, Options),
aevm -> pp_icode(Icode, Options),
Icode = ast_to_icode(TypedAst, Options), #{ typed_ast => TypedAst,
pp_icode(Icode, Options), type_env => TypeEnv,
#{ icode => Icode, icode => Icode }.
typed_ast => TypedAst,
type_env => TypeEnv};
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
#{ fcode => Fcode,
typed_ast => TypedAst,
type_env => TypeEnv}
end.
join_errors(Prefix, Errors, Pfun) -> join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
@@ -173,15 +130,14 @@ join_errors(Prefix, Errors, Pfun) ->
%% terms for the arguments. %% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has %% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T) %% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} -spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()}
| {ok, string(), [term()]}
| {error, term()}
when Type :: term(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of PatchFun = fun(T) -> {tuple, [typerep, T]} end,
case check_call(Source, FunName, Args, Options, PatchFun) of
Err = {error, _} when Args == [] -> Err = {error, _} when Args == [] ->
%% Try with default init-function %% Try with default init-function
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of case check_call(insert_init_function(Source, Options), FunName, Args, Options, PatchFun) of
{error, _} -> Err; %% The first error is most likely better... {error, _} -> Err; %% The first error is most likely better...
Res -> Res Res -> Res
end; end;
@@ -189,46 +145,27 @@ check_call(Source, "init" = FunName, Args, Options) ->
Res Res
end; end;
check_call(Source, FunName, Args, Options) -> check_call(Source, FunName, Args, Options) ->
check_call1(Source, FunName, Args, Options). PatchFun = fun(T) -> T end,
check_call(Source, FunName, Args, Options, PatchFun).
check_call1(ContractString0, FunName, Args, Options) -> check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try try
case proplists:get_value(backend, Options, aevm) of %% First check the contract without the __call function
aevm -> #{} = string_to_icode(ContractString0, Options),
%% First check the contract without the __call function ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{} = string_to_code(ContractString0, Options), #{typed_ast := TypedAst,
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options), icode := Icode} = string_to_icode(ContractString, Options),
#{typed_ast := TypedAst, {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
icode := Icode} = string_to_code(ContractString, Options), ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), RetVMType = case RetType of
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], {id, _, "_"} -> any;
RetVMType = case RetType of _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
{id, _, "_"} -> any; end,
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) #{ functions := Funs } = Icode,
end, ArgIcode = get_arg_icode(Funs),
#{ functions := Funs } = Icode, ArgTerms = [ icode_to_term(T, Arg) ||
ArgIcode = get_arg_icode(Funs), {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
ArgTerms = [ icode_to_term(T, Arg) || {ok, FunName, {ArgVMTypes, PatchFun(RetVMType)}, ArgTerms}
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 =
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)}; {error, join_errors("Parse errors", Errors, fun (E) -> E end)};
@@ -242,32 +179,16 @@ check_call1(ContractString0, FunName, Args, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) ->
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
{def, _FName, Args} = Body,
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
first_none_match(_CallName, _Hashes, []) ->
error(unable_to_find_unique_call_name);
first_none_match(CallName, Hashes, [Char|Chars]) ->
case not lists:member(aeb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
true ->
CallName;
false ->
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
end.
%% Add the __call function to a contract. %% Add the __call function to a contract.
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string(). -spec insert_call_function(string(), string(), [string()], options()) -> string().
insert_call_function(Code, Call, FunName, Args, Options) -> insert_call_function(Code, FunName, Args, Options) ->
Ast = parse(Code, Options), Ast = parse(Code, Options),
Ind = last_contract_indent(Ast), Ind = last_contract_indent(Ast),
lists:flatten( lists:flatten(
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), lists:duplicate(Ind, " "),
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n" "function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]). ]).
-spec insert_init_function(string(), options()) -> string(). -spec insert_init_function(string(), options()) -> string().
@@ -277,7 +198,7 @@ insert_init_function(Code, Options) ->
lists:flatten( lists:flatten(
[ Code, [ Code,
"\n\n", "\n\n",
lists:duplicate(Ind, " "), "entrypoint init() = ()\n" lists:duplicate(Ind, " "), "function init() = ()\n"
]). ]).
last_contract_indent(Decls) -> last_contract_indent(Decls) ->
@@ -289,60 +210,38 @@ last_contract_indent(Decls) ->
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) -> -spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) -> to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]). to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) -> -spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(_, _, error, Err, _Options) -> to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}}; {ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) -> to_sophia_value(_, _, revert, Data, _Options) ->
case proplists:get_value(backend, Options, aevm) of case aeb_heap:from_binary(string, Data) of
aevm -> {ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
case aeb_heap:from_binary(string, Data) of {error, _} = Err -> Err
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err
end;
fate ->
Err = aeb_fate_encoding:deserialize(Data),
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
end; end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) -> to_sophia_value(ContractString, FunName, ok, Data, Options) ->
Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), #{ typed_ast := TypedAst,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, Options),
{ok, _, Type0} = get_decode_type(FunName, TypedAst), {ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case proplists:get_value(backend, Options, aevm) of case aeb_heap:from_binary(VmType, Data) of
aevm -> {ok, VmValue} ->
Icode = maps:get(icode, Code), try
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), {ok, translate_vm_value(VmType, Type, VmValue)}
case aeb_heap:from_binary(VmType, Data) of catch throw:cannot_translate_to_sophia ->
{ok, VmValue} -> Type0Str = prettypr:format(aeso_pretty:type(Type0)),
try {error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)} [Data, VmType, Type0Str]))],
catch throw:cannot_translate_to_sophia -> fun (E) -> E end)}
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end; end;
fate -> {error, _Err} ->
try {error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} fun(E) -> E end)}
catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))],
fun (E) -> E end)};
_:R ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))}
end
end end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -357,91 +256,101 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
address_literal(Type, N) -> {Type, [], <<N:256>>}.
%% TODO: somewhere else
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 ->
{bytes, [], <<Val:Len/unit:8>>};
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
none -> {con, [], "None"};
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [translate_vm_value(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
translate_vm_value(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
translate_vm_value(VmTypes, ConType, Args);
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) -> -spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()} {ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}. | {error, term()}.
create_calldata(Code, Fun, Args) -> create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]). case check_call(Code, Fun, Args, []) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) -> aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{ok, binary()} {error, _} = Err -> Err
| {error, term()}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end
end. end.
-spec decode_calldata(string(), string(), binary()) -> -spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]} {ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}. | {error, term()}.
decode_calldata(ContractString, FunName, Calldata) -> decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), #{ typed_ast := TypedAst,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, []),
{ok, Args, _} = get_decode_type(FunName, TypedAst), {ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end, DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args), ArgTypes = lists:map(DropArg, Args),
Type0 = {tuple_t, [], ArgTypes}, Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case proplists:get_value(backend, Options, aevm) of case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
aevm -> {ok, {_, VmValue}} ->
Icode = maps:get(icode, Code), try
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), {tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of {ok, ArgTypes, Values}
{ok, {_, VmValue}} -> catch throw:cannot_translate_to_sophia ->
try Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue), {error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
%% Values are Sophia expressions in AST format [VmValue, VmType, Type0Str]))],
{ok, ArgTypes, Values} fun (E) -> E end)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end; end;
fate -> {error, _Err} ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of {error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
{ok, FateArgs} -> fun(E) -> E end)}
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
[FateArgs, Type0Str]))],
fun (E) -> E end)}
end;
{error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"],
fun(E) -> E end)}
end
end end
catch catch
error:{parse_errors, Errors} -> error:{parse_errors, Errors} ->
@@ -456,6 +365,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
fun (E) -> io_lib:format("~p", [E]) end)} fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args; [Args] -> Args;
@@ -481,11 +391,7 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
(_) -> [] end, (_) -> [] end,
case lists:flatmap(GetType, Defs) of case lists:flatmap(GetType, Defs) of
[{Args, Ret}] -> {ok, Args, Ret}; [{Args, Ret}] -> {ok, Args, Ret};
[] -> [] -> {error, missing_function}
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function}
end
end; end;
get_decode_type(FunName, [_ | Contracts]) -> get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract %% The __decode should be in the final contract
@@ -494,10 +400,13 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% Translate an icode value (error if not value) to an Erlang term that can be %% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary(). %% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N; icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) -> icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>, <<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str; Str;
icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 ->
Value;
icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32->
list_to_tuple([W || {integer, W} <- Words]);
icode_to_term({list, T}, {list, Vs}) -> icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ]; [ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) -> icode_to_term({tuple, Ts}, {tuple, Vs}) ->
@@ -541,9 +450,8 @@ to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) -> extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end, ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end, TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)), ArgTypesOnly(Args), TypeRep)
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions, || {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name), not is_tuple(Name),
not lists:member(private, Attrs) not lists:member(private, Attrs)
@@ -567,7 +475,9 @@ pp(Code, Options, Option, PPFun) ->
ok ok
end. end.
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% TODO: Tempoary parser hook below...
sophia_type_to_typerep(String) -> sophia_type_to_typerep(String) ->
{ok, Ast} = aeso_parser:type(String), {ok, Ast} = aeso_parser:type(String),
@@ -576,14 +486,9 @@ sophia_type_to_typerep(String) ->
catch _:_ -> {error, bad_type} catch _:_ -> {error, bad_type}
end. end.
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Options) -> parse(Text, Options) ->
parse(Text, sets:new(), Options).
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Included, Options) ->
%% Try and return something sensible here! %% Try and return something sensible here!
case aeso_parser:string(Text, Included, Options) of case aeso_parser:string(Text, Options) of
%% Yay, it worked! %% Yay, it worked!
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
%% Scan errors. %% Scan errors.
@@ -602,7 +507,6 @@ parse(Text, Included, Options) ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File])) parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end. end.
-spec parse_error(aeso_parse_lib:pos(), string()) -> none().
parse_error(Pos, ErrorString) -> parse_error(Pos, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]), Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}). error({parse_errors, [Error]}).
@@ -616,3 +520,4 @@ pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos}); pos_error({Line, Pos});
pos_error({File, Line, Pos}) -> pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]). io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
File diff suppressed because it is too large Load Diff
+1 -9
View File
@@ -13,7 +13,6 @@
pp/1, pp/1,
set_name/2, set_name/2,
set_namespace/2, set_namespace/2,
set_payable/2,
enter_namespace/2, enter_namespace/2,
get_namespace/1, get_namespace/1,
qualify/2, qualify/2,
@@ -49,7 +48,6 @@
, type_vars => #{ string() => aeb_aevm_data:type() } , type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag , constructors => #{ [string()] => integer() } %% name to tag
, options => [any()] , options => [any()]
, payable => boolean()
}. }.
pp(Icode) -> pp(Icode) ->
@@ -67,8 +65,7 @@ new(Options) ->
, types => builtin_types() , types => builtin_types()
, type_vars => #{} , type_vars => #{}
, constructors => builtin_constructors() , constructors => builtin_constructors()
, options => Options , options => Options}.
, payable => false }.
builtin_types() -> builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
@@ -78,7 +75,6 @@ builtin_types() ->
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
, "hash" => Word , "hash" => Word
, "unit" => fun([]) -> {tuple, []} end
, "signature" => fun([]) -> {tuple, [word, word]} end , "signature" => fun([]) -> {tuple, [word, word]} end
, "oracle" => fun([_, _]) -> word end , "oracle" => fun([_, _]) -> word end
, "oracle_query" => fun([_, _]) -> word end , "oracle_query" => fun([_, _]) -> word end
@@ -107,10 +103,6 @@ new_env() ->
set_name(Name, Icode) -> set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode). maps:put(contract_name, Name, Icode).
-spec set_payable(boolean(), icode()) -> icode().
set_payable(Payable, Icode) ->
maps:put(payable, Payable, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode(). -spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }. set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
+1 -3
View File
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) -> fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]}, ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep), <<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}. {integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top %% Expects two return addresses below N elements on the stack. Picks the top
@@ -343,8 +343,6 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
%% Environment primitives %% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) -> assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)]; [i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) -> assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)]; [i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) -> assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
+299
View File
@@ -0,0 +1,299 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Fate backend for Sophia compiler
%%% @end
%%% Created : 11 Jan 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_fate).
-include("aeso_icode.hrl").
-export([compile/2]).
%% -- Preamble ---------------------------------------------------------------
-define(TODO(What), error({todo, ?FILE, ?LINE, ?FUNCTION_NAME, What})).
-define(i(__X__), {immediate, __X__}).
-define(a, {stack, 0}).
-record(env, { args = [], stack = [], tailpos = true }).
%% -- Debugging --------------------------------------------------------------
%% debug(Options, Fmt) -> debug(Options, Fmt, []).
debug(Options, Fmt, Args) ->
case proplists:get_value(debug, Options, true) of
true -> io:format(Fmt, Args);
false -> ok
end.
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(ICode, Options) ->
#{ contract_name := _ContractName,
state_type := _StateType,
functions := Functions } = ICode,
SFuns = functions_to_scode(Functions, Options),
SFuns1 = optimize_scode(SFuns, Options),
to_basic_blocks(SFuns1, Options).
functions_to_scode(Functions, Options) ->
maps:from_list(
[ {list_to_binary(Name), function_to_scode(Name, Args, Body, Type, Options)}
|| {Name, _Ann, Args, Body, Type} <- Functions, Name /= "init" ]). %% TODO: skip init for now
function_to_scode(Name, Args, Body, Type, Options) ->
debug(Options, "Compiling ~p ~p : ~p ->\n ~p\n", [Name, Args, Type, Body]),
ArgTypes = [ icode_type_to_fate(T) || {_, T} <- Args ],
ResType = icode_type_to_fate(Type),
SCode = to_scode(init_env(Args), Body),
debug(Options, " scode: ~p\n", [SCode]),
{{ArgTypes, ResType}, SCode}.
%% -- Types ------------------------------------------------------------------
%% TODO: the Fate types don't seem to be specified anywhere...
icode_type_to_fate(word) -> integer;
icode_type_to_fate(string) -> string;
icode_type_to_fate({tuple, Types}) ->
{tuple, lists:map(fun icode_type_to_fate/1, Types)};
icode_type_to_fate({list, Type}) ->
{list, icode_type_to_fate(Type)};
icode_type_to_fate(typerep) -> typerep;
icode_type_to_fate(Type) -> ?TODO(Type).
%% -- Phase I ----------------------------------------------------------------
%% Icode to structured assembly
%% -- Environment functions --
init_env(Args) ->
#env{ args = Args, stack = [], tailpos = true }.
push_env(Type, Env) ->
Env#env{ stack = [{"_", Type} | Env#env.stack] }.
notail(Env) -> Env#env{ tailpos = false }.
lookup_var(#env{ args = Args, stack = S }, X) ->
case {keyfind_index(X, 1, S), keyfind_index(X, 1, Args)} of
{false, false} -> false;
{false, Arg} -> {arg, Arg};
{Local, _} -> {stack, Local}
end.
%% -- The compiler --
to_scode(_Env, #integer{ value = N }) ->
[aeb_fate_code:push(?i(N))]; %% Doesn't exist (yet), translated by desugaring
to_scode(Env, #var_ref{name = X}) ->
case lookup_var(Env, X) of
false -> error({unbound_variable, X, Env});
{stack, N} -> [aeb_fate_code:dup(?i(N))];
{arg, N} -> [aeb_fate_code:push({arg, N})]
end;
to_scode(Env, #binop{ op = Op, left = A, right = B }) ->
[ to_scode(notail(Env), B)
, to_scode(push_env(binop_type_r(Op), Env), A)
, binop_to_scode(Op) ];
to_scode(Env, #ifte{decision = Dec, then = Then, else = Else}) ->
[ to_scode(notail(Env), Dec)
, {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
to_scode(_Env, Icode) -> ?TODO(Icode).
%% -- Operators --
binop_types('+') -> {word, word};
binop_types('-') -> {word, word};
binop_types('==') -> {word, word};
binop_types(Op) -> ?TODO(Op).
%% binop_type_l(Op) -> element(1, binop_types(Op)).
binop_type_r(Op) -> element(2, binop_types(Op)).
binop_to_scode('+') -> add_a_a_a(); %% Optimization introduces other variants
binop_to_scode('-') -> sub_a_a_a();
binop_to_scode('==') -> eq_a_a_a().
% binop_to_scode(Op) -> ?TODO(Op).
add_a_a_a() -> aeb_fate_code:add(?a, ?a, ?a).
sub_a_a_a() -> aeb_fate_code:sub(?a, ?a, ?a).
eq_a_a_a() -> aeb_fate_code:eq(?a, ?a, ?a).
%% -- Phase II ---------------------------------------------------------------
%% Optimize
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
flatten_s({ifte, Then, Else}) -> {ifte, flatten(Then), flatten(Else)};
flatten_s(I) -> I.
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
Code0 = flatten(Code),
debug(Options, "Optimizing ~s\n", [Name]),
debug(Options, " original : ~p\n", [Code0]),
Code1 = simplify(Code0),
debug(Options, " simplified: ~p\n", [Code1]),
Code2 = desugar(Code1),
debug(Options, " desugared : ~p\n", [Code2]),
{{Args, Res}, Code2}.
simplify([]) -> [];
simplify([I | Code]) ->
simpl_top(simpl_s(I), simplify(Code)).
simpl_s({ifte, Then, Else}) ->
{ifte, simplify(Then), simplify(Else)};
simpl_s(I) -> I.
%% add_i 0 --> nop
simpl_top({'ADD', _, ?i(0), _}, Code) -> Code;
%% push n, add_a --> add_i n
simpl_top({'PUSH', ?a, ?i(N)},
[{'ADD', ?a, ?a, ?a} | Code]) ->
simpl_top( aeb_fate_code:add(?a, ?i(N), ?a), Code);
%% push n, add_i m --> add_i (n + m)
simpl_top({'PUSH', ?a, ?i(N)}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top(aeb_fate_code:push(?i(N + M)), Code);
%% add_i n, add_i m --> add_i (n + m)
simpl_top({'ADD', ?a, ?i(N), ?a}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top({'ADD', ?a, ?i(N + M), ?a}, Code);
simpl_top(I, Code) -> [I | Code].
%% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_code:inc()];
desugar({ifte, Then, Else}) -> [{ifte, desugar(Then), desugar(Else)}];
desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I].
%% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks
to_basic_blocks(Funs, Options) ->
maps:from_list([ {Name, {{Args, Res},
bb(Name, Code ++ [aeb_fate_code:return()], Options)}}
|| {Name, {{Args, Res}, Code}} <- maps:to_list(Funs) ]).
bb(Name, Code, Options) ->
Blocks0 = blocks(Code),
Blocks = optimize_blocks(Blocks0),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
BBs = [ set_labels(Labels, B) || B <- Blocks ],
debug(Options, "Final code for ~s:\n ~p\n", [Name, BBs]),
maps:from_list(BBs).
%% -- Break up scode into basic blocks --
blocks(Code) ->
Top = make_ref(),
blocks([{Top, Code}], []).
blocks([], Acc) ->
lists:reverse(Acc);
blocks([{Ref, Code} | Blocks], Acc) ->
block(Ref, Code, [], Blocks, Acc).
block(Ref, [], CodeAcc, Blocks, BlockAcc) ->
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
block(Ref, [{ifte, Then, Else} | Code], Acc, Blocks, BlockAcc) ->
ThenLbl = make_ref(),
RestLbl = make_ref(),
block(Ref, Else ++ [{jump, RestLbl}],
[{jumpif, ThenLbl} | Acc],
[{ThenLbl, Then ++ [{jump, RestLbl}]},
{RestLbl, Code} | Blocks],
BlockAcc);
block(Ref, [I | Code], Acc, Blocks, BlockAcc) ->
block(Ref, Code, [I | Acc], Blocks, BlockAcc).
%% -- Reorder, inline, and remove dead blocks --
optimize_blocks(Blocks) ->
%% We need to look at the last instruction a lot, so reverse all blocks.
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
RBlocks = Rev(Blocks),
RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
RBlocks3 = remove_dead_blocks(RBlocks2),
Rev(RBlocks3).
%% Choose the next block based on the final jump.
reorder_blocks([], Acc) ->
lists:reverse(Acc);
reorder_blocks([{Ref, Code} | Blocks], Acc) ->
reorder_blocks(Ref, Code, Blocks, Acc).
reorder_blocks(Ref, Code, Blocks, Acc) ->
Acc1 = [{Ref, Code} | Acc],
case Code of
['RETURN'|_] -> reorder_blocks(Blocks, Acc1);
[{'RETURNR', _}|_] -> reorder_blocks(Blocks, Acc1);
[{jump, L}|_] ->
NotL = fun({L1, _}) -> L1 /= L end,
case lists:splitwith(NotL, Blocks) of
{Blocks1, [{L, Code1} | Blocks2]} ->
reorder_blocks(L, Code1, Blocks1 ++ Blocks2, Acc1);
{_, []} -> reorder_blocks(Blocks, Acc1)
end
end.
%% Inline short blocks ( 2 instructions)
inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
case maps:get(L, BlockMap, nocode) of
Dest when length(Dest) < 3 ->
%% Remove Ref to avoid infinite loops
inline_block(maps:remove(Ref, BlockMap), L, Dest) ++ Code;
_ -> Code0
end;
inline_block(_, _, Code) -> Code.
%% Remove unused blocks
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
BlockMap = maps:from_list(Blocks),
LiveBlocks = chase_labels([Top], BlockMap, #{}),
[ B || B = {L, _} <- Blocks, maps:is_key(L, LiveBlocks) ].
chase_labels([], _, Live) -> Live;
chase_labels([L | Ls], Map, Live) ->
Code = maps:get(L, Map),
Jump = fun({jump, A}) -> [A || not maps:is_key(A, Live)];
({jumpif, A}) -> [A || not maps:is_key(A, Live)];
(_) -> [] end,
New = lists:flatmap(Jump, Code),
chase_labels(New ++ Ls, Map, Live#{ L => true }).
%% -- Translate label refs to indices --
set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
set_labels(Labels, {jump, Ref}) -> aeb_fate_code:jump(maps:get(Ref, Labels));
set_labels(Labels, {jumpif, Ref}) -> aeb_fate_code:jumpif(?a, maps:get(Ref, Labels));
set_labels(_, I) -> I.
%% -- Helpers ----------------------------------------------------------------
with_ixs(Xs) ->
lists:zip(lists:seq(0, length(Xs) - 1), Xs).
keyfind_index(X, J, Xs) ->
case [ I || {I, E} <- with_ixs(Xs), X == element(J, E) ] of
[I | _] -> I;
[] -> false
end.
+47 -129
View File
@@ -6,8 +6,6 @@
-export([string/1, -export([string/1,
string/2, string/2,
string/3,
hash_include/2,
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
@@ -16,24 +14,15 @@
| {error, {aeso_parse_lib:pos(), atom(), term()}} | {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}. | {error, {aeso_parse_lib:pos(), atom()}}.
-type include_hash() :: {string(), binary()}.
-spec string(string()) -> parse_result(). -spec string(string()) -> parse_result().
string(String) -> string(String) ->
string(String, sets:new(), []). string(String, []).
-spec string(string(), aeso_compiler:options()) -> parse_result(). -spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) -> string(String, Opts) ->
case lists:keyfind(src_file, 1, Opts) of
{src_file, File} -> string(String, sets:add_element(File, sets:new()), Opts);
false -> string(String, sets:new(), Opts)
end.
-spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result().
string(String, Included, Opts) ->
case parse_and_scan(file(), String, Opts) of case parse_and_scan(file(), String, Opts) of
{ok, AST} -> {ok, AST} ->
expand_includes(AST, Included, Opts); expand_includes(AST, Opts);
Err = {error, _} -> Err = {error, _} ->
Err Err
end. end.
@@ -57,9 +46,8 @@ decl() ->
choice( choice(
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -72,22 +60,13 @@ decl() ->
, ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5}) , ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5})
%% Function declarations %% Function declarations
, ?RULE(modifiers(), fun_or_entry(), id(), tok(':'), type(), add_modifiers(_1, _2, {fun_decl, get_ann(_2), _3, _5})) , ?RULE(modifiers(), keyword(function), id(), tok(':'), type(), add_modifiers(_1, {fun_decl, _2, _3, _5}))
, ?RULE(modifiers(), fun_or_entry(), fundef(), add_modifiers(_1, _2, set_pos(get_pos(get_ann(_2)), _3))) , ?RULE(modifiers(), keyword(function), fundef(), add_modifiers(_1, set_pos(get_pos(_2), _3)))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])). ])).
fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]).
modifiers() -> modifiers() ->
many(choice([token(stateful), token(payable), token(private), token(public)])). many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Entry = {entrypoint, _}, Node) ->
add_modifiers(Mods ++ [Entry], Node);
add_modifiers(Mods, {function, _}, Node) ->
add_modifiers(Mods, Node).
add_modifiers([], Node) -> Node; add_modifiers([], Node) -> Node;
add_modifiers(Mods = [Tok | _], Node) -> add_modifiers(Mods = [Tok | _], Node) ->
@@ -108,7 +87,7 @@ constructors() ->
sep1(constructor(), tok('|')). sep1(constructor(), tok('|')).
constructor() -> %% TODO: format for Con() vs Con constructor() -> %% TODO: format for Con() vs Con
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}), choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})). ?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
con_args() -> paren_list(con_arg()). con_args() -> paren_list(con_arg()).
@@ -120,7 +99,9 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations ------------------------------------------------------- %% -- Let declarations -------------------------------------------------------
letdecl() -> letdecl() ->
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)). choice(
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
letdef() -> choice(valdef(), fundef()). letdef() -> choice(valdef(), fundef()).
@@ -150,10 +131,9 @@ type() -> ?LAZY_P(type100()).
type100() -> type200(). type100() -> type200().
type200() -> type200() ->
?RULE(many({type300(), keyword('=>')}), type300(), fun_t(_1, _2)). ?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type300() -> type400().
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
type400() -> type400() ->
choice( choice(
@@ -168,17 +148,11 @@ type400() ->
typeAtom() -> typeAtom() ->
?LAZY_P(choice( ?LAZY_P(choice(
[ parens(type()) [ id(), token(con), token(qcon), token(qid), tvar()
, args_t() , ?RULE(keyword('('), comma_sep(type()), tok(')'), tuple_t(_1, _2))
, id(), token(con), token(qcon), token(qid), tvar()
])). ])).
args_t() -> fun_domain() -> ?RULE(?LAZY_P(type300()), fun_domain(_1)).
?LAZY_P(choice(
[ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
%% Singleton case handled separately
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
])).
%% -- Statements ------------------------------------------------------------- %% -- Statements -------------------------------------------------------------
@@ -239,27 +213,13 @@ exprAtom() ->
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true} , {bool, keyword(true), true}
, {bool, keyword(false), false} , {bool, keyword(false), false}
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, {list, [], bracket_list(Expr)} , {list, [], bracket_list(Expr)}
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
]) ])
end). end).
comprehension_exp() ->
?LAZY_P(choice(
[ comprehension_bind()
, letdecl()
, comprehension_if()
])).
comprehension_if() ->
?RULE(keyword('if'), parens(expr()), {comprehension_if, _1, _2}).
comprehension_bind() ->
?RULE(id(), tok('<-'), expr(), {comprehension_bind, _1, _3}).
arg_expr() -> arg_expr() ->
?LAZY_P( ?LAZY_P(
choice([ ?RULE(id(), tok('='), expr(), {named_arg, [], _1, _3}) choice([ ?RULE(id(), tok('='), expr(), {named_arg, [], _1, _3})
@@ -296,20 +256,14 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []}; record([]) -> {map, [], []};
record(Fs) -> record(Fs) ->
case record_or_map(Fs) of case record_or_map(Fs) of
record -> record -> {record, get_ann(hd(Fs)), Fs};
Fld = fun({field, _, [_], _} = F) -> F;
({field, Ann, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
({field, Ann, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
map -> map ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val}; KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, FAnn, LV, Id, _}) -> ({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id)); bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, FAnn, LV, _}) -> ({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end, bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
{map, Ann, lists:map(KV, Fs)} {map, Ann, lists:map(KV, Fs)}
end. end.
@@ -487,7 +441,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) -> build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
{'if', Ann, Cond, Then, Else}; {'if', Ann, Cond, Then, Else};
build_if(Ann, Cond, Then, []) -> build_if(Ann, Cond, Then, []) ->
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}. {'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) -> else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]); else_branches(Stmts, [Elif | Acc]);
@@ -500,14 +454,16 @@ tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}. tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
fun_t(Domains, Type) -> fun_t(Domains, Type) ->
lists:foldr(fun({{args_t, _, Dom}, Ann}, T) -> {fun_t, Ann, [], Dom, T}; lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
({Dom, Ann}, T) -> {fun_t, Ann, [], [Dom], T} end,
Type, Domains). Type, Domains).
tuple_e(Ann, []) -> {unit, Ann};
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}. tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}. %% TODO: not nice
fun_domain({tuple_t, _, Args}) -> Args;
fun_domain(T) -> [T].
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({app, Ann, Con = {'::', _}, Es}) -> parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
@@ -522,6 +478,7 @@ parse_pattern({record, Ann, Fs}) ->
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)}; {record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
parse_pattern(E = {con, _, _}) -> E; parse_pattern(E = {con, _, _}) -> E;
parse_pattern(E = {id, _, _}) -> E; parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E; parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E; parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E; parse_pattern(E = {bytes, _, _}) -> E;
@@ -538,48 +495,37 @@ return_error({no_file, L, C}, Err) ->
return_error({F, L, C}, Err) -> return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])). fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). return_error(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> no_return().
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E), ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])). prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions ------------------------------------------------------- %% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes(AST, Included, Opts) -> expand_includes([], Acc, _Opts) ->
Ann = [{origin, system}],
AST1 = [ {include, Ann, {string, Ann, File}}
|| File <- lists:usort(auto_imports(AST)) ] ++ AST,
expand_includes(AST1, Included, [], Opts).
expand_includes([], _Included, Acc, _Opts) ->
{ok, lists:reverse(Acc)}; {ok, lists:reverse(Acc)};
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
case get_include_code(File, Ann, Opts) of case read_file(File, Opts) of
{ok, Code} -> {ok, Bin} ->
Hashed = hash_include(File, Code), Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case sets:is_element(Hashed, Included) of case string(binary_to_list(Bin), Opts1) of
false -> {ok, AST1} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), expand_includes(AST1 ++ AST, Acc, Opts);
Included1 = sets:add_element(Hashed, Included), Err = {error, _} ->
case parse_and_scan(file(), Code, Opts1) of Err
{ok, AST1} ->
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} ->
Err
end;
true ->
expand_includes(AST, Included, Acc, Opts)
end; end;
Err = {error, _} -> {error, _} ->
Err {error, {get_pos(S), include_error, File}}
end; end;
expand_includes([E | AST], Included, Acc, Opts) -> expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, Included, [E | Acc], Opts). expand_includes(AST, [E | Acc], Opts).
read_file(File, Opts) -> read_file(File, Opts) ->
case proplists:get_value(include, Opts, {explicit_files, #{}}) of case proplists:get_value(include, Opts, {explicit_files, #{}}) of
@@ -594,31 +540,3 @@ read_file(File, Opts) ->
end end
end. end.
stdlib_options() ->
[{include, {file_system, [aeso_stdlib:stdlib_include_path()]}}].
get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, _}, {ok,_ }} ->
return_error(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
{_, {ok, Bin}} ->
{ok, binary_to_list(Bin)};
{{ok, Bin}, _} ->
{ok, binary_to_list(Bin)};
{_, _} ->
{error, {ann_pos(Ann), include_error, File}}
end.
-spec hash_include(string() | binary(), string()) -> include_hash().
hash_include(File, Code) when is_binary(File) ->
hash_include(binary_to_list(File), Code);
hash_include(File, Code) when is_list(File) ->
{filename:basename(File), crypto:hash(sha256, Code)}.
auto_imports({comprehension_bind, _, _}) -> [<<"ListInternal.aes">>];
auto_imports({'..', _}) -> [<<"ListInternal.aes">>];
auto_imports(L) when is_list(L) ->
lists:flatmap(fun auto_imports/1, L);
auto_imports(T) when is_tuple(T) ->
auto_imports(tuple_to_list(T));
auto_imports(_) -> [].
+15 -35
View File
@@ -153,22 +153,15 @@ decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
equals(typedecl(Kind, T, Vars), typedef(Def)); equals(typedecl(Kind, T, Vars), typedef(Def));
decl({fun_decl, Ann, F, T}) -> decl({fun_decl, _, F, T}) ->
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of hsep(text("function"), typed(name(F), T));
true -> text("entrypoint");
false -> text("function")
end,
hsep(Fun, typed(name(F), T));
decl(D = {letfun, Attrs, _, _, _, _}) -> decl(D = {letfun, Attrs, _, _, _, _}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> Mod = fun({Mod, true}) when Mod == private; Mod == internal; Mod == public; Mod == stateful ->
text(atom_to_list(Mod)); text(atom_to_list(Mod));
(_) -> empty() end, (_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
true -> "entrypoint"; decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
false -> "function" decl(D = {letrec, _, _}) -> letdecl("let", D).
end,
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
@@ -191,7 +184,9 @@ name({typed, _, Name, _}) -> name(Name).
letdecl(Let, {letval, _, F, T, E}) -> letdecl(Let, {letval, _, F, T, E}) ->
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E); block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, E}) -> letdecl(Let, {letfun, _, F, Args, T, E}) ->
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E). block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
letdecl(Let, {letrec, _, [D | Ds]}) ->
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
-spec args([aeso_syntax:arg()]) -> doc(). -spec args([aeso_syntax:arg()]) -> doc().
args(Args) -> args(Args) ->
@@ -222,7 +217,7 @@ typedef({variant_t, Constructors}) ->
-spec constructor_t(aeso_syntax:constructor_t()) -> doc(). -spec constructor_t(aeso_syntax:constructor_t()) -> doc().
constructor_t({constr_t, _, C, []}) -> name(C); constructor_t({constr_t, _, C, []}) -> name(C);
constructor_t({constr_t, _, C, Args}) -> beside(name(C), args_type(Args)). constructor_t({constr_t, _, C, Args}) -> beside(name(C), tuple_type(Args)).
-spec field_t(aeso_syntax:field_t()) -> doc(). -spec field_t(aeso_syntax:field_t()) -> doc().
field_t({field_t, _, Name, Type}) -> field_t({field_t, _, Name, Type}) ->
@@ -234,18 +229,13 @@ type(Type, Options) ->
-spec type(aeso_syntax:type()) -> doc(). -spec type(aeso_syntax:type()) -> doc().
type({fun_t, _, Named, Args, Ret}) -> type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, Ret}) ->
follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret)); follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret));
type({app_t, _, Type, []}) -> type({app_t, _, Type, []}) ->
type(Type); type(Type);
type({app_t, _, Type, Args}) -> type({app_t, _, Type, Args}) ->
beside(type(Type), args_type(Args)); beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) -> type({tuple_t, _, Args}) ->
tuple_type(Args); tuple_type(Args);
type({args_t, _, Args}) ->
args_type(Args);
type({bytes_t, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) -> type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"])); text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) -> type({named_arg_t, _, Name, Type, _Default}) ->
@@ -260,19 +250,9 @@ type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T); type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T). type(T = {tvar, _, _}) -> name(T).
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
tuple(lists:map(fun type/1, Args)).
-spec tuple_type([aeso_syntax:type()]) -> doc(). -spec tuple_type([aeso_syntax:type()]) -> doc().
tuple_type([]) -> tuple_type(Args) ->
text("unit"); tuple(lists:map(fun type/1, Args)).
tuple_type(Factors) ->
beside(
[ text("(")
, par(punctuate(text(" *"), lists:map(fun type/1, Factors)), 0)
, text(")")
]).
-spec arg_expr(aeso_syntax:arg_expr()) -> doc(). -spec arg_expr(aeso_syntax:arg_expr()) -> doc().
arg_expr({named_arg, _, Name, E}) -> arg_expr({named_arg, _, Name, E}) ->
@@ -352,7 +332,7 @@ expr_p(_, {Type, _, Bin})
Type == oracle_pubkey; Type == oracle_pubkey;
Type == oracle_query_id -> Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\""); expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) -> expr_p(_, {char, _, C}) ->
case C of case C of
@@ -385,7 +365,6 @@ stmt_p({else, Else}) ->
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}. -spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('||') -> {200, 300, 200}; bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300}; bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500}; bin_prec('<') -> {400, 500, 500};
@@ -457,6 +436,7 @@ statements(Stmts) ->
statement(S = {letval, _, _, _, _}) -> letdecl("let", S); statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S); statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
statement(S = {letrec, _, _}) -> letdecl("let", S);
statement(E) -> expr(E). statement(E) -> expr(E).
get_elifs(Expr) -> get_elifs(Expr, []). get_elifs(Expr) -> get_elifs(Expr, []).
+2 -2
View File
@@ -36,8 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())} , {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
-17
View File
@@ -1,17 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Radosław Rowicki
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Standard library for Sophia
%%% @end
%%% Created : 6 July 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_stdlib).
-export([stdlib_include_path/0]).
stdlib_include_path() ->
filename:join([code:priv_dir(aesophia), "stdlib"]).
+11 -14
View File
@@ -25,7 +25,7 @@
-type ann_origin() :: system | user. -type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif. -type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private]. -type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type name() :: string(). -type name() :: string().
-type id() :: {id, ann(), name()}. -type id() :: {id, ann(), name()}.
@@ -43,7 +43,8 @@
-type letbind() -type letbind()
:: {letval, ann(), id(), type(), expr()} :: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}. | {letfun, ann(), id(), [arg()], type(), expr()}
| {letrec, ann(), [letbind()]}.
-type arg() :: {arg, ann(), id(), type()}. -type arg() :: {arg, ann(), id(), type()}.
@@ -59,8 +60,7 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()} -type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]} | {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]} | {tuple_t, ann(), [type()]}
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages | {bytes_t, ann(), integer()}
| {bytes_t, ann(), integer() | any}
| id() | qid() | id() | qid()
| con() | qcon() %% contracts | con() | qcon() %% contracts
| tvar(). | tvar().
@@ -70,11 +70,12 @@
-type constant() -type constant()
:: {int, ann(), integer()} :: {int, ann(), integer()}
| {bool, ann(), true | false} | {bool, ann(), true | false}
| {bytes, ann(), binary()} | {hash, ann(), binary()}
| {account_pubkey, ann(), binary()} | {account_pubkey, binary()}
| {contract_pubkey, ann(), binary()} | {contract_pubkey, binary()}
| {oracle_pubkey, ann(), binary()} | {oracle_pubkey, binary()}
| {oracle_query_id, ann(), binary()} | {oracle_query_id, binary()}
| {unit, ann()}
| {string, ann(), binary()} | {string, ann(), binary()}
| {char, ann(), integer()}. | {char, ann(), integer()}.
@@ -93,7 +94,6 @@
| {proj, ann(), expr(), id()} | {proj, ann(), expr(), id()}
| {tuple, ann(), [expr()]} | {tuple, ann(), [expr()]}
| {list, ann(), [expr()]} | {list, ann(), [expr()]}
| {list_comp, ann(), expr(), [comprehension_exp()]}
| {typed, ann(), expr(), type()} | {typed, ann(), expr(), type()}
| {record, ann(), [field(expr())]} | {record, ann(), [field(expr())]}
| {record, ann(), expr(), [field(expr())]} %% record update | {record, ann(), expr(), [field(expr())]} %% record update
@@ -106,10 +106,6 @@
| id() | qid() | con() | qcon() | id() | qid() | con() | qcon()
| constant(). | constant().
-type comprehension_exp() :: [ {comprehension_bind, id(), expr()}
| {comprehension_if, ann(), expr()}
| letbind() ].
-type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}. -type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}.
%% When lvalue is a projection this is sugar for accessing fields in nested %% When lvalue is a projection this is sugar for accessing fields in nested
@@ -154,3 +150,4 @@ get_ann(Key, Node, Default) ->
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X); qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]}; qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}. qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+26 -35
View File
@@ -6,7 +6,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_syntax_utils). -module(aeso_syntax_utils).
-export([used_ids/1, used_types/2, used/1]). -export([used_ids/1, used_types/1, used/1]).
-record(alg, {zero, plus, scoped}). -record(alg, {zero, plus, scoped}).
@@ -39,6 +39,11 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end, BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end, BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X), Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of Rec = case X of
%% lists (bound things in head scope over tail) %% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As)); [A | As] -> Scoped(Same(A), Same(As));
@@ -49,7 +54,8 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T); {fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]); {letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]); {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef() %% typedef()
{alias_t, T} -> Type(T); {alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs); {record_t, Fs} -> Type(Fs);
@@ -71,15 +77,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{proj, _, E, _} -> Expr(E); {proj, _, E, _} -> Expr(E);
{tuple, _, As} -> Expr(As); {tuple, _, As} -> Expr(As);
{list, _, As} -> Expr(As); {list, _, As} -> Expr(As);
{list_comp, _, Y, []} -> Expr(Y);
{list_comp, A, Y, [{comprehension_bind, I, E}|R]} ->
Plus(Expr(E), Scoped(BindExpr(I), Expr({list_comp, A, Y, R})));
{list_comp, A, Y, [{comprehension_if, _, E}|R]} ->
Plus(Expr(E), Expr({list_comp, A, Y, R}));
{list_comp, A, Y, [D = {letval, _, F, _, _} | R]} ->
Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R})));
{list_comp, A, Y, [D = {letfun, _, F, _, _, _} | R]} ->
Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R})));
{typed, _, E, T} -> Plus(Expr(E), Type(T)); {typed, _, E, T} -> Plus(Expr(E), Type(T));
{record, _, Fs} -> Expr(Fs); {record, _, Fs} -> Expr(Fs);
{record, _, E, Fs} -> Expr([E | Fs]); {record, _, E, Fs} -> Expr([E | Fs]);
@@ -92,7 +89,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{field, _, LV, E} -> Expr([LV, E]); {field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]); {field, _, LV, _, E} -> Expr([LV, E]);
%% arg() %% arg()
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T)); {arg, _, X, T} -> Plus(Expr(X), Type(T));
%% alt() %% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E)); {'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim() %% elim()
@@ -107,35 +104,29 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies %% Name dependencies
used_ids(E) -> used_ids(E) ->
[ X || {{term, [X]}, _} <- used(E) ]. [ X || {term, [X]} <- used(E) ].
used_types([Top] = _CurrentNS, T) -> used_types(T) ->
F = fun({{type, [X]}, _}) -> [X]; [ X || {type, [X]} <- used(T) ].
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]} -type entity() :: {term, [string()]}
| {type, [string()]} | {type, [string()]}
| {namespace, [string()]}. | {namespace, [string()]}.
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}). -spec entity_alg() -> alg([entity()]).
entity_alg() -> entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end, IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end, Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> maps:without(Keys, Map) end,
Scoped = fun(Xs, Ys) -> Scoped = fun(Xs, Ys) ->
Bound = [E || E <- maps:keys(Xs), IsBound(E)], {Bound, Others} = lists:partition(IsBound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ], Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
Others = Remove(Bound1, Ys), lists:umerge(Xs -- Bound1, Others)
maps:merge(Remove(Bound, Xs), Others)
end, end,
#alg{ zero = #{} #alg{ zero = []
, plus = fun maps:merge/2 , plus = fun lists:umerge/2
, scoped = Scoped }. , scoped = Scoped }.
-spec used(_) -> [{entity(), aeso_syntax:ann()}]. -spec used(_) -> [entity()].
used(D) -> used(D) ->
Kind = fun(expr) -> term; Kind = fun(expr) -> term;
(bind_expr) -> bound_term; (bind_expr) -> bound_term;
@@ -143,14 +134,14 @@ used(D) ->
(bind_type) -> bound_type (bind_type) -> bound_type
end, end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end, NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end, NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs = Xs =
maps:to_list(fold(entity_alg(), fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann}; fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann}; (K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann}; (K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> #{} (_, _) -> []
end, decl, D)), end, decl, D),
lists:filter(NotBound, Xs). lists:filter(NotBound, Xs).
-113
View File
@@ -1,113 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N});
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin});
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N};
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
{list, [], [from_fate(Type, X) || X <- List]};
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, [0, 1], 0, {}} -> {con, [], "None"};
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
end;
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
{tuple, [], []};
from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_fate(KeyType, Key),
from_fate(ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
when length(Cons) > Tag ->
ConType = lists:nth(Tag + 1, Cons),
Arity = lists:nth(Tag + 1, Ar),
case tuple_to_list(Args) of
ArgList when length(ArgList) == Arity ->
from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia)
end;
from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia).
+2 -3
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "4.0.0-rc1"}, {vsn, "2.1.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
@@ -8,8 +8,7 @@
jsx, jsx,
syntax_tools, syntax_tools,
getopt, getopt,
aebytecode, aebytecode
eblake2
]}, ]},
{env,[]}, {env,[]},
{modules, []}, {modules, []},
+78
View File
@@ -0,0 +1,78 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
case Opts of
[version] ->
print_vsn();
[help] ->
usage();
_ ->
compile(Opts)
end;
{ok, {_, NonOpts}} ->
io:format("Can't understand ~p\n\n", [NonOpts]),
usage();
{error, {Reason, Data}} ->
io:format("Error: ~s ~p\n\n", [Reason, Data]),
usage()
end.
compile(Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined ->
io:format("Error: no input source file\n\n"),
usage();
File ->
compile(File, Opts)
end.
compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined),
try
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
write_outfile(OutFile, Res),
io:format("\nCompiled successfully!\n")
catch
%% The compiler errors.
error:{type_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
error:{parse_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
error:{code_errors, Errors} ->
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
%% General programming errors in the compiler.
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
io:format("\n~s\n", [ErrorString])
end.
write_outfile(undefined, _) -> ok;
write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+20 -21
View File
@@ -1,7 +1,7 @@
-module(aeso_abi_tests). -module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-compile([export_all, nowarn_export_all]). -compile(export_all).
-define(SANDBOX(Code), sandbox(fun() -> Code end)). -define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123). -define(DUMMY_HASH_WORD, 16#123).
@@ -62,11 +62,10 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
ok = Check("string * list(int) * option(bool)", ok = Check("(string, list(int), option(bool))",
"(\"Hello\", [1, 2, 3], Some(true))"), "(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"), ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"), ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
@@ -76,15 +75,15 @@ encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract MakeCall =\n"
, " type arg_type = ", SophiaType, "\n" , " type arg_type = ", SophiaType, "\n"
, " type an_alias('a) = string * 'a\n" , " type an_alias('a) = (string, 'a)\n"
, " record r = {x : an_alias(int), y : variant}\n" , " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n" , " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ], , " function foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
{ok, _, {[Type], _}, [Arg]} -> {ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]), io:format("Type ~p~n", [Type]),
Data = encode(Arg), Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, []) of case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
{ok, Sophia} -> {ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))])); lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} -> {error, Err} ->
@@ -120,14 +119,16 @@ calldata_init_test() ->
calldata_indent_test() -> calldata_indent_test() ->
Test = fun(Extra) -> Test = fun(Extra) ->
Code = parameterized_contract(Extra, "foo", ["int"]), encode_decode_calldata_(
encode_decode_calldata_(Code, "foo", ["42"], word) parameterized_contract(Extra, "foo", ["int"]),
"foo", ["42"], word)
end, end,
Test(" stateful entrypoint bla() = ()"), Test(" stateful function bla() = ()"),
Test(" type x = int"), Test(" type x = int"),
Test(" stateful entrypoint bla(x : int) =\n" Test(" private function bla : int => int"),
Test(" public stateful function bla(x : int) =\n"
" x + 1"), " x + 1"),
Test(" stateful entrypoint bla(x : int) : int =\n" Test(" stateful private function bla(x : int) : int =\n"
" x + 1"), " x + 1"),
ok. ok.
@@ -137,18 +138,18 @@ parameterized_contract(FunName, Types) ->
parameterized_contract(ExtraCode, FunName, Types) -> parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten( lists:flatten(
["contract Remote =\n" ["contract Remote =\n"
" entrypoint bla : () => unit\n\n" " function bla : () => ()\n\n"
"contract Dummy =\n", "contract Dummy =\n",
ExtraCode, "\n", ExtraCode, "\n",
" type an_alias('a) = string * 'a\n" " type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n" " record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n" " datatype variant = Red | Blue(map(string, int))\n"
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]). " function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() -> oracle_test() ->
Contract = Contract =
"contract OracleTest =\n" "contract OracleTest =\n"
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n" " function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n", " Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
@@ -159,7 +160,7 @@ oracle_test() ->
permissive_literals_fail_test() -> permissive_literals_fail_test() ->
Contract = Contract =
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n" " function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n", " Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} = {error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []), aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
@@ -173,16 +174,14 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetType). encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) -> encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []), {ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]), ?assertEqual(RetVMType1, RetVMType),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata), {ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
case FunName of case FunName of
"init" -> "init" ->
ok; ok;
_ -> _ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []), {ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ], Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values}) ?assertMatch({X, X}, {Args, Values})
end, end,
+52 -101
View File
@@ -2,124 +2,75 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N), do_test() ->
fun() -> test_contract(N) end} test_contract(1),
|| N <- [1, 2, 3]]. test_contract(2),
test_contract(3).
test_contract(N) -> test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N), {Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract), {ok,JSON} = aeso_aci:encode(Contract),
?assertEqual([MapACI], JSON), ?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual(DecACI, aeso_aci:decode(JSON)).
test_cases(1) -> test_cases(1) ->
Contract = <<"payable contract C =\n" Contract = <<"contract C =\n"
" payable stateful entrypoint a(i : int) = i+1\n">>, " function a(i : int) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, #{<<"name">> => <<"C">>,
type_defs => [], <<"type_defs">> => [],
payable => true, <<"functions">> =>
functions => [#{<<"name">> => <<"a">>,
[#{name => <<"a">>, <<"arguments">> =>
arguments => [#{<<"name">> => <<"i">>,
[#{name => <<"i">>, <<"type">> => <<"int">>}],
type => <<"int">>}], <<"returns">> => <<"int">>,
returns => <<"int">>, <<"stateful">> => false}]}},
stateful => true, DecACI = <<"contract C =\n"
payable => true}]}}, " function a : (int) => int\n">>,
DecACI = <<"payable contract C =\n"
" payable entrypoint a : (int) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(2) -> test_cases(2) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type allan = int\n" " type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>, " function a(i : allan) = i+1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{name => <<"C">>, payable => false, #{<<"name">> => <<"C">>,
type_defs => <<"type_defs">> =>
[#{name => <<"allan">>, [#{<<"name">> => <<"allan">>,
typedef => <<"int">>, <<"typedef">> => <<"int">>,
vars => []}], <<"vars">> => []}],
functions => <<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"C.allan">>}], <<"type">> => <<"int">>}],
name => <<"a">>, <<"name">> => <<"a">>,
returns => <<"int">>, <<"returns">> => <<"int">>,
stateful => false, <<"stateful">> => false}]}},
payable => false}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type allan = int\n" " function a : (int) => int\n">>,
" entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI}; {Contract,MapACI,DecACI};
test_cases(3) -> test_cases(3) ->
Contract = <<"contract C =\n" Contract = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a(i : bert(string)) = 1\n">>, " function a(i : bert(string)) = 1\n">>,
MapACI = #{contract => MapACI = #{<<"contract">> =>
#{functions => #{<<"functions">> =>
[#{arguments => [#{<<"arguments">> =>
[#{name => <<"i">>, [#{<<"name">> => <<"i">>,
type => <<"type">> =>
#{<<"C.bert">> => [<<"string">>]}}], #{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>, <<"name">> => <<"a">>,<<"returns">> => <<"int">>,
stateful => false, payable => false}], <<"stateful">> => false}],
name => <<"C">>, payable => false, <<"name">> => <<"C">>,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, <<"type_defs">> =>
state => <<"unit">>, [#{<<"name">> => <<"bert">>,
type_defs => <<"typedef">> =>
[#{name => <<"bert">>, #{<<"variant">> =>
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]}, [#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}}, <<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>, " function a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}. {Contract,MapACI,DecACI}.
%% Rounttrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
_:R ->
io:format("Error: ~p\n", [R]),
error(R)
end;
{error, E} ->
io:format("Error: ~p\n", [E]),
error({parse_error, E})
end.
-129
View File
@@ -1,129 +0,0 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc Test Sophia language compiler.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_calldata_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
true -> ok;
false ->
?assertEqual(FateExprs, AevmExprs)
end
end} || {ContractName, Fun, Args} <- compilable_contracts()].
ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)),
Exprs.
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() ->
[
{"identity", "init", []},
{"maps", "init", []},
{"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
"414243444546474849505152535455565758596061626364656667"]},
{"funargs", "trettiotva", ["#0102030405060708091011121314151617181920212223242526272829303132"]},
{"funargs", "find_oracle", ["ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5"]},
{"funargs", "find_query", ["oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY"]},
{"funargs", "traffic_light", ["Green"]},
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
{"bytes_equality", "init", []},
{"address_chain", "init", []},
{"counter", "init",
["-3334353637383940202122232425262728293031323334353637"]},
{"dutch_auction", "init",
["ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt", "200000", "1000"]},
{"maps", "fromlist_i",
["[(1, {x = 1, y = 2}), (2, {x = 3, y = 4}), (3, {x = 4, y = 4})]"]},
{"maps", "get_i", ["1", "{}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
{"strings", "str_concat", ["\"test\"","\"me\""]},
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
{"__call" "init", []},
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f20212223242526272829303132333435363738"]},
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
{"stub", "foo", ["42"]},
{"stub", "foo", ["-42"]},
{"payable", "foo", ["42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
[].
+57 -128
View File
@@ -16,25 +16,20 @@
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", [ {"Testing the " ++ ContractName ++ " contract",
fun() -> fun() ->
case compile(Backend, ContractName) of case compile(ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
?assertMatch(Code when is_binary(Code), ByteCode);
#{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code});
ErrBin -> ErrBin ->
io:format("\n~s", [ErrBin]), io:format("\n~s", [ErrBin]),
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts() ] ++
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
case compile(aevm, ContractName) of case compile(ContractName) of
<<"Type errors\n", ErrorString/binary>> -> <<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString); check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> -> <<"Parse errors\n", ErrorString/binary>> ->
@@ -49,21 +44,19 @@ simple_compile_test_() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), {ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin} {File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]), end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), #{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"), #{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2) ?assertMatch(true, Code1 == Code2)
end} ] ++ end} ] ++
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend), [ {"Testing deadcode elimination",
fun() -> fun() ->
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"), #{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile(Backend, "deadcode"), #{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode), SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode), SizeDeadCode = byte_size(DeadCode),
Delta = if Backend == aevm -> 40; ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok ok
end} || Backend <- [aevm, fate] ]. end} ].
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -74,13 +67,12 @@ check_errors(Expect, ErrorString) ->
{Missing, Extra} -> ?assertEqual(Missing, Extra) {Missing, Extra} -> ?assertEqual(Missing, Extra)
end. end.
compile(Backend, Name) -> compile(Name) ->
compile(Backend, Name, compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name, Options) -> compile(Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} -> ErrorString {error, ErrorString} -> ErrorString
end. end.
@@ -94,7 +86,6 @@ compilable_contracts() ->
"dutch_auction", "dutch_auction",
"environment", "environment",
"factorial", "factorial",
"functions",
"fundme", "fundme",
"identity", "identity",
"maps", "maps",
@@ -115,24 +106,9 @@ compilable_contracts() ->
"include", "include",
"basic_auth", "basic_auth",
"bitcoin_auth", "bitcoin_auth",
"address_literals", "address_literals"
"bytes_equality",
"address_chain",
"namespace_bug",
"bytes_to_x",
"aens",
"tuple_match",
"cyclic_include",
"stdlib_include",
"double_include",
"manual_stdlib_include",
"list_comp",
"payable"
]. ].
not_yet_compilable(fate) -> [];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
failing_contracts() -> failing_contracts() ->
@@ -140,9 +116,6 @@ failing_contracts() ->
[<<"Duplicate definitions of abort at\n" [<<"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
@@ -154,12 +127,12 @@ failing_contracts() ->
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of state at\n" <<"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
[<<"Unbound variable zz at line 17, column 23">>, [<<"Unbound variable zz at line 17, column 21">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
@@ -170,18 +143,18 @@ failing_contracts() ->
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -193,7 +166,7 @@ failing_contracts() ->
" int">>, " int">>,
<<"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
@@ -203,51 +176,50 @@ failing_contracts() ->
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 42)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 38)">>,
<<"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 35)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n" <<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>, " x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 14).">>, <<"No record type with fields y, z (at line 14, column 22)">>,
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, <<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"No record type with fields y, z (at line 14, column 24)">>, <<"Record type r2 does not have field y (at line 15, column 22)">>]}
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>,
<<"Let binding at line 47, column 5 must be followed by an expression">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>,
<<"Let binding at line 54, column 5 must be followed by an expression">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]}
, {"init_type_error", , {"init_type_error",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
[<<"Cannot unify string\n" [<<"Cannot unify string\n"
" and unit\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash", , {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} [<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events", , {"bad_events",
[<<"The indexed type string (at line 9, column 25) is not a word type">>, [<<"The payload type int (at line 10, column 30) should be string">>,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} <<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
, {"bad_events2", , {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, [<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} <<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash", , {"type_clash",
[<<"Cannot unify int\n" [<<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the record projection at line 12, column 42\n" "when checking the record projection at line 12, column 40\n"
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>]}
@@ -270,46 +242,46 @@ failing_contracts() ->
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<"Cannot unify oracle_query('1, '2)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('1, '2)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<"Cannot unify oracle_query('3, '4)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('3, '4)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<"Cannot unify oracle_query('5, '6)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('5, '6)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<"Cannot unify oracle('7, '8)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('7, '8)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<"Cannot unify oracle('9, '10)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('9, '10)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<"Cannot unify oracle('11, '12)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('11, '12)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<"Cannot unify address\n"
@@ -330,47 +302,4 @@ failing_contracts() ->
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>]} " bytes(32)">>]}
, {"stateful",
[<<"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
<<"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
, {"bad_init_state_access",
[<<"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n"
" - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 38), which calls\n"
" - put (at line 7, column 39)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 29)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>]}
, {"field_parse_error",
[<<"line 6, column 1: In field_parse_error at 5:26:\n"
"Cannot use nested fields or keys in record construction: p.x\n">>]}
, {"modifier_checks",
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]}
, {"list_comp_not_a_list",
[<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
]}
, {"list_comp_if_not_bool",
[<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
]}
, {"list_comp_bad_shadow",
[<<"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
]}
]. ].
+4 -10
View File
@@ -62,7 +62,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts %% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]), [ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end} fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
}. }.
parse_contract(Name) -> parse_contract(Name) ->
@@ -71,10 +71,8 @@ parse_contract(Name) ->
roundtrip_contract(Name) -> roundtrip_contract(Name) ->
round_trip(aeso_test_utils:read_contract(Name)). round_trip(aeso_test_utils:read_contract(Name)).
parse_string(Text) -> parse_string(Text, []). parse_string(Text) ->
case aeso_parser:string(Text) of
parse_string(Text, Opts) ->
case aeso_parser:string(Text, Opts) of
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
Err -> error(Err) Err -> error(Err)
end. end.
@@ -86,16 +84,12 @@ parse_expr(Text) ->
round_trip(Text) -> round_trip(Text) ->
Contract = parse_string(Text), Contract = parse_string(Text),
Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))), Text1 = prettypr:format(aeso_pretty:decls(Contract)),
Contract1 = parse_string(Text1), Contract1 = parse_string(Text1),
NoSrcLoc = remove_line_numbers(Contract), NoSrcLoc = remove_line_numbers(Contract),
NoSrcLoc1 = remove_line_numbers(Contract1), NoSrcLoc1 = remove_line_numbers(Contract1),
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)). ?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
strip_stdlib([{namespace, _, {con, _, "ListInternal"}, _} | Decls]) ->
strip_stdlib(Decls);
strip_stdlib(Decls) -> Decls.
remove_line_numbers({line, _L}) -> {line, 0}; remove_line_numbers({line, _L}) -> {line, 0};
remove_line_numbers({col, _C}) -> {col, 0}; remove_line_numbers({col, _C}) -> {col, 0};
remove_line_numbers([H|T]) -> remove_line_numbers([H|T]) ->
+1 -1
View File
@@ -41,7 +41,7 @@ all_tokens() ->
%% Operators %% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords %% Keywords
lists:map(Lit, [contract, type, 'let', switch]) ++ lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
%% Comment token (not an actual token), just for tests %% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"}, [{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++ {comment, 0, "/* bla /* bla bla */*/"}] ++
-5
View File
@@ -1,5 +0,0 @@
contract Identity =
function main (x:int) = x
function __call() = 12
+1 -1
View File
@@ -8,7 +8,7 @@ contract AbortTest =
{ value = v } { value = v }
// Aborting // Aborting
public function do_abort(v : int, s : string) : unit = public function do_abort(v : int, s : string) : () =
put_value(v) put_value(v)
revert_abort(s) revert_abort(s)
+3 -3
View File
@@ -1,9 +1,9 @@
contract Interface = contract Interface =
function do_abort : (int, string) => unit function do_abort : (int, string) => ()
function get_value : () => int function get_value : () => int
function put_value : (int) => unit function put_value : (int) => ()
function get_values : () => list(int) function get_values : () => list(int)
function put_values : (int) => unit function put_values : (int) => ()
contract AbortTestInt = contract AbortTestInt =
-36
View File
@@ -1,36 +0,0 @@
contract Remote =
entrypoint main : (int) => unit
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
entrypoint is_o(a : address) =
Address.is_oracle(a)
entrypoint is_c(a : address) =
Address.is_contract(a)
// entrypoint get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// entrypoint get_c(a : address) : option(Remote) =
// Address.get_contract(a)
entrypoint check_o(o : o_type) =
Oracle.check(o)
entrypoint check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// entrypoint h_to_i(h : hash) : int =
// Hash.to_int(h)
// entrypoint a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
entrypoint c_creator() : address =
Contract.creator
entrypoint is_payable(a : address) : bool =
Address.is_payable(a)
+5 -5
View File
@@ -1,14 +1,14 @@
contract Remote = contract Remote =
entrypoint foo : () => unit function foo : () => ()
contract AddressLiterals = contract AddressLiterals =
entrypoint addr() : address = function addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle() : oracle(int, bool) = function oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query() : oracle_query(int, bool) = function query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote = function contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+30 -43
View File
@@ -1,68 +1,55 @@
// AENS tests // AENS tests
contract AENSTest = contract AENSTest =
type subnames = map(string, map(string, address))
// Name resolution // Name resolution
stateful entrypoint resolve_word(name : string, key : string) : option(address) = function resolve_word(name : string, key : string) : option(address) =
AENS.resolve(name, key) AENS.resolve(name, key)
stateful entrypoint resolve_string(name : string, key : string) : option(string) = function resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key) AENS.resolve(name, key)
// Transactions // Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash chash : hash) : () = // Commitment hash
AENS.preclaim(addr, chash) AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address) function signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash, // Commitment hash chash : hash, // Commitment hash
sign : signature) : unit = // Signed by addr (if not Contract.address) sign : signature) : () = // Signed by addr (if not Contract.address)
AENS.preclaim(addr, chash, signature = sign) AENS.preclaim(addr, chash, signature = sign)
stateful entrypoint claim(addr : address, function claim(addr : address,
name : string, name : string,
salt : int) : unit = salt : int) : () =
AENS.claim(addr, name, salt) AENS.claim(addr, name, salt)
stateful entrypoint signedClaim(addr : address, function signedClaim(addr : address,
name : string, name : string,
salt : int, salt : int,
sign : signature) : unit = sign : signature) : () =
AENS.claim(addr, name, salt, signature = sign) AENS.claim(addr, name, salt, signature = sign)
// TODO: update() -- how to handle pointers? // TODO: update() -- how to handle pointers?
stateful entrypoint transfer(owner : address, function transfer(owner : address,
new_owner : address, new_owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.transfer(owner, new_owner, name) AENS.transfer(owner, new_owner, name_hash)
stateful entrypoint signedTransfer(owner : address, function signedTransfer(owner : address,
new_owner : address, new_owner : address,
name : string, name_hash : hash,
sign : signature) : unit = sign : signature) : () =
AENS.transfer(owner, new_owner, name, signature = sign) AENS.transfer(owner, new_owner, name_hash, signature = sign)
stateful entrypoint revoke(owner : address, function revoke(owner : address,
name : string) : unit = name_hash : hash) : () =
AENS.revoke(owner, name) AENS.revoke(owner, name_hash)
stateful entrypoint signedRevoke(owner : address, function signedRevoke(owner : address,
name : string, name_hash : hash,
sign : signature) : unit = sign : signature) : () =
AENS.revoke(owner, name, signature = sign) AENS.revoke(owner, name_hash, signature = sign)
stateful entrypoint subname(owner : address,
name : string,
subnames : subnames) : unit =
AENS.subname(owner, name, subnames)
stateful entrypoint signedSubname(owner : address,
name : string,
subnames : subnames,
sign : signature) : unit =
AENS.subname(owner, name, subnames, signature = sign)
+6 -4
View File
@@ -104,10 +104,10 @@ contract AEProof =
proofsByOwner : map(address, array(uint)) } proofsByOwner : map(address, array(uint)) }
function notarize(document:string, comment:string, ipfsHash:hash) = function notarize(document:string, comment:string, ipfsHash:hash) =
let _ = require(aetoken.balanceOf(caller()) > 0, "false") let _ = require(aetoken.balanceOf(caller()) > 0)
let proofHash: uint = calculateHash(document) let proofHash: uint = calculateHash(document)
let proof : proof = Map.get_(proofHash, state().proofs) let proof : proof = Map.get_(proofHash, state().proofs)
let _ = require(proof.owner == #0, "false") let _ = require(proof.owner == #0)
let proof' : proof = proof { owner = caller() let proof' : proof = proof { owner = caller()
, timestamp = block().timestamp , timestamp = block().timestamp
, proofBlock = block().height , proofBlock = block().height
@@ -124,12 +124,12 @@ contract AEProof =
function getProof(document) : proof = function getProof(document) : proof =
let calcHash = calculateHash(document) let calcHash = calculateHash(document)
let proof = Map.get_(calcHash, state().proofs) let proof = Map.get_(calcHash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
function getProofByHash(hash: uint) : proof = function getProofByHash(hash: uint) : proof =
let proof = Map.get_(hash, state().proofs) let proof = Map.get_(hash, state().proofs)
let _ = require(proof.owner != #0, "false") let _ = require(proof.owner != #0)
proof proof
@@ -141,3 +141,5 @@ contract AEProof =
function getProofsByOwner(owner: address): array(uint) = function getProofsByOwner(owner: address): array(uint) =
Map.get(owner, state()) Map.get(owner, state())
function require(x : bool) : unit = if(x) () else abort("false")
+6 -1
View File
@@ -24,7 +24,7 @@ contract AllSyntax =
if(valWithType(Map.empty) == None) if(valWithType(Map.empty) == None)
print(42 mod 10 * 5 / 3) print(42 mod 10 * 5 / 3)
function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ []) function funWithType(x : int, y) : (int, list(int)) = (x, 0 :: [y] ++ [])
function funNoType() = function funNoType() =
let foo = (x, y : bool) => let foo = (x, y : bool) =>
if (! (y && x =< 0x0b || true)) [x] if (! (y && x =< 0x0b || true)) [x]
@@ -36,6 +36,11 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => () (x, y :: _) => ()
function mutual() =
let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
recFun(0)
let hash : address = #01ab0fff11 let hash : address = #01ab0fff11
let b = false let b = false
let qcon = Mod.Con let qcon = Mod.Con
+13 -13
View File
@@ -1,33 +1,33 @@
contract Remote = contract Remote =
entrypoint foo : () => unit function foo : () => ()
contract AddressLiterals = contract AddressLiterals =
entrypoint addr1() : bytes(32) = function addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr2() : Remote = function addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr3() : oracle(int, bool) = function addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle1() : oracle_query(int, bool) = function oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle2() : bytes(32) = function oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle3() : Remote = function oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query1() : oracle(int, bool) = function query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query2() : bytes(32) = function query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query3() : Remote = function query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr1() : address = function contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr2() : oracle(int, bool) = function contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) = function contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
+9 -7
View File
@@ -6,18 +6,20 @@ contract Events =
datatype event = datatype event =
Event1(indexed alias_int, indexed int, string) Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string) | BadEvent1(indexed string, string)
| BadEvent2(indexed alias_string) | BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
entrypoint f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) = function f2(s : string) =
Chain.event(Event2(s, Call.caller)) Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) = function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7))) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i) function i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a) function a2s(a : address) = Address.to_str(a)
+6 -5
View File
@@ -8,16 +8,17 @@ contract Events =
| Event2(alias_string, indexed alias_address) | Event2(alias_string, indexed alias_address)
| BadEvent1(string, string) | BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address) | BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
entrypoint f1(x : int, y : string) = function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y)) Chain.event(Event1(x, x+1, y))
entrypoint f2(s : string) = function f2(s : string) =
Chain.event(Event2(s, Call.caller)) Chain.event(Event2(s, Call.caller))
entrypoint f3(x : int) = function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7))) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint i2s(i : int) = Int.to_str(i) function i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a) function a2s(a : address) = Address.to_str(a)
+1 -1
View File
@@ -3,4 +3,4 @@ contract Bad =
namespace Foo = namespace Foo =
function foo() = 42 function foo() = 42
entrypoint foo() = 43 function foo() = 43
-13
View File
@@ -1,13 +0,0 @@
contract BadInit =
type state = int
entrypoint new_state(n) = state + n
stateful entrypoint roundabout(n) = put(n)
stateful entrypoint set_state(n) = roundabout(n)
stateful entrypoint init() =
set_state(4)
new_state(0)
state + state
+6 -4
View File
@@ -2,16 +2,18 @@
contract BasicAuth = contract BasicAuth =
record state = { nonce : int, owner : address } record state = { nonce : int, owner : address }
entrypoint init() = { nonce = 1, owner = Call.caller } function init() = { nonce = 1, owner = Call.caller }
stateful entrypoint authorize(n : int, s : signature) : bool = function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low") require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high") require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 }) put(state{ nonce = n + 1 })
switch(Auth.tx_hash) switch(Auth.tx_hash)
None => abort("Not in Auth context") None => abort("Not in Auth context")
Some(tx_hash) => Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s) Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) = function to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+6 -4
View File
@@ -1,9 +1,9 @@
contract BitcoinAuth = contract BitcoinAuth =
record state = { nonce : int, owner : bytes(20) } record state = { nonce : int, owner : bytes(64) }
entrypoint init(owner' : bytes(20)) = { nonce = 1, owner = owner' } function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
stateful entrypoint authorize(n : int, s : bytes(65)) : bool = function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low") require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high") require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 }) put(state{ nonce = n + 1 })
@@ -11,6 +11,8 @@ contract BitcoinAuth =
None => abort("Not in Auth context") None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s) Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) : hash = function to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+2 -2
View File
@@ -5,8 +5,8 @@ contract BuiltinBug =
record state = {proofs : map(address, list(string))} record state = {proofs : map(address, list(string))}
entrypoint init() = {proofs = {}} public function init() = {proofs = {}}
stateful entrypoint createProof(hash : string) = public stateful function createProof(hash : string) =
put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } ) put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } )
+7 -3
View File
@@ -1,8 +1,12 @@
contract TestContract = contract TestContract =
record state = {_allowed : map(address, map(address, int))} record state = {
_allowed : map(address, map(address, int))}
entrypoint init() = {_allowed = {}} public stateful function init() = {
_allowed = {}}
public stateful function approve(spender: address, value: int) : bool =
stateful entrypoint approve(spender: address, value: int) : bool =
put(state{_allowed[Call.caller][spender] = value}) put(state{_allowed[Call.caller][spender] = value})
true true
-18
View File
@@ -1,18 +0,0 @@
contract BytesEquality =
entrypoint eq16(a : bytes(16), b) = a == b
entrypoint ne16(a : bytes(16), b) = a != b
entrypoint eq32(a : bytes(32), b) = a == b
entrypoint ne32(a : bytes(32), b) = a != b
entrypoint eq47(a : bytes(47), b) = a == b
entrypoint ne47(a : bytes(47), b) = a != b
entrypoint eq64(a : bytes(64), b) = a == b
entrypoint ne64(a : bytes(64), b) = a != b
entrypoint eq65(a : bytes(65), b) = a == b
entrypoint ne65(a : bytes(65), b) = a != b
-8
View File
@@ -1,8 +0,0 @@
contract BytesToX =
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
entrypoint to_str(b : bytes(12)) : string =
String.concat(Bytes.to_str(b), Bytes.to_str(#ffff))
entrypoint to_str_big(b : bytes(65)) : string =
Bytes.to_str(b)
+25 -25
View File
@@ -1,78 +1,78 @@
contract Remote = contract Remote =
entrypoint up_to : (int) => list(int) function up_to : (int) => list(int)
entrypoint sum : (list(int)) => int function sum : (list(int)) => int
entrypoint some_string : () => string function some_string : () => string
entrypoint pair : (int, string) => int * string function pair : (int, string) => (int, string)
entrypoint squares : (int) => list(int * int) function squares : (int) => list((int, int))
entrypoint filter_some : (list(option(int))) => list(int) function filter_some : (list(option(int))) => list(int)
entrypoint all_some : (list(option(int))) => option(list(int)) function all_some : (list(option(int))) => option(list(int))
contract ComplexTypes = contract ComplexTypes =
record state = { worker : Remote } record state = { worker : Remote }
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
entrypoint sum_acc(xs, n) = function sum_acc(xs, n) =
switch(xs) switch(xs)
[] => n [] => n
x :: xs => sum_acc(xs, x + n) x :: xs => sum_acc(xs, x + n)
// Sum a list of integers // Sum a list of integers
entrypoint sum(xs : list(int)) = function sum(xs : list(int)) =
sum_acc(xs, 0) sum_acc(xs, 0)
entrypoint up_to_acc(n, xs) = function up_to_acc(n, xs) =
switch(n) switch(n)
0 => xs 0 => xs
_ => up_to_acc(n - 1, n :: xs) _ => up_to_acc(n - 1, n :: xs)
entrypoint up_to(n) = up_to_acc(n, []) function up_to(n) = up_to_acc(n, [])
record answer('a) = {label : string, result : 'a} record answer('a) = {label : string, result : 'a}
entrypoint remote_triangle(worker, n) : answer(int) = function remote_triangle(worker, n) : answer(int) =
let xs = worker.up_to(gas = 100000, n) let xs = worker.up_to(gas = 100000, n)
let t = worker.sum(xs) let t = worker.sum(xs)
{ label = "answer:", result = t } { label = "answer:", result = t }
entrypoint remote_list(n) : list(int) = function remote_list(n) : list(int) =
state.worker.up_to(n) state.worker.up_to(n)
entrypoint some_string() = "string" function some_string() = "string"
entrypoint remote_string() : string = function remote_string() : string =
state.worker.some_string() state.worker.some_string()
entrypoint pair(x : int, y : string) = (x, y) function pair(x : int, y : string) = (x, y)
entrypoint remote_pair(n : int, s : string) : int * string = function remote_pair(n : int, s : string) : (int, string) =
state.worker.pair(gas = 10000, n, s) state.worker.pair(gas = 10000, n, s)
entrypoint map(f, xs) = function map(f, xs) =
switch(xs) switch(xs)
[] => [] [] => []
x :: xs => f(x) :: map(f, xs) x :: xs => f(x) :: map(f, xs)
entrypoint squares(n) = function squares(n) =
map((i) => (i, i * i), up_to(n)) map((i) => (i, i * i), up_to(n))
entrypoint remote_squares(n) : list(int * int) = function remote_squares(n) : list((int, int)) =
state.worker.squares(n) state.worker.squares(n)
// option types // option types
entrypoint filter_some(xs : list(option(int))) : list(int) = function filter_some(xs : list(option(int))) : list(int) =
switch(xs) switch(xs)
[] => [] [] => []
None :: ys => filter_some(ys) None :: ys => filter_some(ys)
Some(x) :: ys => x :: filter_some(ys) Some(x) :: ys => x :: filter_some(ys)
entrypoint remote_filter_some(xs : list(option(int))) : list(int) = function remote_filter_some(xs : list(option(int))) : list(int) =
state.worker.filter_some(xs) state.worker.filter_some(xs)
entrypoint all_some(xs : list(option(int))) : option(list(int)) = function all_some(xs : list(option(int))) : option(list(int)) =
switch(xs) switch(xs)
[] => Some([]) [] => Some([])
None :: ys => None None :: ys => None
@@ -81,6 +81,6 @@ contract ComplexTypes =
Some(xs) => Some(x :: xs) Some(xs) => Some(x :: xs)
None => None None => None
entrypoint remote_all_some(xs : list(option(int))) : option(list(int)) = function remote_all_some(xs : list(option(int))) : option(list(int)) =
state.worker.all_some(gas = 10000, xs) state.worker.all_some(gas = 10000, xs)
+3 -3
View File
@@ -3,7 +3,7 @@ contract Counter =
record state = { value : int } record state = { value : int }
entrypoint init(val) = { value = val } function init(val) = { value = val }
entrypoint get() = state.value function get() = state.value
stateful entrypoint tick() = put(state{ value = state.value + 1 }) function tick() = put(state{ value = state.value + 1 })
-4
View File
@@ -1,4 +0,0 @@
include "cyclic_include_forth.aes"
contract CI =
entrypoint ci() = Back.back() + Forth.forth()
-4
View File
@@ -1,4 +0,0 @@
include "cyclic_include_forth.aes"
namespace Back =
function back() = 2
-4
View File
@@ -1,4 +0,0 @@
include "cyclic_include_back.aes"
namespace Forth =
function forth() = 3
+5 -5
View File
@@ -1,5 +1,5 @@
namespace MyList = namespace List =
function map1(f : 'a => 'b, xs : list('a)) = function map1(f : 'a => 'b, xs : list('a)) =
switch(xs) switch(xs)
@@ -13,9 +13,9 @@ namespace MyList =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = function inc1(xs : list(int)) : list(int) =
MyList.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = function inc2(xs : list(int)) : list(int) =
MyList.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
-7
View File
@@ -1,7 +0,0 @@
include "included.aes"
include "../contracts/included.aes"
contract Include =
entrypoint foo() =
Included.foo()
+6 -3
View File
@@ -10,13 +10,16 @@ contract DutchAuction =
sold : bool } sold : bool }
// Add to work around current lack of predefined functions // Add to work around current lack of predefined functions
stateful function spend(to, amount) = private function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
private function require(b : bool, err : string) =
if(!b) abort(err)
// TTL set by user on posting contract, typically (start - end ) div dec // TTL set by user on posting contract, typically (start - end ) div dec
entrypoint init(beneficiary, start, decrease) : state = public function init(beneficiary, start, decrease) : state =
require(start > 0 && decrease > 0, "bad args") require(start > 0 && decrease > 0, "bad args")
{ start_amount = start, { start_amount = start,
start_height = Chain.block_height, start_height = Chain.block_height,
@@ -27,7 +30,7 @@ contract DutchAuction =
// -- API // -- API
// We are the buyer... interesting case to buy for someone else and keep 10% // We are the buyer... interesting case to buy for someone else and keep 10%
stateful entrypoint bid() = public stateful function bid() =
require( !(state.sold), "sold") require( !(state.sold), "sold")
let cost = let cost =
state.start_amount - (Chain.block_height - state.start_height) * state.dec state.start_amount - (Chain.block_height - state.start_height) * state.dec
+23 -23
View File
@@ -1,69 +1,69 @@
// Testing primitives for accessing the block chain environment // Testing primitives for accessing the block chain environment
contract Interface = contract Interface =
entrypoint contract_address : () => address function contract_address : () => address
entrypoint call_origin : () => address function call_origin : () => address
entrypoint call_caller : () => address function call_caller : () => address
entrypoint call_value : () => int function call_value : () => int
contract Environment = contract Environment =
record state = {remote : Interface} record state = {remote : Interface}
entrypoint init(remote) = {remote = remote} function init(remote) = {remote = remote}
stateful entrypoint set_remote(remote) = put({remote = remote}) function set_remote(remote) = put({remote = remote})
// -- Information about the this contract --- // -- Information about the this contract ---
// Address // Address
entrypoint contract_address() : address = Contract.address function contract_address() : address = Contract.address
entrypoint nested_address(who) : address = function nested_address(who) : address =
who.contract_address(gas = 1000) who.contract_address(gas = 1000)
// Balance // Balance
entrypoint contract_balance() : int = Contract.balance function contract_balance() : int = Contract.balance
// -- Information about the current call --- // -- Information about the current call ---
// Origin // Origin
entrypoint call_origin() : address = Call.origin function call_origin() : address = Call.origin
entrypoint nested_origin() : address = function nested_origin() : address =
state.remote.call_origin() state.remote.call_origin()
// Caller // Caller
entrypoint call_caller() : address = Call.caller function call_caller() : address = Call.caller
entrypoint nested_caller() : address = function nested_caller() : address =
state.remote.call_caller() state.remote.call_caller()
// Value // Value
entrypoint call_value() : int = Call.value function call_value() : int = Call.value
stateful entrypoint nested_value(value : int) : int = function nested_value(value : int) : int =
state.remote.call_value(value = value / 2) state.remote.call_value(value = value / 2)
// Gas price // Gas price
entrypoint call_gas_price() : int = Call.gas_price function call_gas_price() : int = Call.gas_price
// -- Information about the chain --- // -- Information about the chain ---
// Account balances // Account balances
entrypoint get_balance(acct : address) : int = Chain.balance(acct) function get_balance(acct : address) : int = Chain.balance(acct)
// Block hash // Block hash
entrypoint block_hash(height : int) : option(hash) = Chain.block_hash(height) function block_hash(height : int) : int = Chain.block_hash(height)
// Coinbase // Coinbase
entrypoint coinbase() : address = Chain.coinbase function coinbase() : address = Chain.coinbase
// Block timestamp // Block timestamp
entrypoint timestamp() : int = Chain.timestamp function timestamp() : int = Chain.timestamp
// Block height // Block height
entrypoint block_height() : int = Chain.block_height function block_height() : int = Chain.block_height
// Difficulty // Difficulty
entrypoint difficulty() : int = Chain.difficulty function difficulty() : int = Chain.difficulty
// Gas limit // Gas limit
entrypoint gas_limit() : int = Chain.gas_limit function gas_limit() : int = Chain.gas_limit
+3
View File
@@ -77,6 +77,9 @@ contract ERC20Token =
put( state{approval_log = e :: state.approval_log }) put( state{approval_log = e :: state.approval_log })
e e
private function require(b : bool, err : string) =
if(!b) abort(err)
private function sub(_a : int, _b : int) : int = private function sub(_a : int, _b : int) : int =
require(_b =< _a, "Error") require(_b =< _a, "Error")
_a - _b _a - _b
+16 -34
View File
@@ -1,40 +1,22 @@
contract Remote =
entrypoint dummy : () => unit
contract Events = contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
// Valid index types datatype event =
type ix1 = int Event1(indexed alias_int, indexed int, string)
type ix2 = bool | Event2(alias_string, indexed alias_address)
type ix3 = bits // | BadEvent1(indexed string, string)
type ix4 = bytes(12) // | BadEvent2(indexed int, int)
type ix5 = hash // bytes(32)
type ix6 = address
type ix7 = Remote
type ix8 = oracle(int, int)
type ix9 = oracle_query(int, int)
// Valid payload types function f1(x : int, y : string) =
type data1 = string Chain.event(Event1(x, x+1, y))
type data2 = signature // bytes(64)
type data3 = bytes(65)
datatype event function f2(s : string) =
= Nodata0 Chain.event(Event2(s, Call.caller))
| Nodata1(ix1)
| Nodata2(ix2, ix3)
| Nodata3(ix4, ix5, ix6)
| Data0(data1)
| Data1(data2, ix7)
| Data2(ix8, data3, ix9)
| Data3(ix1, ix2, ix5, data1)
entrypoint nodata0() = Chain.event(Nodata0) function f3(x : int) =
entrypoint nodata1(ix1) = Chain.event(Nodata1(ix1)) Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
entrypoint nodata2(ix2, ix3) = Chain.event(Nodata2(ix2, ix3))
entrypoint nodata3(ix4, ix5, ix6) = Chain.event(Nodata3(ix4, ix5, ix6))
entrypoint data0(data1) = Chain.event(Data0(data1))
entrypoint data1(data2, ix7) = Chain.event(Data1(data2, ix7))
entrypoint data2(ix8, data3, ix9) = Chain.event(Data2(ix8, data3, ix9))
entrypoint data3(ix1, ix2, ix5, data1) = Chain.event(Data3(ix1, ix2, ix5, data1))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+4 -4
View File
@@ -1,17 +1,17 @@
// An implementation of the factorial function where each recursive // An implementation of the factorial function where each recursive
// call is to another contract. Not the cheapest way to compute factorial. // call is to another contract. Not the cheapest way to compute factorial.
contract FactorialServer = contract FactorialServer =
entrypoint fac : (int) => int function fac : (int) => int
contract Factorial = contract Factorial =
record state = {worker : FactorialServer} record state = {worker : FactorialServer}
entrypoint init(worker) = {worker = worker} function init(worker) = {worker = worker}
stateful entrypoint set_worker(worker) = put(state{worker = worker}) function set_worker(worker) = put(state{worker = worker})
entrypoint fac(x : int) : int = function fac(x : int) : int =
if(x == 0) 1 if(x == 0) 1
else x * state.worker.fac(x - 1) else x * state.worker.fac(x - 1)
-5
View File
@@ -1,5 +0,0 @@
contract Fail =
record pt = {x : int, y : int}
record r = {p : pt}
function fail() = {p.x = 0, p.y = 0}
-47
View File
@@ -1,47 +0,0 @@
contract FunctionArguments =
entrypoint sum(n : int, m: int) =
n + m
entrypoint append(xs : list(string)) =
switch(xs)
[] => ""
y :: ys => String.concat(y, append(ys))
entrypoint menot(b) =
!b
entrypoint bitsum(b : bits) =
Bits.sum(b)
record answer('a) = {label : string, result : 'a}
entrypoint read(a : answer(int)) =
a.result
entrypoint sjutton(b : bytes(17)) =
b
entrypoint sextiosju(b : bytes(67)) =
b
entrypoint trettiotva(b : bytes(32)) =
b
entrypoint find_oracle(o : oracle(int, bool)) =
true
entrypoint find_query(q : oracle_query(int, bool)) =
true
datatype colour() = Green | Yellow | Red | Pantone(int)
entrypoint traffic_light(c : colour) =
Red
entrypoint tuples(t : unit) =
t
entrypoint due(t : Chain.ttl) =
true
-15
View File
@@ -1,15 +0,0 @@
contract Functions =
function curry(f : ('a, 'b) => 'c) =
(x) => (y) => f(x, y)
function map(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map(f, xs)
function map'() = map
function plus(x, y) = x + y
entrypoint test1(xs : list(int)) = map(curry(plus)(5), xs)
entrypoint test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
entrypoint test3(xs : list(int)) =
let m(f, xs) = map(f, xs)
m((x) => x + 1, xs)
+10 -7
View File
@@ -12,20 +12,23 @@ contract FundMe =
deadline : int, deadline : int,
goal : int } goal : int }
stateful function spend(args : spend_args) = private function require(b : bool, err : string) =
if(!b) abort(err)
private function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount) Chain.spend(args.recipient, args.amount)
entrypoint init(beneficiary, deadline, goal) : state = public function init(beneficiary, deadline, goal) : state =
{ contributions = {}, { contributions = {},
beneficiary = beneficiary, beneficiary = beneficiary,
deadline = deadline, deadline = deadline,
total = 0, total = 0,
goal = goal } goal = goal }
function is_contributor(addr) = private function is_contributor(addr) =
Map.member(addr, state.contributions) Map.member(addr, state.contributions)
stateful entrypoint contribute() = public stateful function contribute() =
if(Chain.block_height >= state.deadline) if(Chain.block_height >= state.deadline)
spend({ recipient = Call.caller, amount = Call.value }) // Refund money spend({ recipient = Call.caller, amount = Call.value }) // Refund money
false false
@@ -36,7 +39,7 @@ contract FundMe =
total @ tot = tot + Call.value }) total @ tot = tot + Call.value })
true true
stateful entrypoint withdraw() = public stateful function withdraw() =
if(Chain.block_height < state.deadline) if(Chain.block_height < state.deadline)
abort("Cannot withdraw before deadline") abort("Cannot withdraw before deadline")
if(Call.caller == state.beneficiary) if(Call.caller == state.beneficiary)
@@ -46,13 +49,13 @@ contract FundMe =
else else
abort("Not a contributor or beneficiary") abort("Not a contributor or beneficiary")
stateful function withdraw_beneficiary() = private stateful function withdraw_beneficiary() =
require(state.total >= state.goal, "Project was not funded") require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary, spend({recipient = state.beneficiary,
amount = Contract.balance }) amount = Contract.balance })
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts }) put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
stateful function withdraw_contributor() = private stateful function withdraw_contributor() =
if(state.total >= state.goal) if(state.total >= state.goal)
abort("Project was funded") abort("Project was funded")
let to = Call.caller let to = Call.caller
+1 -1
View File
@@ -1,3 +1,3 @@
contract Identity = contract Identity =
entrypoint main (x:int) = x function main (x:int) = x
+2 -2
View File
@@ -2,8 +2,8 @@ include "included.aes"
include "../contracts/included2.aes" include "../contracts/included2.aes"
contract Include = contract Include =
entrypoint foo() = function foo() =
Included.foo() < Included2a.bar() Included.foo() < Included2a.bar()
entrypoint bar() = function bar() =
Included2b.foo() > Included.foo() Included2b.foo() > Included.foo()
+2 -2
View File
@@ -3,6 +3,6 @@ contract InitTypeError =
type state = map(int, int) type state = map(int, int)
// Check that the compiler catches ill-typed init entrypoint // Check that the compiler catches ill-typed init function
entrypoint init() = "not the right type!" function init() = "not the right type!"
-23
View File
@@ -1,23 +0,0 @@
contract ListComp =
entrypoint sample1() = [1,2,3]
entrypoint sample2() = [4,5]
entrypoint l1() = [x | x <- sample1()]
entrypoint l1_true() = [1,2,3]
entrypoint l2() = [x + y | x <- sample1(), y <- sample2()]
entrypoint l2_true() = [5,6,6,7,7,8]
entrypoint l3() = [x ++ y | x <- [[":)"] | x <- [1,2]]
, y <- [[":("]]]
entrypoint l3_true() = [[":)", ":("], [":)", ":("]]
entrypoint l4() = [(a, b, c) | let is_pit(a, b, c) = a*a + b*b == c*c
, let base = [1..10]
, a <- base
, b <- base, if (b >= a)
, c <- base, if (c >= b)
, if (is_pit(a, b, c))
]
entrypoint l4_true() = [(3, 4, 5), (6, 8, 10)]
-2
View File
@@ -1,2 +0,0 @@
contract BadComp =
entrypoint failing() = [x + 1 | x <- [1,2,3], let x = "XD"]
-2
View File
@@ -1,2 +0,0 @@
contract BadComp =
entrypoint failing() = [x | x <- [], if (3)]
-2
View File
@@ -1,2 +0,0 @@
contract ListCompBad =
entrypoint failing() = [x | x <- 1]
-6
View File
@@ -1,6 +0,0 @@
// This should include Lists.aes implicitly, since Option.aes does.
include "Option.aes"
contract Test =
entrypoint i_should_build() =
List.is_empty(Option.to_list(None))
+56 -56
View File
@@ -4,97 +4,97 @@ contract Maps =
record state = { map_i : map(int, pt), record state = { map_i : map(int, pt),
map_s : map(string, pt) } map_s : map(string, pt) }
entrypoint init() = { map_i = {}, map_s = {} } function init() = { map_i = {}, map_s = {} }
entrypoint get_state() = state function get_state() = state
// {[k] = v} // {[k] = v}
entrypoint map_i() = function map_i() =
{ [1] = {x = 1, y = 2}, { [1] = {x = 1, y = 2},
[2] = {x = 3, y = 4}, [2] = {x = 3, y = 4},
[3] = {x = 5, y = 6} } [3] = {x = 5, y = 6} }
entrypoint map_s() = function map_s() =
{ ["one"] = {x = 1, y = 2}, { ["one"] = {x = 1, y = 2},
["two"] = {x = 3, y = 4}, ["two"] = {x = 3, y = 4},
["three"] = {x = 5, y = 6} } ["three"] = {x = 5, y = 6} }
stateful entrypoint map_state_i() = put(state{ map_i = map_i() }) function map_state_i() = put(state{ map_i = map_i() })
stateful entrypoint map_state_s() = put(state{ map_s = map_s() }) function map_state_s() = put(state{ map_s = map_s() })
// m[k] // m[k]
entrypoint get_i(k, m : map(int, pt)) = m[k] function get_i(k, m : map(int, pt)) = m[k]
entrypoint get_s(k, m : map(string, pt)) = m[k] function get_s(k, m : map(string, pt)) = m[k]
entrypoint get_state_i(k) = get_i(k, state.map_i) function get_state_i(k) = get_i(k, state.map_i)
entrypoint get_state_s(k) = get_s(k, state.map_s) function get_state_s(k) = get_s(k, state.map_s)
// m[k = v] // m[k = v]
entrypoint get_def_i(k, v, m : map(int, pt)) = m[k = v] function get_def_i(k, v, m : map(int, pt)) = m[k = v]
entrypoint get_def_s(k, v, m : map(string, pt)) = m[k = v] function get_def_s(k, v, m : map(string, pt)) = m[k = v]
entrypoint get_def_state_i(k, v) = get_def_i(k, v, state.map_i) function get_def_state_i(k, v) = get_def_i(k, v, state.map_i)
entrypoint get_def_state_s(k, v) = get_def_s(k, v, state.map_s) function get_def_state_s(k, v) = get_def_s(k, v, state.map_s)
// m{[k] = v} // m{[k] = v}
entrypoint set_i(k, p, m : map(int, pt)) = m{ [k] = p } function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
entrypoint set_s(k, p, m : map(string, pt)) = m{ [k] = p } function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
stateful entrypoint set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) }) function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
stateful entrypoint set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) }) function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
// m{f[k].x = v} // m{f[k].x = v}
entrypoint setx_i(k, x, m : map(int, pt)) = m{ [k].x = x } function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
entrypoint setx_s(k, x, m : map(string, pt)) = m{ [k].x = x } function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
stateful entrypoint setx_state_i(k, x) = put(state{ map_i[k].x = x }) function setx_state_i(k, x) = put(state{ map_i[k].x = x })
stateful entrypoint setx_state_s(k, x) = put(state{ map_s[k].x = x }) function setx_state_s(k, x) = put(state{ map_s[k].x = x })
// m{[k] @ x = v } // m{[k] @ x = v }
entrypoint addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d } function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
entrypoint addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d } function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
stateful entrypoint addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d }) function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
stateful entrypoint addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d }) function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
// m{[k = def] @ x = v } // m{[k = def] @ x = v }
entrypoint addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d } function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
entrypoint addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d } function addx_def_s(k, v, d, m : map(string, pt)) = m{ [k = v].x @ x = x + d }
// Map.member // Map.member
entrypoint member_i(k, m : map(int, pt)) = Map.member(k, m) function member_i(k, m : map(int, pt)) = Map.member(k, m)
entrypoint member_s(k, m : map(string, pt)) = Map.member(k, m) function member_s(k, m : map(string, pt)) = Map.member(k, m)
entrypoint member_state_i(k) = member_i(k, state.map_i) function member_state_i(k) = member_i(k, state.map_i)
entrypoint member_state_s(k) = member_s(k, state.map_s) function member_state_s(k) = member_s(k, state.map_s)
// Map.lookup // Map.lookup
entrypoint lookup_i(k, m : map(int, pt)) = Map.lookup(k, m) function lookup_i(k, m : map(int, pt)) = Map.lookup(k, m)
entrypoint lookup_s(k, m : map(string, pt)) = Map.lookup(k, m) function lookup_s(k, m : map(string, pt)) = Map.lookup(k, m)
entrypoint lookup_state_i(k) = lookup_i(k, state.map_i) function lookup_state_i(k) = lookup_i(k, state.map_i)
entrypoint lookup_state_s(k) = lookup_s(k, state.map_s) function lookup_state_s(k) = lookup_s(k, state.map_s)
// Map.lookup_default // Map.lookup_default
entrypoint lookup_def_i(k, m : map(int, pt), def : pt) = function lookup_def_i(k, m : map(int, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_s(k, m : map(string, pt), def : pt) = function lookup_def_s(k, m : map(string, pt), def : pt) =
Map.lookup_default(k, m, def) Map.lookup_default(k, m, def)
entrypoint lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def) function lookup_def_state_i(k, def) = lookup_def_i(k, state.map_i, def)
entrypoint lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def) function lookup_def_state_s(k, def) = lookup_def_s(k, state.map_s, def)
// Map.delete // Map.delete
entrypoint delete_i(k, m : map(int, pt)) = Map.delete(k, m) function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
entrypoint delete_s(k, m : map(string, pt)) = Map.delete(k, m) function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
stateful entrypoint delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) }) function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
stateful entrypoint delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) }) function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
// Map.size // Map.size
entrypoint size_i(m : map(int, pt)) = Map.size(m) function size_i(m : map(int, pt)) = Map.size(m)
entrypoint size_s(m : map(string, pt)) = Map.size(m) function size_s(m : map(string, pt)) = Map.size(m)
entrypoint size_state_i() = size_i(state.map_i) function size_state_i() = size_i(state.map_i)
entrypoint size_state_s() = size_s(state.map_s) function size_state_s() = size_s(state.map_s)
// Map.to_list // Map.to_list
entrypoint tolist_i(m : map(int, pt)) = Map.to_list(m) function tolist_i(m : map(int, pt)) = Map.to_list(m)
entrypoint tolist_s(m : map(string, pt)) = Map.to_list(m) function tolist_s(m : map(string, pt)) = Map.to_list(m)
entrypoint tolist_state_i() = tolist_i(state.map_i) function tolist_state_i() = tolist_i(state.map_i)
entrypoint tolist_state_s() = tolist_s(state.map_s) function tolist_state_s() = tolist_s(state.map_s)
// Map.from_list // Map.from_list
entrypoint fromlist_i(xs : list(int * pt)) = Map.from_list(xs) function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
entrypoint fromlist_s(xs : list(string * pt)) = Map.from_list(xs) function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
stateful entrypoint fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) }) function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
stateful entrypoint fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) }) function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
@@ -3,6 +3,6 @@ contract MissingFieldsInRecordExpr =
record r('a) = {x : int, y : string, z : 'a} record r('a) = {x : int, y : string, z : 'a}
type alias('a) = r('a) type alias('a) = r('a)
entrypoint fail1() = { x = 0 } function fail1() = { x = 0 }
entrypoint fail2(z : 'a) : r('a) = { y = "string", z = z } function fail2(z : 'a) : r('a) = { y = "string", z = z }
entrypoint fail3() : alias(int) = { x = 0, z = 1 } function fail3() : alias(int) = { x = 0, z = 1 }
+1 -1
View File
@@ -2,5 +2,5 @@
contract MissingStateType = contract MissingStateType =
// Check that we get a type error also for implicit state // Check that we get a type error also for implicit state
entrypoint init() = "should be ()" function init() = "should be ()"
-12
View File
@@ -1,12 +0,0 @@
namespace Lib =
entrypoint foo() = ()
contract Remote =
public function foo : () => unit
function bla() = ()
contract Contract =
public function foo() = ()
public private stateful function all_the_things() = ()
private entrypoint wha() = ()
+2 -2
View File
@@ -28,8 +28,8 @@ contract MultiSig =
let n = length(owners) + 1 let n = length(owners) + 1
{ nRequired = nRequired, { nRequired = nRequired,
nOwners = n, nOwners = n,
owners = Map.from_list(MyList.zip([1..n], caller() :: owners)), owners = Map.from_list(List.zip([1..n], caller() :: owners)),
ownerIndex = Map.from_list(MyList.zip(caller() :: owners, [1..n])) } ownerIndex = Map.from_list(List.zip(caller() :: owners, [1..n])) }
function lookup(map, key) = function lookup(map, key) =
switch(Map.get(key, map)) switch(Map.get(key, map))
+10 -11
View File
@@ -1,17 +1,16 @@
contract NameClash = contract NameClash =
entrypoint double_proto : () => int function double_proto : () => int
entrypoint double_proto : () => int function double_proto : () => int
entrypoint proto_and_def : int => int function proto_and_def : int => int
entrypoint proto_and_def(n) = n + 1 function proto_and_def(n) = n + 1
entrypoint double_def(x) = x function double_def(x) = x
entrypoint double_def(y) = 0 function double_def(y) = 0
// abort, require, put and state are builtin // abort, put and state are builtin
entrypoint abort() : int = 0 function abort() : int = 0
entrypoint require(b, err) = if(b) abort(err) function put(x) = x
entrypoint put(x) = x function state(x, y) = x + y
entrypoint state(x, y) = x + y
-18
View File
@@ -1,18 +0,0 @@
namespace Foo =
record bla = {x : int, y : bool}
function bar() : Foo.bla = {x = 17, y = true}
contract Bug =
// Crashed the type checker
entrypoint foo() = Foo.bar()
// Also crashed the type checker
type t = Foo.bla
entrypoint test() =
let x : t = Foo.bar()
x
+1 -1
View File
@@ -2,4 +2,4 @@
// You can't shadow existing contracts or namespaces. // You can't shadow existing contracts or namespaces.
contract Call = contract Call =
entrypoint whatever() = () function whatever() = ()
+5 -5
View File
@@ -1,5 +1,5 @@
namespace MyList = namespace List =
function map1(f : 'a => 'b, xs : list('a)) = function map1(f : 'a => 'b, xs : list('a)) =
switch(xs) switch(xs)
@@ -13,9 +13,9 @@ namespace MyList =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = function inc1(xs : list(int)) : list(int) =
MyList.map1((x) => x + 1, xs) List.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = function inc2(xs : list(int)) : list(int) =
MyList.map2((x) => x + 1, xs) List.map2((x) => x + 1, xs)
+23 -21
View File
@@ -9,31 +9,31 @@ contract Oracles =
type oracle_id = oracle(query_t, answer_t) type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t) type query_id = oracle_query(query_t, answer_t)
stateful entrypoint registerOracle(acct : address, function registerOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful entrypoint registerIntIntOracle(acct : address, function registerIntIntOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle(int, int) = ttl : ttl) : oracle(int, int) =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful entrypoint registerStringStringOracle(acct : address, function registerStringStringOracle(acct : address,
qfee : fee, qfee : fee,
ttl : ttl) : oracle(string, string) = ttl : ttl) : oracle(string, string) =
Oracle.register(acct, qfee, ttl) Oracle.register(acct, qfee, ttl)
stateful entrypoint signedRegisterOracle(acct : address, function signedRegisterOracle(acct : address,
sign : signature, sign : signature,
qfee : fee, qfee : fee,
ttl : ttl) : oracle_id = ttl : ttl) : oracle_id =
Oracle.register(acct, qfee, ttl, signature = sign) Oracle.register(acct, qfee, ttl, signature = sign)
entrypoint queryFee(o : oracle_id) : fee = function queryFee(o : oracle_id) : fee =
Oracle.query_fee(o) Oracle.query_fee(o)
stateful entrypoint createQuery(o : oracle_id, function createQuery(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -42,7 +42,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl) Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production! // Do not use in production!
stateful entrypoint unsafeCreateQuery(o : oracle_id, function unsafeCreateQuery(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -50,7 +50,7 @@ contract Oracles =
Oracle.query(o, q, qfee, qttl, rttl) Oracle.query(o, q, qfee, qttl, rttl)
// Do not use in production! // Do not use in production!
stateful entrypoint unsafeCreateQueryThenErr(o : oracle_id, function unsafeCreateQueryThenErr(o : oracle_id,
q : query_t, q : query_t,
qfee : fee, qfee : fee,
qttl : ttl, qttl : ttl,
@@ -59,52 +59,54 @@ contract Oracles =
require(qfee >= 100000000000000000, "causing a late error") require(qfee >= 100000000000000000, "causing a late error")
res res
stateful entrypoint extendOracle(o : oracle_id, function extendOracle(o : oracle_id,
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful entrypoint signedExtendOracle(o : oracle_id, function signedExtendOracle(o : oracle_id,
sign : signature, // Signed oracle address sign : signature, // Signed oracle address
ttl : ttl) : unit = ttl : ttl) : () =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, r) Oracle.respond(o, q, r)
stateful entrypoint signedRespond(o : oracle_id, function signedRespond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
entrypoint getQuestion(o : oracle_id, function getQuestion(o : oracle_id,
q : query_id) : query_t = q : query_id) : query_t =
Oracle.get_question(o, q) Oracle.get_question(o, q)
entrypoint hasAnswer(o : oracle_id, function hasAnswer(o : oracle_id,
q : query_id) = q : query_id) =
switch(Oracle.get_answer(o, q)) switch(Oracle.get_answer(o, q))
None => false None => false
Some(_) => true Some(_) => true
entrypoint getAnswer(o : oracle_id, function getAnswer(o : oracle_id,
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
datatype complexQuestion = Why(int) | How(string) datatype complexQuestion = Why(int) | How(string)
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int) datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
stateful entrypoint complexOracle(question) = function complexOracle(question) =
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer) let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100)) let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337)) Oracle.respond(o, q, Answer(question, "magic", 1337))
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
stateful entrypoint signedComplexOracle(question, sig) = function signedComplexOracle(question, sig) =
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer) let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100)) let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig) Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
+2
View File
@@ -20,3 +20,5 @@ contract OraclesGas =
Oracle.respond(o, q, answer) Oracle.respond(o, q, answer)
() ()
private function require(b : bool, err : string) =
if(!b) abort(err)
+3 -1
View File
@@ -21,7 +21,7 @@ contract Oracles =
function respond(o : oracle_id, function respond(o : oracle_id,
q : query_id, q : query_id,
sign : signature, sign : signature,
r : answer_t) : unit = r : answer_t) : () =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
@@ -33,3 +33,5 @@ contract Oracles =
q : query_id) : option(answer_t) = q : query_id) : option(answer_t) =
Oracle.get_answer(o, q) Oracle.get_answer(o, q)
private function require(b : bool, err : string) =
if(!b) abort(err)
-3
View File
@@ -1,3 +0,0 @@
payable contract Test =
payable entrypoint foo(x : int) = ()
function bar() = 42
+3 -3
View File
@@ -47,7 +47,7 @@ module Voting : Voting = {
let init(proposalNames: args): state = let init(proposalNames: args): state =
{ chairPerson: caller(), { chairPerson: caller(),
voters: AddrMap.empty, voters: AddrMap.empty,
proposals: MyList.map((name) => {name: name, voteCount: 0}, proposalNames) proposals: List.map((name) => {name: name, voteCount: 0}, proposalNames)
}; };
/* Boilerplate */ /* Boilerplate */
@@ -73,7 +73,7 @@ module Voting : Voting = {
}; };
let addVote(candidate, weight) = { let addVote(candidate, weight) = {
let proposal = MyList.nth(state().proposals, candidate); let proposal = List.nth(state().proposals, candidate);
proposal.voteCount = proposal.voteCount + weight; proposal.voteCount = proposal.voteCount + weight;
}; };
@@ -121,6 +121,6 @@ module Voting : Voting = {
/* const */ /* const */
let currentTally() = let currentTally() =
MyList.map((p) => (p.name, p.voteCount), state().proposals); List.map((p) => (p.name, p.voteCount), state().proposals);
} }
+1 -1
View File
@@ -13,7 +13,7 @@ open Voting;
let print_tally() = { let print_tally() = {
let tally = call(other, () => currentTally()); let tally = call(other, () => currentTally());
MyList.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally); List.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally);
let winner = call(other, () => winnerName()); let winner = call(other, () => winnerName());
Printf.printf("Winner: %s\n", winner); Printf.printf("Winner: %s\n", winner);
}; };
+9 -9
View File
@@ -1,27 +1,27 @@
contract Remote1 = contract Remote1 =
entrypoint main : (int) => int function main : (int) => int
contract Remote2 = contract Remote2 =
entrypoint call : (Remote1, int) => int function call : (Remote1, int) => int
contract Remote3 = contract Remote3 =
entrypoint get : () => int function get : () => int
entrypoint tick : () => unit function tick : () => ()
contract RemoteCall = contract RemoteCall =
stateful entrypoint call(r : Remote1, x : int) : int = function call(r : Remote1, x : int) : int =
r.main(gas = 10000, value = 10, x) r.main(gas = 10000, value = 10, x)
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) = function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
r2.call(r1, x) r2.call(r1, x)
entrypoint increment(r3 : Remote3) = function increment(r3 : Remote3) =
r3.tick() r3.tick()
entrypoint get(r3 : Remote3) = function get(r3 : Remote3) =
r3.get() r3.get()
entrypoint plus(x, y) = x + y function plus(x, y) = x + y
+99
View File
@@ -0,0 +1,99 @@
contract Oracles =
function registerOracle :
(address,
int,
Chain.ttl) => oracle(string, int)
function createQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function unsafeCreateQuery :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
function respond :
(oracle(string, int),
oracle_query(string, int),
int) => ()
contract OraclesErr =
function unsafeCreateQueryThenErr :
(oracle(string, int),
string,
int,
Chain.ttl,
Chain.ttl) => oracle_query(string, int)
contract RemoteOracles =
public function callRegisterOracle(
r : Oracles,
acct : address,
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
r.registerOracle(acct, qfee, ttl)
public function callCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
require(value =< Call.value, "insufficient value")
r.createQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQuery(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryThenErr(
r : OraclesErr,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
// Do not use in production!
public function callUnsafeCreateQueryAndThenErr(
r : Oracles,
value : int,
o : oracle(string, int),
q : string,
qfee : int,
qttl : Chain.ttl,
rttl : Chain.ttl) : oracle_query(string, int) =
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
switch(0) 1 => ()
x // Never reached.
public function callRespond(
r : Oracles,
o : oracle(string, int),
q : oracle_query(string, int),
qr : int) =
r.respond(o, q, qr)
private function require(b : bool, err : string) =
if(!b) abort(err)
-1
View File
@@ -1,4 +1,3 @@
contract Simple = contract Simple =
type t = int => int type t = int => int
entrypoint dummy() = ()
+5 -5
View File
@@ -6,11 +6,11 @@
contract SimpleStorage { contract SimpleStorage {
uint storedData uint storedData
entrypoint set(uint x) { function set(uint x) {
storedData = x storedData = x
} }
entrypoint get() constant returns (uint) { function get() constant returns (uint) {
return storedData return storedData
} }
} }
@@ -20,9 +20,9 @@ contract SimpleStorage =
record state = { data : int } record state = { data : int }
entrypoint init(value : int) : state = { data = value } function init(value : int) : state = { data = value }
entrypoint get() : int = state.data function get() : int = state.data
stateful entrypoint set(value : int) = function set(value : int) =
put(state{data = value}) put(state{data = value})
+7 -7
View File
@@ -1,26 +1,26 @@
contract SpendContract = contract SpendContract =
entrypoint withdraw : (int) => int function withdraw : (int) => int
contract SpendTest = contract SpendTest =
stateful entrypoint spend(to, amount) = function spend(to, amount) =
let total = Contract.balance let total = Contract.balance
Chain.spend(to, amount) Chain.spend(to, amount)
total - amount total - amount
stateful entrypoint withdraw(amount) : int = function withdraw(amount) : int =
spend(Call.caller, amount) spend(Call.caller, amount)
stateful entrypoint withdraw_from(account, amount) = function withdraw_from(account, amount) =
account.withdraw(amount) account.withdraw(amount)
withdraw(amount) withdraw(amount)
stateful entrypoint spend_from(from, to, amount) = function spend_from(from, to, amount) =
from.withdraw(amount) from.withdraw(amount)
Chain.spend(to, amount) Chain.spend(to, amount)
Chain.balance(to) Chain.balance(to)
entrypoint get_balance() = Contract.balance function get_balance() = Contract.balance
entrypoint get_balance_of(a) = Chain.balance(a) function get_balance_of(a) = Chain.balance(a)

Some files were not shown because too many files have changed in this diff Show More