Compare commits

...

38 Commits

Author SHA1 Message Date
Hans Svensson fd6cde535e Merge pull request #296 from davidyuk/patch-3
docs: Use consistent event definitions between examples
2021-06-24 09:30:08 +02:00
Hans Svensson b25339bb8f Merge pull request #303 from aeternity/radrow-patch-1
Mention `init` quirk with Call.value
2021-04-29 18:51:13 +02:00
Hans Svensson cf793667ca Merge pull request #304 from aeternity/clarify-aesocompiler
Clarify aeso_compiler use
2021-04-29 18:50:32 +02:00
Artur Puzio c54a0cec3d Clarify aeso_compiler use 2021-03-24 10:34:39 +00:00
Radosław Rowicki bc47c25138 Mention init quirk with Call.value 2021-03-22 10:26:34 +01:00
Radosław Rowicki 3b2ce63fa7 Merge pull request #300 from aeternity/erlps-lima
Trampoline in parser
2021-03-08 13:33:34 +01:00
radrow 8b4a1aaf0d Trampoline 2021-03-08 12:45:21 +01:00
Radosław Rowicki c6e7db2381 Merge pull request #299 from aeternity/fix-ets
Fix constraints ordering
2021-03-05 10:42:16 +01:00
radrow 4e60d019ca Fix constraints ordering 2021-02-23 11:05:02 +01:00
Radosław Rowicki b8002029cf Merge pull request #294 from aeternity/mergesort
Upgrade sorting function
2021-02-23 08:58:36 +01:00
Denis Davidyuk 4630f8a09b Use consistent event definitions between examples 2021-02-16 14:52:37 +03:00
radrow 1a14602f36 Upgrade sorting function 2021-02-09 14:18:42 +01:00
Hans Svensson e2ef95d6fd Merge pull request #293 from aeternity/GH-292-desugar_error
Properly handle type errors during desugar
2021-02-05 11:14:47 +01:00
Hans Svensson 22aaeceba8 Properly handle type errors during desugar 2021-01-25 21:28:10 +01:00
Radosław Rowicki f1d95484a5 Merge pull request #288 from aeternity/expose-interface-fix-lima
Fix interface exposure (lima)
2020-10-21 14:01:21 +02:00
radrow 7e65f26211 Fix interface exposure 2020-10-21 12:42:42 +02:00
Radosław Rowicki 0b83422189 Merge pull request #284 from aeternity/hermetization-turnoff
Debug mode to turn off hermetization
2020-10-12 13:17:40 +02:00
radrow 1a5017ce2b Debug mode turns off hermetization
Added tests and fixed bugs
2020-10-09 18:41:30 +02:00
Grzegorz Uriasz 25fa365c29 Merge pull request #280 from aeternity/optionally_generate_aci
Provide the ACI along with the bytecode
2020-09-10 10:05:51 +02:00
Grzegorz Uriasz bb728db51b Provide the ACI along with the bytecode 2020-09-09 18:39:02 +02:00
Grzegorz Uriasz 1fee306daa Merge pull request #279 from aeternity/optimize_calldata_generation
Improve call data encoding times by 35%
2020-09-09 15:50:36 +02:00
Grzegorz Uriasz c4eaf2249a Don't regenerate the AST 2020-09-09 15:33:34 +02:00
Hans Svensson 6c23fd0d41 Merge pull request #278 from aeternity/contains-fun
Added `contains` functions in List and Option. Fixed one type error
2020-08-27 11:25:38 +02:00
radrow 3d73e52d48 Fix tests 2020-08-26 15:56:21 +02:00
radrow 89b3ec3d17 minor optimization 2020-08-26 12:10:24 +02:00
radrow 7e32ef57c2 Added contains functions in List and Option. Fixed one type error catch 2020-08-26 11:56:18 +02:00
Hans Svensson ed5447e430 Merge pull request #273 from aeternity/GH272-singleton_record_calldata_decode
Fix singleton record calldata decode + test
2020-08-11 07:58:36 +02:00
Hans Svensson db4de5d926 Fix singleton record calldata decode + test 2020-08-10 16:37:09 +02:00
Radosław Rowicki 6b60fde2df Merge pull request #267 from aeternity/option-fix
Fixed `force` function
2020-06-18 15:05:55 +02:00
Radosław Rowicki dd8eea0d55 Merge pull request #261 from aeternity/fixes
Ban empty contracts, ban function blocks with mismatched declarations
2020-06-07 18:01:03 +02:00
Radosław Rowicki eb71abc665 Fixed force function 2020-06-06 14:36:46 +02:00
Hans Svensson eff1ad4688 Merge pull request #265 from aeternity/improve_docs
Make the network id an explicit part of the signature material
2020-06-05 13:00:42 +02:00
Hans Svensson cb2588fae2 Make the network id an explicit part of the signature material 2020-05-29 08:47:01 +02:00
Hans Svensson 08261a319b Merge pull request #264 from aeternity/update_readme
Make README less outdated
2020-05-29 08:45:23 +02:00
Hans Svensson f21717a9c0 Make README less outdated 2020-05-29 08:35:19 +02:00
Hans Svensson 9753f90034 Merge pull request #262 from aeternity/fix_types
Fix AENS types + whitespace
2020-05-27 08:10:18 +02:00
Hans Svensson 8f240a7ddf Fix AENS types + whitespace 2020-05-27 08:06:06 +02:00
radrow 54e43764ca Ban empty contracts, ban function blocks with mismatched declarations 2020-05-15 19:09:08 +02:00
19 changed files with 397 additions and 144 deletions
+5 -8
View File
@@ -1,12 +1,11 @@
# aesophia # aesophia
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code. This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system.
The compiler is currently being used three places
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
## Documentation ## Documentation
@@ -14,7 +13,6 @@ can then be loaded into the æternity system.
* [Sophia Documentation](docs/sophia.md). * [Sophia Documentation](docs/sophia.md).
* [Sophia Standard Library](docs/sophia_stdlib.md). * [Sophia Standard Library](docs/sophia_stdlib.md).
## Versioning ## Versioning
`aesophia` has a version that is only loosely connected to the version of the `aesophia` has a version that is only loosely connected to the version of the
@@ -23,7 +21,6 @@ minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc. API changes etc.
## Interface Modules ## Interface Modules
The basic modules for interfacing the compiler: The basic modules for interfacing the compiler:
+12 -12
View File
@@ -78,7 +78,7 @@ The main unit of code in Sophia is the *contract*.
- A contract may define a type `state` encapsulating its local - A contract may define a type `state` encapsulating its local
state. When creating a new contract the `init` entrypoint is executed and the state. When creating a new contract the `init` entrypoint is executed and the
state is initialized to its return value. state is initialized to its return value.
The language offers some primitive functions to interact with the blockchain and contracts. The language offers some primitive functions to interact with the blockchain and contracts.
Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract) Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract)
and the [Call](sophia_stdlib.md#Call) namespaces in the documentation. and the [Call](sophia_stdlib.md#Call) namespaces in the documentation.
@@ -279,7 +279,7 @@ so even cyclic includes should be working without any special tinkering.
### Standard library ### Standard library
Sophia offers [standard library](sophia_stdlib.md) which exposes some Sophia offers [standard library](sophia_stdlib.md) which exposes some
primitive operations and some higher level utilities. The builtin primitive operations and some higher level utilities. The builtin
namespaces like `Chain`, `Contract`, `Map` namespaces like `Chain`, `Contract`, `Map`
are included by default and are supported internally by the compiler. are included by default and are supported internally by the compiler.
@@ -446,7 +446,7 @@ Example syntax:
Lists can be constructed using the range syntax using special `..` operator: Lists can be constructed using the range syntax using special `..` operator:
``` ```
[1..4] == [1,2,3,4] [1..4] == [1,2,3,4]
``` ```
The ranges are always ascending and have step equal to 1. The ranges are always ascending and have step equal to 1.
@@ -551,7 +551,7 @@ Please refer to the `Bytes` [library documentation](sophia_stdlib.md#Bytes).
### Cryptographic builins ### Cryptographic builins
Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to
hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`. hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`.
#### AEVM note #### AEVM note
@@ -587,7 +587,7 @@ Example for an oracle answering questions of type `string` with answers of type
contract Oracles = contract Oracles =
stateful entrypoint registerOracle(acct : address, stateful entrypoint registerOracle(acct : address,
sign : signature, // Signed oracle address + contract address sign : signature, // Signed network id + oracle address + contract address
qfee : int, qfee : int,
ttl : Chain.ttl) : oracle(string, int) = ttl : Chain.ttl) : oracle(string, int) =
Oracle.register(acct, signature = sign, qfee, ttl) Oracle.register(acct, signature = sign, qfee, ttl)
@@ -608,13 +608,13 @@ contract Oracles =
Oracle.extend(o, ttl) Oracle.extend(o, ttl)
stateful entrypoint signExtendOracle(o : oracle(string, int), stateful entrypoint signExtendOracle(o : oracle(string, int),
sign : signature, // Signed oracle address + contract address sign : signature, // Signed network id + oracle address + contract address
ttl : Chain.ttl) : unit = ttl : Chain.ttl) : unit =
Oracle.extend(o, signature = sign, ttl) Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle(string, int), stateful entrypoint respond(o : oracle(string, int),
q : oracle_query(string, int), q : oracle_query(string, int),
sign : signature, // Signed oracle query id + contract address sign : signature, // Signed network id + oracle query id + contract address
r : int) = r : int) =
Oracle.respond(o, q, signature = sign, r) Oracle.respond(o, q, signature = sign, r)
@@ -635,7 +635,7 @@ contract Oracles =
#### Sanity checks #### Sanity checks
When an Oracle literal is passed to a contract, no deep checks are performed. When an Oracle literal is passed to a contract, no deep checks are performed.
For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query) For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query)
functions are provided. functions are provided.
@@ -658,7 +658,7 @@ To use events a contract must declare a datatype `event`, and events are then
logged using the `Chain.event` function: logged using the `Chain.event` function:
``` ```
datatype event datatype event
= Event1(int, int, string) = Event1(int, int, string)
| Event2(string, address) | Event2(string, address)
@@ -686,8 +686,8 @@ will emit one Event of each kind in the example.
``` ```
entrypoint emit_events() : () = entrypoint emit_events() : () =
Chain.event(TheFirstEvent(42)) Chain.event(Event1(42, 34, "foo"))
Chain.event(AnotherEvent(Contract.address, "This is not indexed")) Chain.event(Event2("This is not indexed", Contract.address))
``` ```
#### Argument order #### Argument order
+43 -17
View File
@@ -369,7 +369,7 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`.
* The `acct` is the address of the oracle to register (can be the same as the contract). * The `acct` is the address of the oracle to register (can be the same as the contract).
* `signature` is a signature proving that the contract is allowed to register the account - * `signature` is a signature proving that the contract is allowed to register the account -
the account address + the contract address (concatenated as byte arrays) is the `network id` + `account address` + `contract address` (concatenated as byte arrays) is
signed with the signed with the
private key of the account, proving you have the private key of the oracle to be. If the private key of the account, proving you have the private key of the oracle to be. If the
address is the same as the contract `sign` is ignored and can be left out entirely. address is the same as the contract `sign` is ignored and can be left out entirely.
@@ -403,7 +403,7 @@ Responds to the question `q` on `o`.
Unless the contract address is the same as the oracle address the `signature` Unless the contract address is the same as the oracle address the `signature`
(which is an optional, named argument) (which is an optional, named argument)
needs to be provided. Proving that we have the private key of the oracle by needs to be provided. Proving that we have the private key of the oracle by
signing the oracle query id + contract address signing the `network id` + `oracle query id` + `contract address`
### extend ### extend
@@ -468,7 +468,8 @@ Naming System (AENS).
If `owner` is equal to `Contract.address` the signature `signature` is If `owner` is equal to `Contract.address` the signature `signature` is
ignored, and can be left out since it is a named argument. Otherwise we need ignored, and can be left out since it is a named argument. Otherwise we need
a signature to prove that we are allowed to do AENS operations on behalf of a signature to prove that we are allowed to do AENS operations on behalf of
`owner` `owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature),
i.e. the signature material should be prefixed by the network id.
### resolve ### resolve
``` ```
@@ -486,7 +487,7 @@ type checked against this type at run time.
AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit
``` ```
The signature should be over `owner address` + `Contract.address` The signature should be over `network id` + `owner address` + `Contract.address`
(concatenated as byte arrays). (concatenated as byte arrays).
@@ -495,29 +496,29 @@ The signature should be over `owner address` + `Contract.address`
AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit
``` ```
The signature should be over `owner address` + `name_hash` + `Contract.address` The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
### transfer ### transfer
``` ```
AENS.transfer(owner : address, new_owner : address, name_hash : hash, <signature : signature>) : unit AENS.transfer(owner : address, new_owner : address, name : string, <signature : signature>) : unit
``` ```
Transfers name to the new owner. Transfers name to the new owner.
The signature should be over `owner address` + `name_hash` + `Contract.address` The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
### revoke ### revoke
``` ```
AENS.revoke(owner : address, name_hash : hash, <signature : signature>) : unit AENS.revoke(owner : address, name : string, <signature : signature>) : unit
``` ```
Revokes the name to extend the ownership time. Revokes the name to extend the ownership time.
The signature should be over `owner address` + `name_hash` + `Contract.address` The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing. using the private key of the `owner` account for signing.
@@ -568,12 +569,15 @@ Call.caller : address
The address of the entity (possibly another contract) calling the contract. The address of the entity (possibly another contract) calling the contract.
### value ### value
``` ```
Call.value : int Call.value : int
``` ```
The amount of coins transferred to the contract in the call. The amount of coins transferred to the contract in the call. Note that in the `init`
entrypoint this value will be always `0` in order to get the contract creation value
one needs to inspect `Contract.balance`.
### gas ### gas
@@ -699,6 +703,13 @@ List.last(l : list('a)) : option('a)
Returns `Some` of the last element of a list or `None` if the list is empty. Returns `Some` of the last element of a list or `None` if the list is empty.
### contains
```
List.contains(e : 'a, l : list('a)) : bool
```
Checks if list `l` contains element `e`. Equivalent to `List.find(x => x == e, l) != None`.
### find ### find
``` ```
List.find(p : 'a => bool, l : list('a)) : option('a) List.find(p : 'a => bool, l : list('a)) : option('a)
@@ -967,12 +978,20 @@ List.unzip(l : list('a * 'b)) : list('a) * list('b)
Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices. Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices.
### merge
```
List.merge(lesser_cmp : ('a, 'a) => bool, l1 : list('a), l2 : list('a)) : list('a)
```
Merges two sorted lists into a single sorted list. O(length(l1) + length(l2))
### sort ### sort
``` ```
List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a)
``` ```
Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2). Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. O(length(l) * log_2(length(l))).
### intersperse ### intersperse
@@ -1039,6 +1058,13 @@ Option.force(o : option('a)) : 'a
Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`.
### contains
```
Option.contains(e : 'a, o : option('a)) : bool
```
Returns `true` if and only if `o` contains element equal to `e`. Equivalent to `Option.match(false, x => x == e, o)`.
### on_elem ### on_elem
``` ```
Option.on_elem(o : option('a), f : 'a => unit) : unit Option.on_elem(o : option('a), f : 'a => unit) : unit
@@ -1149,7 +1175,7 @@ will yield `None`.
### choose ### choose
``` ```
Option.choose(o1 : option('a), o2 : option('a)) : option('a) Option.choose(o1 : option('a), o2 : option('a)) : option('a)
``` ```
Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s. Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s.
@@ -1261,7 +1287,7 @@ Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c)
Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd))
``` ```
Turns a function that takes n arguments into a curried function that takes Turns a function that takes n arguments into a curried function that takes
one argument and returns a function that waits for the rest in the same one argument and returns a function that waits for the rest in the same
manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`. manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`.
@@ -1631,7 +1657,7 @@ This namespace provides operations on rational numbers. A rational number is rep
as a fraction of two integers which are stored internally in the `frac` datatype. as a fraction of two integers which are stored internally in the `frac` datatype.
The datatype consists of three constructors `Neg/2`, `Zero/0` and `Pos/2` which determine the The datatype consists of three constructors `Neg/2`, `Zero/0` and `Pos/2` which determine the
sign of the number. Both values stored in `Neg` and `Pos` need to be strictly positive sign of the number. Both values stored in `Neg` and `Pos` need to be strictly positive
integers. However, when creating a `frac` you should never use the constructors explicitly. integers. However, when creating a `frac` you should never use the constructors explicitly.
Instead of that, always use provided functions like `make_frac` or `from_int`. This helps Instead of that, always use provided functions like `make_frac` or `from_int`. This helps
keeping the internal representation well defined. keeping the internal representation well defined.
@@ -1767,14 +1793,14 @@ Rounds a fraction to the nearest greater or equal integer.
### round_to_zero ### round_to_zero
`Frac.round_to_zero(f : frac) : int` `Frac.round_to_zero(f : frac) : int`
Rounds a fraction towards zero. Rounds a fraction towards zero.
Effectively `ceil` if lesser than zero and `floor` if greater. Effectively `ceil` if lesser than zero and `floor` if greater.
### round_from_zero ### round_from_zero
`Frac.round_from_zero(f : frac) : int` `Frac.round_from_zero(f : frac) : int`
Rounds a fraction from zero. Rounds a fraction from zero.
Effectively `ceil` if greater than zero and `floor` if lesser. Effectively `ceil` if greater than zero and `floor` if lesser.
@@ -1806,7 +1832,7 @@ Subtraction of two fractions.
### inv ### inv
`Frac.inv(a : frac) : frac` `Frac.inv(a : frac) : frac`
Inverts a fraction. Throws error if `a` is zero. Inverts a fraction. Throws error if `a` is zero.
### mul ### mul
+67 -7
View File
@@ -15,10 +15,14 @@ namespace List =
_::t => Some(t) _::t => Some(t)
function last(l : list('a)) : option('a) = switch(l) function last(l : list('a)) : option('a) = switch(l)
[] => None [] => None
[x] => Some(x) [x] => Some(x)
_::t => last(t) _::t => last(t)
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None` /** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists. * if no such element exists.
*/ */
@@ -223,11 +227,67 @@ namespace List =
(left, right)::t => unzip_(t, left::acc_l, right::acc_r) (left, right)::t => unzip_(t, left::acc_l, right::acc_r)
// TODO: Improve? /** Merges two sorted lists using `lt` comparator
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l) */
[] => [] function
h::t => switch (partition((x) => lesser_cmp(x, h), t)) merge : (('a, 'a) => bool, list('a), list('a)) => list('a)
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger) merge(lt, x::xs, y::ys) =
if(lt(x, y)) x::merge(lt, xs, y::ys)
else y::merge(lt, x::xs, ys)
merge(_, [], ys) = ys
merge(_, xs, []) = xs
/** Mergesort inspired by
* https://hackage.haskell.org/package/base-4.14.1.0/docs/src/Data.OldList.html#sort
*/
function
sort : (('a, 'a) => bool, list('a)) => list('a)
sort(_, []) = []
sort(lt, l) =
merge_all(lt, monotonic_subs(lt, l))
/** Splits list into compound increasing sublists
*/
private function
monotonic_subs : (('a, 'a) => bool, list('a)) => list(list('a))
monotonic_subs(lt, x::y::rest) =
if(lt(y, x)) desc(lt, y, [x], rest)
else asc(lt, y, [x], rest)
monotonic_subs(_, l) = [l]
/** Extracts the longest descending prefix and proceeds with monotonic split
*/
private function
desc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
desc(lt, x, acc, h::t) =
if(lt(x, h)) (x::acc) :: monotonic_subs(lt, h::t)
else desc(lt, h, x::acc, t)
desc(_, x, acc, []) = [x::acc]
/** Extracts the longest ascending prefix and proceeds with monotonic split
*/
private function
asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
asc(lt, x, acc, h::t) =
if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t)
else asc(lt, h, x::acc, t)
asc(_, x, acc, []) = [List.reverse(x::acc)]
/** Merges list of sorted lists
*/
private function
merge_all : (('a, 'a) => bool, list(list('a))) => list('a)
merge_all(_, [part]) = part
merge_all(lt, parts) = merge_all(lt, merge_pairs(lt, parts))
/** Single round of `merge_all` pairs of lists in a list of list
*/
private function
merge_pairs : (('a, 'a) => bool, list(list('a))) => list(list('a))
merge_pairs(lt, x::y::rest) = merge(lt, x, y) :: merge_pairs(lt, rest)
merge_pairs(_, l) = l
/** Puts `delim` between every two members of the list /** Puts `delim` between every two members of the list
*/ */
+5 -1
View File
@@ -22,7 +22,11 @@ namespace Option =
/** Assume it is `Some` /** Assume it is `Some`
*/ */
function force(o : option('a)) : 'a = default(abort("Forced None value"), o) function force(o : option('a)) : 'a = switch(o)
None => abort("Forced None value")
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o) function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
+13 -7
View File
@@ -14,6 +14,8 @@
, contract_interface/2 , contract_interface/2
, contract_interface/3 , contract_interface/3
, from_typed_ast/2
, render_aci_json/1 , render_aci_json/1
, json_encode_expr/1 , json_encode_expr/1
@@ -23,6 +25,8 @@
-type json() :: jsx:json_term(). -type json() :: jsx:json_term().
-type json_text() :: binary(). -type json_text() :: binary().
-export_type([aci_type/0]).
%% External API %% External API
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}. -spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
file(Type, File) -> file(Type, File) ->
@@ -65,18 +69,20 @@ do_contract_interface(Type, ContractString, Options) ->
try try
Ast = aeso_compiler:parse(ContractString, Options), Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]), %% io:format("~p\n", [Ast]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]), {TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
%% io:format("~p\n", [TypedAst]), %% io:format("~p\n", [TypedAst]),
JArray = [ encode_contract(C) || C <- TypedAst ], from_typed_ast(Type, TypedAst)
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end
catch catch
throw:{error, Errors} -> {error, Errors} throw:{error, Errors} -> {error, Errors}
end. end.
from_typed_ast(Type, TypedAst) ->
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},
+89 -28
View File
@@ -553,17 +553,21 @@ global_env() ->
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. -spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
infer(Contracts) -> infer(Contracts) ->
infer(Contracts, []). infer(Contracts, []).
-type option() :: return_env | dont_unfold | no_code | term(). -type option() :: return_env | dont_unfold | no_code | debug_mode | term().
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(_Options) -> global_env(). init_env(_Options) -> global_env().
-spec infer(aeso_syntax:ast(), list(option())) -> -spec infer(aeso_syntax:ast(), list(option())) ->
aeso_syntax:ast() | {env(), aeso_syntax:ast()}. {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
infer([], Options) ->
create_type_errors(),
type_error({no_decls, proplists:get_value(src_file, Options, no_file)}),
destroy_and_report_type_errors(init_env(Options));
infer(Contracts, Options) -> infer(Contracts, Options) ->
ets_init(), %% Init the ETS table state ets_init(), %% Init the ETS table state
try try
@@ -572,15 +576,15 @@ infer(Contracts, Options) ->
ets_new(type_vars, [set]), ets_new(type_vars, [set]),
check_modifiers(Env, Contracts), check_modifiers(Env, Contracts),
{Env1, Decls} = infer1(Env, Contracts, [], Options), {Env1, Decls} = infer1(Env, Contracts, [], Options),
{Env2, Decls2} = {Env2, DeclsFolded, DeclsUnfolded} =
case proplists:get_value(dont_unfold, Options, false) of case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls}; true -> {Env1, Decls, Decls};
false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end), false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{E, unfold_record_types(E, Decls)} {E, Decls, unfold_record_types(E, Decls)}
end, end,
case proplists:get_value(return_env, Options, false) of case proplists:get_value(return_env, Options, false) of
false -> Decls2; false -> {DeclsFolded, DeclsUnfolded};
true -> {Env2, Decls2} true -> {Env2, DeclsFolded, DeclsUnfolded}
end end
after after
clean_up_ets() clean_up_ets()
@@ -618,15 +622,23 @@ check_scope_name_clash(Env, Kind, Name) ->
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> -spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}. {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, _Options) -> infer_contract_top(Env, Kind, Defs0, Options) ->
create_type_errors(),
Defs = desugar(Defs0), Defs = desugar(Defs0),
infer_contract(Env, Kind, Defs). destroy_and_report_type_errors(Env),
infer_contract(Env, Kind, Defs, Options).
%% infer_contract takes a proplist mapping global names to types, and %% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions. %% a list of definitions.
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}. -spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0) -> infer_contract(Env0, What, Defs0, Options) ->
Defs = process_blocks(Defs0), create_type_errors(),
Defs01 = process_blocks(Defs0),
Defs = case lists:member(debug_mode, Options) of
true -> expose_internals(Defs01, What);
false -> Defs01
end,
destroy_and_report_type_errors(Env0),
Env = Env0#env{ what = What }, Env = Env0#env{ what = What },
Kind = fun({type_def, _, _, _, _}) -> type; Kind = fun({type_def, _, _, _, _}) -> type;
({letfun, _, _, _, _, _}) -> function; ({letfun, _, _, _, _, _}) -> function;
@@ -673,20 +685,39 @@ process_blocks(Decls) ->
-spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()]. -spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()].
process_block(_, []) -> []; process_block(_, []) -> [];
process_block(_, [Decl]) -> [Decl]; process_block(_, [Decl]) -> [Decl];
process_block(Ann, [Decl | Decls]) -> process_block(_Ann, [Decl | Decls]) ->
IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1; IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1;
(_) -> false end end, (_) -> false end end,
case Decl of case Decl of
{fun_decl, Ann1, Id = {id, _, Name}, Type} -> {fun_decl, Ann1, Id = {id, _, Name}, Type} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), Decls), {Clauses, Rest} = lists:splitwith(IsThis(Name), Decls),
[{fun_clauses, Ann1, Id, Type, Clauses} | [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
process_block(Ann, Rest)]; [{fun_clauses, Ann1, Id, Type, Clauses}];
{letfun, Ann1, Id = {id, _, Name}, _, _, _} -> {letfun, Ann1, Id = {id, _, Name}, _, _, _} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]), {Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]),
[{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses} | [type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
process_block(Ann, Rest)] [{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]
end. end.
%% Turns private stuff into public stuff
expose_internals(Defs, What) ->
[ begin
Ann = element(2, Def),
NewAnn = case What of
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
main_contract -> [{entrypoint, true}|Ann]; % minor duplication
contract -> Ann
end,
Def1 = setelement(2, Def, NewAnn),
case Def1 of % fix inner clauses
{fun_clauses, Ans, Id, T, Clauses} ->
{fun_clauses, Ans, Id, T, expose_internals(Clauses, What)};
_ -> Def1
end
end
|| Def <- Defs
].
-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 = #env{ namespace = Ns }, Defs) ->
create_type_errors(), create_type_errors(),
@@ -1526,6 +1557,15 @@ free_vars(L) when is_list(L) ->
[V || Elem <- L, [V || Elem <- L,
V <- free_vars(Elem)]. V <- free_vars(Elem)].
next_count() ->
V = case get(counter) of
undefined ->
0;
X -> X
end,
put(counter, V + 1),
V.
%% Clean up all the ets tables (in case of an exception) %% Clean up all the ets tables (in case of an exception)
ets_tables() -> ets_tables() ->
@@ -1571,6 +1611,18 @@ ets_tab2list(Name) ->
TabId = ets_tabid(Name), TabId = ets_tabid(Name),
ets:tab2list(TabId). ets:tab2list(TabId).
ets_insert_ordered(_, []) -> true;
ets_insert_ordered(Name, [H|T]) ->
ets_insert_ordered(Name, H),
ets_insert_ordered(Name, T);
ets_insert_ordered(Name, Object) ->
Count = next_count(),
TabId = ets_tabid(Name),
ets:insert(TabId, {Count, Object}).
ets_tab2list_ordered(Name) ->
[E || {_, E} <- ets_tab2list(Name)].
%% Options %% Options
create_options(Options) -> create_options(Options) ->
@@ -1606,17 +1658,17 @@ destroy_and_report_unsolved_constraints(Env) ->
%% -- Named argument constraints -- %% -- Named argument constraints --
create_named_argument_constraints() -> create_named_argument_constraints() ->
ets_new(named_argument_constraints, [bag]). ets_new(named_argument_constraints, [ordered_set]).
destroy_named_argument_constraints() -> destroy_named_argument_constraints() ->
ets_delete(named_argument_constraints). ets_delete(named_argument_constraints).
get_named_argument_constraints() -> get_named_argument_constraints() ->
ets_tab2list(named_argument_constraints). ets_tab2list_ordered(named_argument_constraints).
-spec add_named_argument_constraint(named_argument_constraint()) -> ok. -spec add_named_argument_constraint(named_argument_constraint()) -> ok.
add_named_argument_constraint(Constraint) -> add_named_argument_constraint(Constraint) ->
ets_insert(named_argument_constraints, Constraint), ets_insert_ordered(named_argument_constraints, Constraint),
ok. ok.
solve_named_argument_constraints(Env) -> solve_named_argument_constraints(Env) ->
@@ -1655,14 +1707,14 @@ destroy_and_report_unsolved_named_argument_constraints(Env) ->
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}. | {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
create_bytes_constraints() -> create_bytes_constraints() ->
ets_new(bytes_constraints, [bag]). ets_new(bytes_constraints, [ordered_set]).
get_bytes_constraints() -> get_bytes_constraints() ->
ets_tab2list(bytes_constraints). ets_tab2list_ordered(bytes_constraints).
-spec add_bytes_constraint(byte_constraint()) -> true. -spec add_bytes_constraint(byte_constraint()) -> true.
add_bytes_constraint(Constraint) -> add_bytes_constraint(Constraint) ->
ets_insert(bytes_constraints, Constraint). ets_insert_ordered(bytes_constraints, Constraint).
solve_bytes_constraints(Env) -> solve_bytes_constraints(Env) ->
[ solve_bytes_constraint(Env, C) || C <- get_bytes_constraints() ], [ solve_bytes_constraint(Env, C) || C <- get_bytes_constraints() ],
@@ -1716,18 +1768,18 @@ check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
create_field_constraints() -> create_field_constraints() ->
%% A relation from uvars to constraints %% A relation from uvars to constraints
ets_new(field_constraints, [bag]). ets_new(field_constraints, [ordered_set]).
destroy_field_constraints() -> destroy_field_constraints() ->
ets_delete(field_constraints). ets_delete(field_constraints).
-spec constrain([field_constraint()]) -> true. -spec constrain([field_constraint()]) -> true.
constrain(FieldConstraints) -> constrain(FieldConstraints) ->
ets_insert(field_constraints, FieldConstraints). ets_insert_ordered(field_constraints, FieldConstraints).
-spec get_field_constraints() -> [field_constraint()]. -spec get_field_constraints() -> [field_constraint()].
get_field_constraints() -> get_field_constraints() ->
ets_tab2list(field_constraints). ets_tab2list_ordered(field_constraints).
solve_field_constraints(Env) -> solve_field_constraints(Env) ->
FieldCs = FieldCs =
@@ -2266,6 +2318,12 @@ mk_t_err(Pos, Msg) ->
mk_t_err(Pos, Msg, Ctxt) -> mk_t_err(Pos, Msg, Ctxt) ->
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)). aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
mk_error({no_decls, File}) ->
Pos = aeso_errors:pos(File, 0, 0),
mk_t_err(Pos, "Empty contract\n");
mk_error({mismatched_decl_in_funblock, Name, Decl}) ->
Msg = io_lib:format("Mismatch in the function block. Expected implementation/type declaration of ~s function\n", [Name]),
mk_t_err(pos(Decl), Msg);
mk_error({higher_kinded_typevar, T}) -> mk_error({higher_kinded_typevar, T}) ->
Msg = io_lib:format("Type ~s is a higher kinded type variable\n" Msg = io_lib:format("Type ~s is a higher kinded type variable\n"
"(takes another type as an argument)\n", [pp(instantiate(T))] "(takes another type as an argument)\n", [pp(instantiate(T))]
@@ -2551,6 +2609,9 @@ mk_error({mixed_record_and_map, Expr}) ->
Msg = io_lib:format("Mixed record fields and map keys in\n~s", Msg = io_lib:format("Mixed record fields and map keys in\n~s",
[pp_expr(" ", Expr)]), [pp_expr(" ", Expr)]),
mk_t_err(pos(Expr), Msg); mk_t_err(pos(Expr), Msg);
mk_error({conflicting_updates_for_field, Upd, Key}) ->
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
mk_t_err(pos(Upd), Msg);
mk_error(Err) -> mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p\n", [Err]), Msg = io_lib:format("Unknown error: ~p\n", [Err]),
mk_t_err(pos(0, 0), Msg). mk_t_err(pos(0, 0), Msg).
@@ -2773,7 +2834,7 @@ desugar_updates([Upd | Updates]) ->
{More, Updates1} = updates_key(Key, Updates), {More, Updates1} = updates_key(Key, Updates),
%% Check conflicts %% Check conflicts
case length([ [] || [] <- [Rest | More] ]) of case length([ [] || [] <- [Rest | More] ]) of
N when N > 1 -> error({conflicting_updates_for_field, Upd, Key}); N when N > 1 -> type_error({conflicting_updates_for_field, Upd, Key});
_ -> ok _ -> ok
end, end,
[MakeField(lists:append([Rest | More])) | desugar_updates(Updates1)]. [MakeField(lists:append([Rest | More])) | desugar_updates(Updates1)].
+59 -40
View File
@@ -2,7 +2,7 @@
%%% @author Happi (Erik Stenman) %%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt %%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc %%% @doc
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm. %%% Compiler from Aeterinty Sophia language to both AEVM and FATE VM.
%%% @end %%% @end
%%% Created : 12 Dec 2017 %%% Created : 12 Dec 2017
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
@@ -38,11 +38,13 @@
| pp_assembler | pp_assembler
| pp_bytecode | pp_bytecode
| no_code | no_code
| keep_included | keep_included
| debug_mode
| {backend, aevm | fate} | {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()}
| {aci, aeso_aci:aci_type()}.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
@@ -116,7 +118,8 @@ from_string(Backend, ContractString, Options) ->
end. end.
from_string1(aevm, ContractString, Options) -> from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_code(ContractString, Options), #{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode), TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options), Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options), pp_assembler(aevm, Assembler, Options),
@@ -124,47 +127,63 @@ from_string1(aevm, ContractString, Options) ->
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options), pp_bytecode(ByteCode, Options),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, Res = #{byte_code => ByteCode,
compiler_version => Version, compiler_version => Version,
contract_source => ContractString, contract_source => ContractString,
type_info => TypeInfo, type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(), abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode) payable => maps:get(payable, Icode)
}}; },
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options), #{ fcode := FCode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options), FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options), pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, Res = #{byte_code => ByteCode,
compiler_version => Version, compiler_version => Version,
contract_source => ContractString, contract_source => ContractString,
type_info => [], type_info => [],
fate_code => FateCode, fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(), abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode) payable => maps:get(payable, FCode)
}}. },
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
undefined ->
Result;
Type ->
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
maps:put(aci, Aci, Result)
end.
-spec string_to_code(string(), options()) -> map(). -spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) -> 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 | Options]), {TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(UnfoldedTypedAst, Options),
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
Icode = ast_to_icode(TypedAst, Options), Icode = ast_to_icode(UnfoldedTypedAst, Options),
pp_icode(Icode, Options), pp_icode(Icode, Options),
#{ icode => Icode, #{ icode => Icode
typed_ast => TypedAst, , unfolded_typed_ast => UnfoldedTypedAst
type_env => TypeEnv}; , folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
fate -> fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options), Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
#{ fcode => Fcode, #{ fcode => Fcode
typed_ast => TypedAst, , unfolded_typed_ast => UnfoldedTypedAst
type_env => TypeEnv} , folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
end. end.
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
@@ -199,9 +218,9 @@ check_call1(ContractString0, FunName, Args, Options) ->
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
%% First check the contract without the __call function %% First check the contract without the __call function
#{} = string_to_code(ContractString0, Options), #{ast := Ast} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options), ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
#{typed_ast := TypedAst, #{unfolded_typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options), icode := Icode} = string_to_code(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
@@ -221,13 +240,14 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms}; {ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate -> fate ->
%% First check the contract without the __call function %% First check the contract without the __call function
#{fcode := OrgFcode} = string_to_code(ContractString0, Options), #{ fcode := OrgFcode
, ast := Ast } = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []), FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to %% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes, CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)), lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options), ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options), #{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode), CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
@@ -253,9 +273,8 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
end. 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(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
insert_call_function(Code, Call, FunName, Args, Options) -> insert_call_function(Ast, Code, Call, FunName, Args) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast), Ind = last_contract_indent(Ast),
lists:flatten( lists:flatten(
[ Code, [ Code,
@@ -311,7 +330,7 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{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]),
@@ -387,7 +406,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst), {ok, Args, _} = get_decode_type(FunName, TypedAst),
GetType = fun({typed, _, _, T}) -> T; (T) -> T end, GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
+15 -7
View File
@@ -74,25 +74,31 @@
%% first argument. I.e. no backtracking to the second argument if the first %% first argument. I.e. no backtracking to the second argument if the first
%% fails. %% fails.
trampoline({bounce, Cont}) when is_function(Cont, 0) ->
trampoline(Cont());
trampoline(Res) ->
Res.
-define(BOUNCE(X), {bounce, fun() -> X end}).
%% Apply a parser to its continuation. This compiles a parser to its low-level representation. %% Apply a parser to its continuation. This compiles a parser to its low-level representation.
-spec apply_p(parser(A), fun((A) -> parser1(B))) -> parser1(B). -spec apply_p(parser(A), fun((A) -> parser1(B))) -> parser1(B).
apply_p(?lazy(F), K) -> apply_p(F(), K); apply_p(?lazy(F), K) -> apply_p(F(), K);
apply_p(?fail(Err), _) -> {fail, Err}; apply_p(?fail(Err), _) -> {fail, Err};
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(apply_p(Q, K), R) end, apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(trampoline(apply_p(Q, K)), R) end,
apply_p(P, K), Ps); trampoline(apply_p(P, K)), Ps);
apply_p(?bind(P, F), K) -> apply_p(P, fun(X) -> apply_p(F(X), K) end); apply_p(?bind(P, F), K) -> apply_p(P, fun(X) -> apply_p(F(X), K) end);
apply_p(?right(P, Q), K) -> apply_p(P, fun(_) -> apply_p(Q, K) end); apply_p(?right(P, Q), K) -> apply_p(P, fun(_) -> apply_p(Q, K) end);
apply_p(?left(P, Q), K) -> apply_p(P, fun(X) -> apply_p(Q, fun(_) -> K(X) end) end); apply_p(?left(P, Q), K) -> apply_p(P, fun(X) -> apply_p(Q, fun(_) -> K(X) end) end);
apply_p(?map(F, P), K) -> apply_p(P, fun(X) -> K(F(X)) end); apply_p(?map(F, P), K) -> apply_p(P, fun(X) -> K(F(X)) end);
apply_p(?layout, K) -> {layout, K, {fail, {expected, layout_block}}}; apply_p(?layout, K) -> {layout, K, {fail, {expected, layout_block}}};
apply_p(?tok(Atom), K) -> {tok_bind, #{Atom => K}}; apply_p(?tok(Atom), K) -> {tok_bind, #{Atom => K}};
apply_p(?return(X), K) -> K(X); apply_p(?return(X), K) -> ?BOUNCE(K(X));
apply_p([P | Q], K) -> apply_p(P, fun(H) -> apply_p(Q, fun(T) -> K([H | T]) end) end); apply_p([P | Q], K) -> apply_p(P, fun(H) -> apply_p(Q, fun(T) -> K([H | T]) end) end);
apply_p(T, K) when is_tuple(T) -> apply_p(tuple_to_list(T), fun(Xs) -> K(list_to_tuple(Xs)) end); apply_p(T, K) when is_tuple(T) -> apply_p(tuple_to_list(T), fun(Xs) -> K(list_to_tuple(Xs)) end);
apply_p(M, K) when is_map(M) -> apply_p(M, K) when is_map(M) ->
{Keys, Ps} = lists:unzip(maps:to_list(M)), {Keys, Ps} = lists:unzip(maps:to_list(M)),
apply_p(Ps, fun(Vals) -> K(maps:from_list(lists:zip(Keys, Vals))) end); apply_p(Ps, fun(Vals) -> K(maps:from_list(lists:zip(Keys, Vals))) end);
apply_p(X, K) -> K(X). apply_p(X, K) -> ?BOUNCE(K(X)).
%% -- Primitive combinators -------------------------------------------------- %% -- Primitive combinators --------------------------------------------------
@@ -160,7 +166,7 @@ layout() -> ?layout.
%% @doc Parse a sequence of tokens using a parser. Fails if the parse is ambiguous. %% @doc Parse a sequence of tokens using a parser. Fails if the parse is ambiguous.
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}. -spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
parse(P, S) -> parse(P, S) ->
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of case parse1(trampoline(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end)), S) of
{[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}}; {[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
{[A], _} -> {ok, A}; {[A], _} -> {ok, A};
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}} {As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
@@ -241,7 +247,7 @@ col(T) when is_tuple(T) -> element(2, pos(T)).
%% If both parsers want the next token we grab it and merge the continuations. %% If both parsers want the next token we grab it and merge the continuations.
choice1({tok_bind, Map1}, {tok_bind, Map2}) -> choice1({tok_bind, Map1}, {tok_bind, Map2}) ->
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(F(T), G(T)) end end, Map1, Map2)}; {tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(trampoline(F(T)), trampoline(G(T))) end end, Map1, Map2)};
%% If both parsers fail we combine the error messages. If only one fails we discard it. %% If both parsers fail we combine the error messages. If only one fails we discard it.
choice1({fail, E1}, {fail, E2}) -> {fail, add_error(E1, E2)}; choice1({fail, E1}, {fail, E2}) -> {fail, add_error(E1, E2)};
@@ -255,7 +261,7 @@ choice1(P, {return_plus, X, Q}) -> {return_plus, X, choice1(P, Q)};
%% If both sides want a layout block we combine them. If only one side wants a layout block we %% If both sides want a layout block we combine them. If only one side wants a layout block we
%% will commit to a layout block is there is one. %% will commit to a layout block is there is one.
choice1({layout, F, P}, {layout, G, Q}) -> choice1({layout, F, P}, {layout, G, Q}) ->
{layout, fun(N) -> choice1(F(N), G(N)) end, choice1(P, Q)}; {layout, fun(N) -> choice1(trampoline(F(N)), trampoline(G(N))) end, choice1(P, Q)};
choice1({layout, F, P}, Q) -> {layout, F, choice1(P, Q)}; choice1({layout, F, P}, Q) -> {layout, F, choice1(P, Q)};
choice1(P, {layout, G, Q}) -> {layout, G, choice1(P, Q)}. choice1(P, {layout, G, Q}) -> {layout, G, choice1(P, Q)}.
@@ -278,6 +284,8 @@ parse1(P, S) ->
%% The main work horse. Returns a list of possible parses and an error message in case parsing %% The main work horse. Returns a list of possible parses and an error message in case parsing
%% fails. %% fails.
-spec parse1(parser1(A), #ts{}, [A], term()) -> {[A], error()}. -spec parse1(parser1(A), #ts{}, [A], term()) -> {[A], error()}.
parse1({bounce, F}, Ts, Acc, Err) ->
parse1(F(), Ts, Acc, Err);
parse1({tok_bind, Map}, Ts, Acc, Err) -> parse1({tok_bind, Map}, Ts, Acc, Err) ->
case next_token(Ts) of case next_token(Ts) of
{T, Ts1} -> {T, Ts1} ->
+6 -4
View File
@@ -39,9 +39,9 @@
| {pragma, ann(), pragma()} | {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs | {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
| {fun_clauses, ann(), id(), type(), [letbind()]}
| {block, ann(), [decl()]} | {block, ann(), [decl()]}
| fundecl()
| letfun() | letfun()
| letval(). % Only for error msgs | letval(). % Only for error msgs
@@ -50,8 +50,10 @@
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letval() :: {letval, ann(), pat(), expr()}. -type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. -type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind() -type letbind()
:: letfun() :: letfun()
| letval(). | letval().
+2
View File
@@ -95,6 +95,8 @@ from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) -> when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X) {tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]}; || {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, [{field_t, _, FName, FType}]}, Val) ->
{record, [], [{field, [], [{proj, [], FName}], from_fate(FType, Val)}]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val)) from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) -> when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)} {record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
+13 -6
View File
@@ -11,7 +11,10 @@ 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:contract_interface(json, Contract),
?assertEqual([MapACI], JSON), ?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)),
%% Check if the compiler provides correct aci
{ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]),
?assertEqual(JSON, JSON2).
test_cases(1) -> test_cases(1) ->
Contract = <<"payable contract C =\n" Contract = <<"payable contract C =\n"
@@ -80,11 +83,11 @@ test_cases(3) ->
DecACI = <<"contract C =\n" DecACI = <<"contract C =\n"
" type state = unit\n" " type state = unit\n"
" datatype event = SingleEventDefined\n" " datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n" " datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>, " entrypoint a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}. {Contract,MapACI,DecACI}.
%% Rounttrip %% Roundtrip
aci_test_() -> aci_test_() ->
[{"Testing ACI generation for " ++ ContractName, [{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end} fun() -> aci_test_contract(ContractName) end}
@@ -94,8 +97,13 @@ all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) -> aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}], Opts = case lists:member(Name, aeso_compiler_tests:debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts), {ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]), io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON), {ok, ContractStub} = aeso_aci:render_aci_json(JSON),
@@ -122,4 +130,3 @@ check_stub(Stub, Options) ->
_ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ], _ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ],
error({parse_errors, Errs}) error({parse_errors, Errs})
end. end.
+1
View File
@@ -113,6 +113,7 @@ compilable_contracts() ->
{"funargs", "traffic_light", ["Pantone(12)"]}, {"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]}, {"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]}, %% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"variant_types", "init", []}, {"variant_types", "init", []},
{"basic_auth", "init", []}, {"basic_auth", "init", []},
{"address_literals", "init", []}, {"address_literals", "init", []},
+41 -7
View File
@@ -39,7 +39,7 @@ simple_compile_test_() ->
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++ not lists:member(ContractName, not_compilable_on(Backend))] ++
[ {"Test file not found error", [ {"Test file not found error",
fun() -> fun() ->
{error, Errors} = aeso_compiler:file("does_not_exist.aes"), {error, Errors} = aeso_compiler:file("does_not_exist.aes"),
@@ -110,7 +110,15 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) -> compile(Backend, 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 ++ ".aes"}, {backend, Backend} | Options]) of Options1 =
case lists:member(Name, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[ {src_file, Name ++ ".aes"}, {backend, Backend}
, {include, {file_system, [aeso_test_utils:contract_path()]}}
] ++ Options,
case aeso_compiler:from_string(String, Options1) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} when is_binary(ErrorString) -> ErrorString; {error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors {error, Errors} -> Errors
@@ -165,11 +173,19 @@ compilable_contracts() ->
"underscore_number_literals", "underscore_number_literals",
"qualified_constructor", "qualified_constructor",
"let_patterns", "let_patterns",
"lhs_matching" "lhs_matching",
"hermetization_turnoff"
]. ].
not_yet_compilable(fate) -> []; not_compilable_on(fate) -> [];
not_yet_compilable(aevm) -> []. not_compilable_on(aevm) ->
["stdlib_include",
"manual_stdlib_include",
"hermetization_turnoff"
].
debug_mode_contracts() ->
["hermetization_turnoff"].
%% Contracts that should produce type errors %% Contracts that should produce type errors
@@ -665,6 +681,16 @@ failing_contracts() ->
"Empty record/map update\n" "Empty record/map update\n"
" r {}">> " r {}">>
]) ])
, ?TYPE_ERROR(bad_function_block,
[<<?Pos(4, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>,
<<?Pos(5, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>
])
, ?TYPE_ERROR(just_an_empty_file,
[<<?Pos(0, 0)
"Empty contract">>
])
, ?TYPE_ERROR(bad_number_of_args, , ?TYPE_ERROR(bad_number_of_args,
[<<?Pos(3, 39) [<<?Pos(3, 39)
"Cannot unify () => unit\n" "Cannot unify () => unit\n"
@@ -687,6 +713,9 @@ failing_contracts() ->
" g : (int, string) => 'c\nto arguments\n" " g : (int, string) => 'c\nto arguments\n"
" \"Litwo, ojczyzno moja\" : string">> " \"Litwo, ojczyzno moja\" : string">>
]) ])
, ?TYPE_ERROR(bad_state,
[<<?Pos(4, 16)
"Conflicting updates for field 'foo'">>])
]. ].
-define(Path(File), "code_errors/" ??File). -define(Path(File), "code_errors/" ??File).
@@ -840,6 +869,11 @@ validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1), ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2), Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source, aeso_compiler:validate_byte_code(
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]). ByteCode#{ byte_code := FCode1 }, Source,
case lists:member(Contract2, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
+5
View File
@@ -0,0 +1,5 @@
contract C =
function
g(1) = 2
f(2) = 3
h(1) = 123
+5
View File
@@ -0,0 +1,5 @@
contract C =
record state = { foo : int }
entrypoint init(i : int) =
state{ foo = i,
foo = 42 }
+5
View File
@@ -45,3 +45,8 @@ contract FunctionArguments =
entrypoint due(t : Chain.ttl) = entrypoint due(t : Chain.ttl) =
true true
record singleton_r = { x : int }
entrypoint singleton_rec(r : singleton_r) =
r.x
+11
View File
@@ -0,0 +1,11 @@
namespace M =
function mf() = mg()
function mg() = mf()
namespace N =
function nf() = ng() + M.mf() + M.mg()
private function ng() = nf() + M.mf() + M.mg()
contract C =
entrypoint f() = N.ng() + N.nf() + g()
function g() = N.ng() + N.nf() + f()