Compare commits

..

65 Commits

Author SHA1 Message Date
Radosław Rowicki d14a0025f2 Merge branch 'master' into radrow-patch-2 2019-09-02 12:09:53 +02:00
Radosław Rowicki fe0c22851f Update CHANGELOG.md 2019-09-02 12:08:01 +02:00
Thomas Arts a50730155f Merge pull request #138 from aeternity/aens-at-full-node-ver-ta
Compile name fee in contracts
2019-09-02 11:07:59 +02:00
Thomas Arts e9f717a17b Update src/aeso_ast_to_icode.erl
Co-Authored-By: Ulf Norell <ulf.norell@gmail.com>
2019-09-02 10:21:35 +02:00
Ulf Norell 97ff1aac23 Merge pull request #136 from radrow/stdlib-extensions
Updated some functions, renamed some stuff, added from_to IN STDLIB
2019-09-02 09:56:56 +02:00
sennui 1ee5a57924 change aebytecode version, aeserialization and add enacl 2019-09-02 08:54:38 +02:00
Thomas Arts cf91a27fb2 Keep sign last 2019-09-01 10:58:49 +02:00
sennui 83d06977f9 add extra argument to claim for bidding 2019-09-01 10:58:49 +02:00
Ulf Norell 41e59506ba Merge pull request #137 from aeternity/polymorpism-checks
Polymorphism checks
2019-08-30 15:48:34 +02:00
Ulf Norell 062309e578 Type variables mentioned in local functions should not be flexible
(cc #112)
2019-08-30 14:22:31 +02:00
Radosław Rowicki 6408969cd3 Remove from_to_ 2019-08-30 14:06:46 +02:00
Radosław Rowicki 71a556ce81 nth update 2019-08-30 13:46:02 +02:00
Radosław Rowicki 256aadd575 [......]
Co-Authored-By: Ulf Norell <ulf.norell@gmail.com>
2019-08-30 13:44:26 +02:00
Ulf Norell f27ba528d8 aebytecode commit 2019-08-30 11:21:26 +02:00
Ulf Norell 6fd39d4cb1 Add checks for polymorphic/higher order oracles and higher order entrypoints (AEVM) 2019-08-30 11:18:20 +02:00
Ulf Norell 1ce95b32ac Add checks for polymorphic/higher order oracles and higher order entrypoints (FATE) 2019-08-30 11:18:20 +02:00
radrow 076d635dbe Fix errors 2019-08-29 15:32:10 +02:00
Radosław Rowicki 6d87960147 Merge pull request #135 from aeternity/radrow-patch-3
Remove find_all from stdlib
2019-08-29 15:25:22 +02:00
radrow 1d962f2001 Updated some functions, renamed, added from_to 2019-08-29 13:41:04 +02:00
Radosław Rowicki cce243e513 Remove find_all from stdlib
It was just a duplicated `filter`
2019-08-28 14:17:30 +02:00
Ulf Norell 60528e9128 Merge pull request #134 from aeternity/unit-to-typerep
Add missing case for builtin unit type
2019-08-28 10:19:10 +02:00
Ulf Norell 80075a9d36 Add missing case for builtin unit type 2019-08-28 09:45:25 +02:00
Ulf Norell d26fcace41 Merge pull request #133 from aeternity/stdlib-overhaul
Stdlib overhaul
2019-08-28 08:38:40 +02:00
Ulf Norell c51531f620 please dialyzer 2019-08-27 18:04:32 +02:00
Ulf Norell 3b2daf8cd6 Better errors when using old tuple type syntax 2019-08-27 15:08:56 +02:00
Ulf Norell 3ff93c5c89 Fix bug in include chasing
... making it possible for the same file to be included multiple times
2019-08-27 14:29:24 +02:00
Ulf Norell 850221aaf3 Remove no_implicit_stdlib option 2019-08-27 14:10:40 +02:00
Ulf Norell 3f1c23ace3 Use .. in list comprehension test 2019-08-27 14:00:23 +02:00
Ulf Norell 0efbcf302c Fix roundtrip test to ignore ListInternal 2019-08-27 14:00:02 +02:00
Ulf Norell 7705138ab2 auto-import ListInternal when using list comprehensions or [a..b] 2019-08-27 13:59:36 +02:00
Ulf Norell 5f733e01dd Implement [a..b] 2019-08-27 13:59:01 +02:00
Ulf Norell 79a928e530 Fix bad type specs 2019-08-27 13:56:02 +02:00
Ulf Norell d23208c191 Fix bugs in dependency analysis 2019-08-27 13:55:45 +02:00
Ulf Norell e7d3a5b9f2 Put flat_map in ListInternal.aes 2019-08-27 11:33:43 +02:00
Ulf Norell 02af75aa34 Move stdlib code to priv dir and don't do any implicit includes 2019-08-27 11:33:29 +02:00
Ulf Norell 9eed18f812 Merge pull request #132 from aeternity/fate-compiler-optimizations
Fate compiler optimizations
2019-08-26 08:25:40 +02:00
Ulf Norell 07cf162703 Fix performance problem in FATE optimiser caused by debug printing 2019-08-23 10:07:43 +02:00
Hans Svensson d4c6187739 Merge pull request #130 from aeternity/PT-168026424-prepare_sophia_4_0_RC1
Preparing 4.0.0-rc1
2019-08-22 16:02:06 +02:00
Hans Svensson 2620aa64b4 Add some no_implicit_stdlib for now 2019-08-22 15:21:41 +02:00
Ulf Norell 20064b72fa Compile tail-calls to current function to jumps 2019-08-22 14:50:15 +02:00
Ulf Norell a942561907 Improved optimizations of FATE code 2019-08-22 14:49:48 +02:00
Ulf Norell a9617a025f Merge pull request #129 from aeternity/deadcode-elim
Deadcode elimination for FATE
2019-08-22 14:25:31 +02:00
Radosław Rowicki 1fbfe418c7 Update CHANGELOG.md 2019-08-22 14:23:15 +02:00
Hans Svensson bde76c8580 Preparing 4.0.0-rc1 2019-08-22 13:30:00 +02:00
Ulf Norell e94b5379ed Deadcode elimination pass 2019-08-21 12:19:01 +02:00
Ulf Norell cbc8909954 Add default init function in fcode pass instead of in assembler 2019-08-21 11:51:36 +02:00
Ulf Norell cfd036b199 Test deadcode elimination for FATE backend 2019-08-21 11:51:36 +02:00
Ulf Norell bbf043f4ee Merge pull request #126 from radrow/listcompfixes
Fixed comprehension dependencies
2019-08-21 11:51:16 +02:00
Ulf Norell ba41ab457d Merge pull request #127 from aeternity/radrow-patch-1
Fixed intersperse in stdlib
2019-08-21 11:51:04 +02:00
Hans Svensson 49634a6024 Merge pull request #128 from aeternity/PT-167996886-a_proper_ecverify
PT-167996886 A proper ecverify
2019-08-21 11:19:48 +02:00
Hans Svensson 2dbef80249 aebytecode commit 2019-08-21 11:09:44 +02:00
Hans Svensson ebdd38c505 Change ecverify into verify_sig and then add an actual ecverify 2019-08-21 09:29:40 +02:00
Radosław Rowicki 5dbca47d34 Fixed intersperse in stdlib 2019-08-20 20:16:52 +02:00
radrow 79d491e4a8 Fixed comprehension dependencies 2019-08-20 18:44:47 +02:00
Hans Svensson 73b9a54172 Merge pull request #125 from aeternity/PT-162578406-payable_modifier
PT-162578406 Add payable modifier
2019-08-19 16:27:18 +02:00
Hans Svensson bb0c3b54df ACI should also track payable 2019-08-19 08:57:31 +02:00
Hans Svensson d0485304b6 Please dialyzer 2019-08-19 08:57:31 +02:00
Hans Svensson 86aeaa40ef Set aebytecode commit 2019-08-19 08:57:31 +02:00
Hans Svensson e9505e240f Add Address.is_payable(address) 2019-08-19 08:57:31 +02:00
Hans Svensson f27d37d624 Add payable modifier for contracts and entrypoints 2019-08-19 08:57:31 +02:00
Ulf Norell e566186800 Merge pull request #123 from aeternity/PT-167221635-remote-type-check
PT-167221635 remote type check
2019-08-16 09:22:30 +02:00
Ulf Norell 956b78fb01 aebytecode commit 2019-08-16 09:14:51 +02:00
Ulf Norell 522d977be9 Remote calls now take typerep arguments 2019-08-16 09:12:04 +02:00
Tino Breddin dd26649f7d [PT-167805291] Add opcode for ecrecover (#122)
* Add opcode for ecrecover

* Update aebytecode

* Extend signature bytes type used for ecrecover

* Add ecrecover to changelog

* Add some type specs

* Please dialyzer
2019-08-14 21:02:46 +02:00
Radosław Rowicki b669d2df1e Added list comprehensions and standard List, Option, Func, Pair, and Triple library (#105)
* Added standard List library and list comprehensions

Added List library.

Flatmaps WIP

Fixed dependency in flat_map

fcode generation

Updated tests to use custom list lib

Added comprehension test

Added stdlib

sanity

Test

* Extended stdlib for lists. Added error message for redefinition of stdlibx

* Fixed type template

* Improved stdlib

* More functions

* Fixed cyclic includes

* Refixed imports and added few tests

* Added fail test

* Undelete removed type spec

* Remove typo

* Fix iter function

* Fixed typo

* Added if guards and let statements in list comp

* Added more fail tests

* Option stliv

* 2 and 3 tuple stdlib

* Updated stdlib to new syntax. Added recursor and changed all/any functions

* Fixed performance issues. Changed include management

* Fixed hash type
2019-08-14 13:53:58 +02:00
52 changed files with 1310 additions and 276 deletions
+33 -3
View File
@@ -6,12 +6,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Standard library support. `ListInternal` can be included implicitly if list comprehensions are used.
- Added the `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
### Changed
### 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 ### Changed
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and - 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`. 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, Parens are not necessary. Note it only affects the types, values remain as their were before,
so `(1, "a") : int * string` 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 ### Removed
## [3.2.0] - 2019-06-28 ## [3.2.0] - 2019-06-28
@@ -108,7 +137,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.2.0...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...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.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.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 [3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
+46
View File
@@ -0,0 +1,46 @@
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))
+214
View File
@@ -0,0 +1,214 @@
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(if(n < 0) "Negative index get" else "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)
function from_to(a : int, b : int) : list(int) = [a..b]
function from_to_step(a : int, b : int, s : int) : list(int) = from_to_step_(a, b, s, [])
private function from_to_step_(a, b, s, acc) =
if (a > b) reverse(acc) else from_to_step_(a + s, b, s, a :: acc)
/* 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(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
insert_by_(cmp, x, l, [])
private function insert_by_(cmp : (('a, 'a) => bool), x : 'a, l : list('a), acc : list('a)) : list('a) =
switch(l)
[] => reverse(x::acc)
h::t =>
if(cmp(x, h)) // x < h
reverse(acc) ++ (x::l)
else
insert_by_(cmp, x, t, h::acc)
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(l : list('a), f : 'a => unit) : unit =
switch(l)
[] => ()
e::l' =>
f(e)
foreach(l', f)
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 flatten(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
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,76 @@
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(o : option('a), f : 'a => unit) : 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
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,37 @@
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)
+2 -2
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"adf3664"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"72b2a58"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
@@ -15,7 +15,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "3.2.0"}, {relx, [{release, {aesophia, "4.0.0-rc1"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+6 -2
View File
@@ -1,17 +1,21 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"adf3664dd03626c115756f79fa0e602fda24318d"}}, {ref,"72b2a581d5a6d488a208331da88de1a488ac2da1"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}}, {ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
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">>},0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
2},
{<<"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",
+14 -7
View File
@@ -113,7 +113,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|| F <- sort_decls(contract_funcs(Contract)), || F <- sort_decls(contract_funcs(Contract)),
is_entrypoint(F) ], is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs}}; #{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}};
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) -> encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ], Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name), #{namespace => #{name => encode_name(Name),
@@ -125,12 +125,14 @@ encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name), #{name => encode_name(Name),
arguments => encode_args(Args), arguments => encode_args(Args),
returns => encode_type(Type), returns => encode_type(Type),
stateful => is_stateful(FDef)}; stateful => is_stateful(FDef),
payable => is_payable(FDef)};
encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) -> encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) ->
#{name => encode_name(Name), #{name => encode_name(Name),
arguments => encode_anon_args(Args), arguments => encode_anon_args(Args),
returns => encode_type(Type), returns => encode_type(Type),
stateful => is_stateful(FDecl)}. stateful => is_stateful(FDecl),
payable => is_payable(FDecl)}.
encode_anon_args(Types) -> encode_anon_args(Types) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))], Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))],
@@ -234,12 +236,13 @@ do_render_aci_json(Json) ->
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}. {ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name, decode_contract(#{contract := #{name := Name,
payable := Payable,
type_defs := Ts0, type_defs := Ts0,
functions := Fs} = C}) -> functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end, MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++ Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0, [ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
["contract ", io_lib:format("~s", [Name])," =\n", [payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)]; decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] -> decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n", ["namespace ", io_lib:format("~s", [Name])," =\n",
@@ -249,8 +252,8 @@ decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ]. decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
%% decode_func(#{name := init}) -> []; %% decode_func(#{name := init}) -> [];
decode_func(#{name := Name, arguments := As, returns := T}) -> decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) ->
[" entrypoint", " ", io_lib:format("~s", [Name]), " : ", [" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ",
decode_args(As), " => ", decode_type(T), $\n]. decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) -> decode_args(As) ->
@@ -321,13 +324,16 @@ decode_deftype(#{record := _Efs}) -> "record";
decode_deftype(#{variant := _}) -> "datatype"; decode_deftype(#{variant := _}) -> "datatype";
decode_deftype(_T) -> "type". decode_deftype(_T) -> "type".
decode_tvars([]) -> []; %No tvars, no parentheses decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) -> decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ], Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)]. [$(,lists:join(", ", Dvs),$)].
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]). 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_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
@@ -352,6 +358,7 @@ sort_decls(Ds) ->
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false). is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false).
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false). 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. typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name.
+80 -8
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(), type()}. -type var_info() :: {aeso_syntax:ann(), utype()}.
-type fun_env() :: [{name(), fun_info()}]. -type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}]. -type type_env() :: [{name(), type_info()}].
@@ -139,11 +139,11 @@ on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
on_scopes(Env = #env{ scopes = Scopes }, Fun) -> on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }. Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), type(), env()) -> env(). -spec bind_var(aeso_syntax:id(), 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(), type()}], env()) -> env(). -spec bind_vars([{aeso_syntax:id(), utype()}], 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)).
@@ -363,6 +363,8 @@ 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,
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end, StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end, TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"}, SignId = {id, Ann, "signature"},
@@ -458,8 +460,10 @@ global_env() ->
%% Crypto/Curve operations %% Crypto/Curve operations
CryptoScope = #scope CryptoScope = #scope
{ funs = MkDefs( { funs = MkDefs(
[{"ecverify", Fun([Hash, Address, SignId], Bool)}, [{"verify_sig", Fun([Hash, Address, SignId], Bool)},
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)}, {"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], 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)}]) },
@@ -501,7 +505,8 @@ global_env() ->
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_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)}]) }, {"is_contract", Fun1(Address, Bool)},
{"is_payable", Fun1(Address, Bool)}]) },
#env{ scopes = #env{ scopes =
#{ [] => TopScope #{ [] => TopScope
@@ -762,7 +767,9 @@ 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 ],
@@ -1074,6 +1081,58 @@ 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),
@@ -1268,7 +1327,7 @@ infer_block(Env, _, [E], BlockType) ->
[check_expr(Env, E, BlockType)]; [check_expr(Env, E, BlockType)];
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) -> infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def), {{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
FunT = freshen_type(Ann, typesig_to_fun_t(TypeSig)), FunT = typesig_to_fun_t(TypeSig),
NewE = bind_var({id, Ann, Name}, FunT, Env), NewE = bind_var({id, Ann, Name}, FunT, Env),
[LetFun|infer_block(NewE, 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) ->
@@ -1295,6 +1354,9 @@ 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]},
@@ -2162,6 +2224,9 @@ pp_error({contract_has_no_entrypoints, Con}) ->
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]); "'function'.\n", [pp_expr("", Con), pp_loc(Con)]);
pp_error({unbound_type, Type}) -> pp_error({unbound_type, Type}) ->
io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(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]).
@@ -2253,6 +2318,13 @@ 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().
+195 -43
View File
@@ -16,7 +16,7 @@
-type option() :: term(). -type option() :: term().
-type attribute() :: stateful | pure | private. -type attribute() :: stateful | payable | pure | private.
-type fun_name() :: {entrypoint, binary()} -type fun_name() :: {entrypoint, binary()}
| {local_fun, [string()]} | {local_fun, [string()]}
@@ -32,8 +32,9 @@
map_delete | map_member | map_size | string_length | map_delete | map_member | map_size | string_length |
string_concat | bits_set | bits_clear | bits_test | bits_sum | string_concat | bits_set | bits_clear | bits_test | bits_sum |
bits_intersection | bits_union | bits_difference | bits_intersection | bits_union | bits_difference |
contract_to_address | crypto_ecverify | crypto_ecverify_secp256k1 | contract_to_address | crypto_verify_sig | crypto_verify_sig_secp256k1 |
crypto_sha3 | crypto_sha256 | crypto_blake2b. crypto_sha3 | crypto_sha256 | crypto_blake2b |
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
-type flit() :: {int, integer()} -type flit() :: {int, integer()}
| {string, binary()} | {string, binary()}
@@ -49,7 +50,7 @@
| nil | nil
| {var, var_name()} | {var, var_name()}
| {def, fun_name(), [fexpr()]} | {def, fun_name(), [fexpr()]}
| {remote, fexpr(), fun_name(), [fexpr()]} | {remote, [ftype()], ftype(), fexpr(), fun_name(), [fexpr()]}
| {builtin, builtin(), [fexpr()]} | {builtin, builtin(), [fexpr()]}
| {con, arities(), tag(), [fexpr()]} | {con, arities(), tag(), [fexpr()]}
| {tuple, [fexpr()]} | {tuple, [fexpr()]}
@@ -64,7 +65,7 @@
%% lambdas) are generated by the fcode compiler, but translated %% lambdas) are generated by the fcode compiler, but translated
%% to closures by the lambda lifter. %% to closures by the lambda lifter.
| {def_u, fun_name(), arity()} | {def_u, fun_name(), arity()}
| {remote_u, fexpr(), fun_name(), arity()} | {remote_u, [ftype()], ftype(), fexpr(), fun_name()}
| {builtin_u, builtin(), arity()} | {builtin_u, builtin(), arity()}
| {lam, [var_name()], fexpr()}. | {lam, [var_name()], fexpr()}.
@@ -108,7 +109,8 @@
-type fcode() :: #{ contract_name := string(), -type fcode() :: #{ contract_name := string(),
state_type := ftype(), state_type := ftype(),
event_type := ftype() | none, event_type := ftype() | none,
functions := #{ fun_name() => fun_def() } }. functions := #{ fun_name() => fun_def() },
payable := boolean() }.
-type type_def() :: fun(([ftype()]) -> ftype()). -type type_def() :: fun(([ftype()]) -> ftype()).
@@ -187,15 +189,16 @@ builtins() ->
{"revoke", 3}]}, {"revoke", 3}]},
{["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2}, {["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2},
{"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]},
{["Crypto"], [{"ecverify", 3}, {"ecverify_secp256k1", 3}, {"sha3", 1}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3},
{"sha256", 1}, {"blake2b", 1}]}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2},
{"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["Auth"], [{"tx_hash", none}]}, {["Auth"], [{"tx_hash", none}]},
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
{["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}]},
{["Int"], [{"to_str", 1}]}, {["Int"], [{"to_str", 1}]},
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}]} {["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
], ],
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes, || {NS, Funs} <- Scopes,
@@ -223,10 +226,13 @@ init_type_env() ->
["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}) ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]})
}. }.
is_no_code(Env) ->
proplists:get_value(no_code, maps:get(options, Env, []), false).
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) -> to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) ->
#{ builtins := Builtins } = Env, #{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main}, MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none}, builtins => Builtins#{[Main, "state"] => {get_state, none},
@@ -236,10 +242,12 @@ to_fcode(Env, [{contract, _, {con, _, Main}, Decls}]) ->
decls_to_fcode(MainEnv, Decls), decls_to_fcode(MainEnv, Decls),
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}), StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
EventType = lookup_type(Env1, [Main, "event"], [], none), EventType = lookup_type(Env1, [Main, "event"], [], none),
Payable = proplists:get_value(payable, Attrs, false),
#{ contract_name => Main, #{ contract_name => Main,
state_type => StateType, state_type => StateType,
event_type => EventType, event_type => EventType,
functions => add_init_function(Env1, payable => Payable,
functions => add_init_function(Env1, StateType,
add_event_function(Env1, EventType, Funs)) }; add_event_function(Env1, EventType, Funs)) };
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
@@ -263,7 +271,7 @@ decls_to_fcode(Env, Decls) ->
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) -> decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) ->
case proplists:get_value(no_code, maps:get(options, Env, []), false) of case is_no_code(Env) of
false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)}); false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)});
true -> Env true -> Env
end; end;
@@ -274,10 +282,13 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R
Attrs = get_attributes(Ann), Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)), FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args), FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body), FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs, Def = #{ attrs => Attrs,
args => FArgs, args => FArgs,
return => type_to_fcode(Env, Ret), return => FRet,
body => FBody }, body => FBody },
NewFuns = Funs#{ FName => Def }, NewFuns = Funs#{ FName => Def },
Env#{ functions := NewFuns }. Env#{ functions := NewFuns }.
@@ -404,9 +415,10 @@ expr_to_fcode(Env, Type, {proj, _Ann, Rec = {typed, _, _, RecType}, {id, _, X}})
{con, _, _} when X == "address" -> {con, _, _} when X == "address" ->
{op, contract_to_address, [expr_to_fcode(Env, Rec)]}; {op, contract_to_address, [expr_to_fcode(Env, Rec)]};
{con, _, _} -> {con, _, _} ->
{fun_t, _, Named, Args, _} = Type, {fun_t, _, _, Args, Ret} = Type,
Arity = length(Named) + length(Args), FArgs = [type_to_fcode(Env, Arg) || Arg <- Args],
{remote_u, expr_to_fcode(Env, Rec), {entrypoint, list_to_binary(X)}, Arity}; {remote_u, FArgs, type_to_fcode(Env, Ret), expr_to_fcode(Env, Rec),
{entrypoint, list_to_binary(X)}};
{record_t, _} -> {record_t, _} ->
{proj, expr_to_fcode(Env, Rec), field_index(Rec, X)} {proj, expr_to_fcode(Env, Rec), field_index(Rec, X)}
end; end;
@@ -444,6 +456,27 @@ expr_to_fcode(Env, _Type, {list, _, Es}) ->
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end, lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
nil, Es); nil, Es);
expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) ->
{def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]),
{def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) ->
{op, '::', [expr_to_fcode(Env, Yield), nil]};
expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, {typed, {id, _, Arg}, _}, BindExpr}|Rest]}) ->
Env1 = bind_var(Env, Arg),
Bind = {lam, [Arg], expr_to_fcode(Env1, {list_comp, As, Yield, Rest})},
{def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]),
{def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]};
expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) ->
make_if(expr_to_fcode(Env, Cond),
expr_to_fcode(Env, Type, {list_comp, As, Yield, Rest}),
nil
);
expr_to_fcode(Env, Type, {list_comp, As, Yield, [LV = {letval, _, _, _, _}|Rest]}) ->
expr_to_fcode(Env, Type, {block, As, [LV, {list_comp, As, Yield, Rest}]});
expr_to_fcode(Env, Type, {list_comp, As, Yield, [LF = {letfun, _, _, _, _, _}|Rest]}) ->
expr_to_fcode(Env, Type, {block, As, [LF, {list_comp, As, Yield, Rest}]});
%% Conditionals %% Conditionals
expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) -> expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) ->
make_if(expr_to_fcode(Env, Cond), make_if(expr_to_fcode(Env, Cond),
@@ -480,7 +513,7 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end; end;
%% Function calls %% Function calls
expr_to_fcode(Env, Type, {app, _Ann, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args), Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of case expr_to_fcode(Env, Fun) of
@@ -494,16 +527,18 @@ expr_to_fcode(Env, Type, {app, _Ann, Fun = {typed, _, _, {fun_t, _, NamedArgsT,
%% Get the type of the oracle from the args or the expression itself %% Get the type of the oracle from the args or the expression itself
OType = get_oracle_type(B, Type, Args1), OType = get_oracle_type(B, Type, Args1),
{oracle, QType, RType} = type_to_fcode(Env, OType), {oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(aeso_syntax:get_ann(Fun), QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}], TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs); builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _} when B =:= aens_resolve -> {builtin_u, B, _} when B =:= aens_resolve ->
%% Get the type we are assuming the name resolves to %% Get the type we are assuming the name resolves to
AensType = type_to_fcode(Env, Type), AensType = type_to_fcode(Env, Type),
validate_aens_resolve_type(aeso_syntax:get_ann(Fun), AensType),
TypeArgs = [{lit, {typerep, AensType}}], TypeArgs = [{lit, {typerep, AensType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs); builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs); {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs}; {def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, Ct, RFun, _Ar} -> {remote, Ct, RFun, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
FFun -> FFun ->
%% FFun is a closure, with first component the function name and %% FFun is a closure, with first component the function name and
%% second component the environment %% second component the environment
@@ -574,6 +609,46 @@ get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) ->
get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType;
get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType.
validate_oracle_type(Ann, QType, RType) ->
ensure_monomorphic(QType, {polymorphic_query_type, Ann, QType}),
ensure_monomorphic(RType, {polymorphic_response_type, Ann, RType}),
ensure_first_order(QType, {higher_order_query_type, Ann, QType}),
ensure_first_order(RType, {higher_order_response_type, Ann, RType}),
ok.
validate_aens_resolve_type(Ann, {variant, [[], [Type]]}) ->
ensure_monomorphic(Type, {polymorphic_aens_resolve, Ann, Type}),
ensure_first_order(Type, {higher_order_aens_resolve, Ann, Type}),
ok.
ensure_first_order_entrypoint(Ann, Args, Ret) ->
[ ensure_first_order(T, {higher_order_entrypoint_argument, Ann, X, T})
|| {X, T} <- Args ],
ensure_first_order(Ret, {higher_order_entrypoint_return, Ann, Ret}),
ok.
ensure_monomorphic(Type, Err) ->
case is_monomorphic(Type) of
true -> ok;
false -> fcode_error(Err)
end.
ensure_first_order(Type, Err) ->
case is_first_order(Type) of
true -> ok;
false -> fcode_error(Err)
end.
is_monomorphic({tvar, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
is_first_order({function, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
%% -- Pattern matching -- %% -- Pattern matching --
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit(). -spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit().
@@ -800,8 +875,10 @@ op_builtins() ->
[map_from_list, map_to_list, map_delete, map_member, map_size, [map_from_list, map_to_list, map_delete, map_member, map_size,
string_length, string_concat, string_sha3, string_sha256, string_blake2b, string_length, string_concat, string_sha3, string_sha256, string_blake2b,
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
bits_difference, int_to_str, address_to_str, crypto_ecverify, bits_difference, int_to_str, address_to_str, crypto_verify_sig,
crypto_ecverify_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b]. crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
].
builtin_to_fcode(require, [Cond, Msg]) -> builtin_to_fcode(require, [Cond, Msg]) ->
make_if(Cond, {tuple, []}, {builtin, abort, [Msg]}); make_if(Cond, {tuple, []}, {builtin, abort, [Msg]});
@@ -827,16 +904,30 @@ builtin_to_fcode(Builtin, Args) ->
%% -- Init function -- %% -- Init function --
add_init_function(_Env, Funs) -> add_init_function(Env, StateType, Funs0) ->
case is_no_code(Env) of
true -> Funs0;
false ->
Funs = add_default_init_function(Env, StateType, Funs0),
InitName = {entrypoint, <<"init">>},
InitFun = #{ args := InitArgs } = maps:get(InitName, Funs, none),
Vars = [ {var, X} || {X, _} <- InitArgs ],
Funs#{ init => InitFun#{ return => {tuple, []},
body => {builtin, set_state, [{def, InitName, Vars}]} } }
end.
add_default_init_function(_Env, StateType, Funs) ->
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
InitFun = #{ args := InitArgs } = case maps:get(InitName, Funs, none) of
case maps:get(InitName, Funs, none) of %% Only add default init function if state is unit.
none -> #{ attrs => [], args => [], return => {tuple, []}, body => {tuple, []} }; none when StateType == {tuple, []} ->
Info -> Info Funs#{ InitName => #{attrs => [],
end, args => [],
Vars = [ {var, X} || {X, _} <- InitArgs ], return => {tuple, []},
Funs#{ init => InitFun#{ return => {tuple, []}, body => {tuple, []}} };
body => {builtin, set_state, [{def, InitName, Vars}]} } }. none -> fcode_error(missing_init_function);
_ -> Funs
end.
%% -- Event function -- %% -- Event function --
@@ -924,12 +1015,13 @@ lambda_lift_expr({Tag, F, Ar}) when Tag == def_u; Tag == builtin_u ->
def_u -> {def, F, Args} def_u -> {def, F, Args}
end, end,
make_closure([], Xs, Body); make_closure([], Xs, Body);
lambda_lift_expr({remote_u, Ct, F, Ar}) -> lambda_lift_expr({remote_u, ArgsT, RetT, Ct, F}) ->
FVs = free_vars(Ct), FVs = free_vars(Ct),
Ct1 = lambda_lift_expr(Ct), Ct1 = lambda_lift_expr(Ct),
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], GasAndValueArgs = 2,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ],
Args = [{var, X} || X <- Xs], Args = [{var, X} || X <- Xs],
make_closure(FVs, Xs, {remote, Ct1, F, Args}); make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args});
lambda_lift_expr(Expr) -> lambda_lift_expr(Expr) ->
case Expr of case Expr of
{lit, _} -> Expr; {lit, _} -> Expr;
@@ -938,7 +1030,7 @@ lambda_lift_expr(Expr) ->
{closure, _, _} -> Expr; {closure, _, _} -> Expr;
{def, D, As} -> {def, D, lambda_lift_exprs(As)}; {def, D, As} -> {def, D, lambda_lift_exprs(As)};
{builtin, B, As} -> {builtin, B, lambda_lift_exprs(As)}; {builtin, B, As} -> {builtin, B, lambda_lift_exprs(As)};
{remote, Ct, F, As} -> {remote, lambda_lift_expr(Ct), F, lambda_lift_exprs(As)}; {remote, ArgsT, RetT, Ct, F, As} -> {remote, ArgsT, RetT, lambda_lift_expr(Ct), F, lambda_lift_exprs(As)};
{con, Ar, C, As} -> {con, Ar, C, lambda_lift_exprs(As)}; {con, Ar, C, As} -> {con, Ar, C, lambda_lift_exprs(As)};
{tuple, As} -> {tuple, lambda_lift_exprs(As)}; {tuple, As} -> {tuple, lambda_lift_exprs(As)};
{proj, A, I} -> {proj, lambda_lift_expr(A), I}; {proj, A, I} -> {proj, lambda_lift_expr(A), I};
@@ -964,13 +1056,16 @@ lambda_lift_exprs(As) -> [lambda_lift_expr(A) || A <- As].
-spec optimize_fcode(fcode()) -> fcode(). -spec optimize_fcode(fcode()) -> fcode().
optimize_fcode(Code = #{ functions := Funs }) -> optimize_fcode(Code = #{ functions := Funs }) ->
Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) }. Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) },
eliminate_dead_code(Code1).
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def(). -spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body }) -> optimize_fun(Fcode, Fun, Def = #{ body := Body }) ->
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]), %% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]),
Def#{ body := inliner(Fcode, Fun, Body) }. Def#{ body := inliner(Fcode, Fun, Body) }.
%% --- Inlining ---
-spec inliner(fcode(), fun_name(), fexpr()) -> fexpr(). -spec inliner(fcode(), fun_name(), fexpr()) -> fexpr().
inliner(Fcode, Fun, {def, Fun1, Args} = E) when Fun1 /= Fun -> inliner(Fcode, Fun, {def, Fun1, Args} = E) when Fun1 /= Fun ->
case should_inline(Fcode, Fun1) of case should_inline(Fcode, Fun1) of
@@ -983,6 +1078,33 @@ should_inline(_Fcode, _Fun1) -> false == list_to_atom("true"). %% Dialyzer
inline(_Fcode, Fun, Args) -> {def, Fun, Args}. %% TODO inline(_Fcode, Fun, Args) -> {def, Fun, Args}. %% TODO
%% --- Deadcode elimination ---
-spec eliminate_dead_code(fcode()) -> fcode().
eliminate_dead_code(Code = #{ functions := Funs }) ->
UsedFuns = used_functions(Funs),
Code#{ functions := maps:filter(fun(Name, _) -> maps:is_key(Name, UsedFuns) end,
Funs) }.
-spec used_functions(#{ fun_name() => fun_def() }) -> #{ fun_name() => true }.
used_functions(Funs) ->
Exported = [ Fun || {Fun, #{ attrs := Attrs }} <- maps:to_list(Funs),
not lists:member(private, Attrs) ],
used_functions(#{}, Exported, Funs).
used_functions(Used, [], _) -> Used;
used_functions(Used, [Name | Rest], Defs) ->
case maps:is_key(Name, Used) of
true -> used_functions(Used, Rest, Defs);
false ->
New =
case maps:get(Name, Defs, undef) of
undef -> []; %% We might be compiling a stub
#{ body := Body } -> used_defs(Body)
end,
used_functions(Used#{ Name => true }, New ++ Rest, Defs)
end.
%% -- Helper functions ------------------------------------------------------- %% -- Helper functions -------------------------------------------------------
%% -- Types -- %% -- Types --
@@ -1133,8 +1255,8 @@ free_vars(Expr) ->
nil -> []; nil -> [];
{def, _, As} -> free_vars(As); {def, _, As} -> free_vars(As);
{def_u, _, _} -> []; {def_u, _, _} -> [];
{remote, Ct, _, As} -> free_vars([Ct | As]); {remote, _, _, Ct, _, As} -> free_vars([Ct | As]);
{remote_u, Ct, _, _} -> free_vars(Ct); {remote_u, _, _, Ct, _} -> free_vars(Ct);
{builtin, _, As} -> free_vars(As); {builtin, _, As} -> free_vars(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{con, _, _, As} -> free_vars(As); {con, _, _, As} -> free_vars(As);
@@ -1152,6 +1274,34 @@ free_vars(Expr) ->
{'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P)) {'case', P, A} -> free_vars(A) -- lists:sort(fsplit_pat_vars(P))
end. end.
used_defs(Xs) when is_list(Xs) ->
lists:umerge([ used_defs(X) || X <- Xs ]);
used_defs(Expr) ->
case Expr of
{var, _} -> [];
{lit, _} -> [];
nil -> [];
{def, F, As} -> lists:umerge([F], used_defs(As));
{def_u, F, _} -> [F];
{remote, _, _, Ct, _, As} -> used_defs([Ct | As]);
{remote_u, _, _, Ct, _} -> used_defs(Ct);
{builtin, _, As} -> used_defs(As);
{builtin_u, _, _} -> [];
{con, _, _, As} -> used_defs(As);
{tuple, As} -> used_defs(As);
{proj, A, _} -> used_defs(A);
{set_proj, A, _, B} -> used_defs([A, B]);
{op, _, As} -> used_defs(As);
{'let', _, A, B} -> used_defs([A, B]);
{funcall, A, Bs} -> used_defs([A | Bs]);
{lam, _, B} -> used_defs(B);
{closure, F, A} -> lists:umerge([F], used_defs(A));
{switch, A} -> used_defs(A);
{split, _, _, As} -> used_defs(As);
{nosplit, A} -> used_defs(A);
{'case', _, A} -> used_defs(A)
end.
get_named_args(NamedArgsT, Args) -> get_named_args(NamedArgsT, Args) ->
IsNamed = fun({named_arg, _, _, _}) -> true; IsNamed = fun({named_arg, _, _, _}) -> true;
(_) -> false end, (_) -> false end,
@@ -1177,8 +1327,8 @@ rename(Ren, Expr) ->
{def_u, _, _} -> Expr; {def_u, _, _} -> Expr;
{builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]};
{builtin_u, _, _} -> Expr; {builtin_u, _, _} -> Expr;
{remote, Ct, F, Es} -> {remote, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]};
{remote_u, Ct, F, Ar} -> {remote_u, rename(Ren, Ct), F, Ar}; {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F};
{con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]};
{tuple, Es} -> {tuple, [rename(Ren, E) || E <- Es]}; {tuple, Es} -> {tuple, [rename(Ren, E) || E <- Es]};
{proj, E, I} -> {proj, rename(Ren, E), I}; {proj, E, I} -> {proj, rename(Ren, E), I};
@@ -1282,7 +1432,9 @@ field_value({field_t, _, {id, _, X}, _}, Fields) ->
%% -- Attributes -- %% -- Attributes --
get_attributes(Ann) -> get_attributes(Ann) ->
[stateful || proplists:get_value(stateful, Ann, false)]. [stateful || proplists:get_value(stateful, Ann, false)] ++
[payable || proplists:get_value(payable, Ann, false)] ++
[private || not proplists:get_value(entrypoint, Ann, false)].
%% -- Basic utilities -- %% -- Basic utilities --
@@ -1392,10 +1544,10 @@ pp_fexpr({builtin_u, B, N}) ->
pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); pp_beside([pp_text(B), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin, B, As}) -> pp_fexpr({builtin, B, As}) ->
pp_call(pp_text(B), As); pp_call(pp_text(B), As);
pp_fexpr({remote_u, Ct, Fun, Ar}) -> pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) ->
pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text("/"), pp_int(Ar)]); pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})]);
pp_fexpr({remote, Ct, Fun, As}) -> pp_fexpr({remote, ArgsT, RetT, Ct, Fun, As}) ->
pp_call(pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun)]), As); pp_call(pp_parens(pp_beside([pp_fexpr(Ct), pp_text("."), pp_fun_name(Fun), pp_text(" : "), pp_ftype({function, ArgsT, RetT})])), As);
pp_fexpr({funcall, Fun, As}) -> pp_fexpr({funcall, Fun, As}) ->
pp_call(pp_fexpr(Fun), As); pp_call(pp_fexpr(Fun), As);
pp_fexpr({switch, Split}) -> pp_split(Split). pp_fexpr({switch, Split}) -> pp_split(Split).
+95 -37
View File
@@ -17,11 +17,15 @@
-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) ->
Name = case lists:last(TypedTree) of {Payable, Name} =
{contract, _, {con, _, Con}, _} -> Con; case lists:last(TypedTree) of
_ -> gen_error(last_declaration_must_be_contract) {contract, Attrs, {con, _, Con}, _} ->
{proplists:get_value(payable, Attrs, false), Con};
_ ->
gen_error(last_declaration_must_be_contract)
end, end,
NewIcode = aeso_icode:set_name(Name, aeso_icode:new(Options)), NewIcode = aeso_icode:set_payable(Payable,
aeso_icode:set_name(Name, aeso_icode:new(Options))),
Icode = code(TypedTree, NewIcode, Options), Icode = code(TypedTree, NewIcode, Options),
deadcode_elimination(Icode). deadcode_elimination(Icode).
@@ -87,7 +91,10 @@ 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) ],
[ check_entrypoint_type(Attrib, Name, Args, T)
|| aeso_syntax:get_ann(entrypoint, Attrib, false) ],
%% TODO: Handle types %% TODO: Handle types
FunName = ast_id(Name), FunName = ast_id(Name),
%% TODO: push funname to env %% TODO: push funname to env
@@ -100,7 +107,7 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
#{ state_type := StateType } = Icode, #{ state_type := StateType } = Icode,
{#tuple{ cpts = [type_value(StateType), ast_body(Body, Icode)] }, {#tuple{ cpts = [type_value(StateType), ast_body(Body, Icode)] },
{tuple, [typerep, ast_typerep(T, Icode)]}}; {tuple, [typerep, ast_typerep(T, Icode)]}};
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)} _ -> {ast_body(Body, Icode), ast_typerep1(T, Icode)}
end, end,
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),
@@ -116,7 +123,7 @@ ast_id({id, _, Id}) -> Id;
ast_id({qid, _, Id}) -> Id. ast_id({qid, _, Id}) -> Id.
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) -> ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode); ast_args(Rest, [{ast_id(Name), ast_typerep1(Type, Icode)}| Acc], Icode);
ast_args([], Acc, _Icode) -> lists:reverse(Acc). ast_args([], Acc, _Icode) -> lists:reverse(Acc).
ast_type(T, Icode) -> ast_type(T, Icode) ->
@@ -265,8 +272,8 @@ ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) ->
ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) -> ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args), {Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0}, prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(Sign, Icode), ast_body(NameFee, Icode)], [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)],
[word, string, word, sign_t(), word], {tuple, []}); [word, string, word, 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, Name]} = get_signature_arg(Args),
@@ -350,16 +357,26 @@ 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", "ecverify"], [Msg, PK, Sig], _, _), Icode) -> ast_body(?qid_app(["Crypto", "verify_sig"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY, #integer{value = 0}, prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #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", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) -> ast_body(?qid_app(["Crypto", "verify_sig_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0}, prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_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) ->
@@ -423,6 +440,9 @@ ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) -> ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0}, prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word); [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) -> ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes, {typed, _, _, {bytes_t, _, N}} = Bytes,
@@ -505,6 +525,10 @@ 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 ->
@@ -519,6 +543,24 @@ 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)
@@ -542,7 +584,7 @@ ast_body({block,As,[E|Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode), #switch{expr=ast_body(E, Icode),
cases=[{#var_ref{name="_"},ast_body({block,As,Rest}, Icode)}]}; cases=[{#var_ref{name="_"},ast_body({block,As,Rest}, Icode)}]};
ast_body({lam,_,Args,Body}, Icode) -> ast_body({lam,_,Args,Body}, Icode) ->
#lambda{args=[#arg{name = ast_id(P), type = ast_type(T, Icode)} || {arg,_,P,T} <- Args], #lambda{args=[#arg{name = ast_id(P), type = ast_typerep1(T, Icode)} || {arg,_,P,T} <- Args],
body=ast_body(Body, Icode)}; body=ast_body(Body, Icode)};
ast_body({typed,_,{record,Attrs,Fields},{record_t,DefFields}}, Icode) -> ast_body({typed,_,{record,Attrs,Fields},{record_t,DefFields}}, Icode) ->
%% Compile as a tuple with the fields in the order they appear in the definition. %% Compile as a tuple with the fields in the order they appear in the definition.
@@ -677,6 +719,22 @@ map_upd(Key, Default, ValFun, Map = {typed, Ann, _, MapType}, Icode) ->
Args = [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Default, Icode), ast_body(ValFun, Icode)], Args = [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Default, Icode), ast_body(ValFun, Icode)],
builtin_call(FunName, Args). builtin_call(FunName, Args).
check_entrypoint_type(Ann, Name, Args, Ret) ->
Check = fun(T, Err) ->
case is_simple_type(T) of
false -> gen_error(Err);
true -> ok
end end,
[ Check(T, {entrypoint_argument_must_have_simple_type, Ann1, Name, X, T})
|| {arg, Ann1, X, T} <- Args ],
Check(Ret, {entrypoint_must_have_simple_return_type, Ann, Name, Ret}).
is_simple_type({tvar, _, _}) -> false;
is_simple_type({fun_t, _, _, _, _}) -> false;
is_simple_type(Ts) when is_list(Ts) -> lists:all(fun is_simple_type/1, Ts);
is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T));
is_simple_type(_) -> true.
is_monomorphic({tvar, _, _}) -> false; is_monomorphic({tvar, _, _}) -> false;
is_monomorphic([H|T]) -> is_monomorphic([H|T]) ->
is_monomorphic(H) andalso is_monomorphic(T); is_monomorphic(H) andalso is_monomorphic(T);
@@ -717,42 +775,49 @@ make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
TVars = [ X || {tvar, _, X} <- Args ], TVars = [ X || {tvar, _, X} <- Args ],
fun(Types) -> fun(Types) ->
TypeEnv1 = maps:from_list(lists:zip(TVars, Types)), TypeEnv1 = maps:from_list(lists:zip(TVars, Types)),
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) }) ast_typerep1(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
end. end.
-spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type(). -spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type().
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])). ast_typerep(Type) ->
ast_typerep(Type, aeso_icode:new([])).
ast_typerep({id, _, Name}, Icode) -> ast_typerep(Type, Icode) ->
case is_simple_type(Type) of
false -> gen_error({not_a_simple_type, Type});
true -> ast_typerep1(Type, Icode)
end.
ast_typerep1({id, _, Name}, Icode) ->
lookup_type_id(Name, [], Icode); lookup_type_id(Name, [], Icode);
ast_typerep({qid, _, Name}, Icode) -> ast_typerep1({qid, _, Name}, Icode) ->
lookup_type_id(Name, [], Icode); lookup_type_id(Name, [], Icode);
ast_typerep({con, _, _}, _) -> ast_typerep1({con, _, _}, _) ->
word; %% Contract type word; %% Contract type
ast_typerep({bytes_t, _, Len}, _) -> ast_typerep1({bytes_t, _, Len}, _) ->
bytes_t(Len); bytes_t(Len);
ast_typerep({app_t, _, {I, _, Name}, Args}, Icode) when I =:= id; I =:= qid -> ast_typerep1({app_t, _, {I, _, Name}, Args}, Icode) when I =:= id; I =:= qid ->
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ], ArgReps = [ ast_typerep1(Arg, Icode) || Arg <- Args ],
lookup_type_id(Name, ArgReps, Icode); lookup_type_id(Name, ArgReps, Icode);
ast_typerep({tvar,_,A}, #{ type_vars := TypeVars }) -> ast_typerep1({tvar,_,A}, #{ type_vars := TypeVars }) ->
case maps:get(A, TypeVars, undefined) of case maps:get(A, TypeVars, undefined) of
undefined -> word; %% We serialize type variables just as addresses in the originating VM. undefined -> word; %% We serialize type variables just as addresses in the originating VM.
Type -> Type Type -> Type
end; end;
ast_typerep({tuple_t,_,Cpts}, Icode) -> ast_typerep1({tuple_t,_,Cpts}, Icode) ->
{tuple, [ast_typerep(C, Icode) || C<-Cpts]}; {tuple, [ast_typerep1(C, Icode) || C<-Cpts]};
ast_typerep({record_t,Fields}, Icode) -> ast_typerep1({record_t,Fields}, Icode) ->
{tuple, [ begin {tuple, [ begin
{field_t, _, _, T} = Field, {field_t, _, _, T} = Field,
ast_typerep(T, Icode) ast_typerep1(T, Icode)
end || Field <- Fields]}; end || Field <- Fields]};
ast_typerep({fun_t,_,_,_,_}, _Icode) -> ast_typerep1({fun_t,_,_,_,_}, _Icode) ->
function; function;
ast_typerep({alias_t, T}, Icode) -> ast_typerep(T, Icode); ast_typerep1({alias_t, T}, Icode) -> ast_typerep1(T, Icode);
ast_typerep({variant_t, Cons}, Icode) -> ast_typerep1({variant_t, Cons}, Icode) ->
{variant, [ begin {variant, [ begin
{constr_t, _, _, Args} = Con, {constr_t, _, _, Args} = Con,
[ ast_typerep(Arg, Icode) || Arg <- Args ] [ ast_typerep1(Arg, Icode) || Arg <- Args ]
end || Con <- Cons ]}. end || Con <- Cons ]}.
ttl_t(Icode) -> ttl_t(Icode) ->
@@ -801,13 +866,6 @@ type_value({map, K, V}) ->
#tuple{ cpts = [#integer{ value = ?TYPEREP_MAP_TAG }, #tuple{ cpts = [#integer{ value = ?TYPEREP_MAP_TAG },
type_value(K), type_value(V)] }. type_value(K), type_value(V)] }.
%% As abort is a built-in in the future it will be illegal to for
%% users to define abort. For the time being strip away all user
%% defined abort functions.
ast_fun_to_icode("abort", _Atts, _Args, _Body, _TypeRep, Icode) ->
%% Strip away all user defined abort functions.
Icode;
ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode) -> ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode) ->
NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs], NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs],
aeso_icode:set_functions(NewFuns, Icode). aeso_icode:set_functions(NewFuns, Icode).
+13 -6
View File
@@ -122,7 +122,8 @@ from_string1(aevm, ContractString, Options) ->
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)
}}; }};
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options), #{fcode := FCode} = string_to_code(ContractString, Options),
@@ -134,7 +135,8 @@ from_string1(fate, ContractString, Options) ->
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)
}}. }}.
-spec string_to_code(string(), options()) -> map(). -spec string_to_code(string(), options()) -> map().
@@ -539,8 +541,9 @@ 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_aevm_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)
@@ -564,9 +567,7 @@ 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),
@@ -575,9 +576,14 @@ 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, Options) of case aeso_parser:string(Text, Included, Options) of
%% Yay, it worked! %% Yay, it worked!
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
%% Scan errors. %% Scan errors.
@@ -596,6 +602,7 @@ parse(Text, 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]}).
+165 -84
View File
@@ -98,26 +98,32 @@
Op =:= 'SHA3' orelse Op =:= 'SHA3' orelse
Op =:= 'SHA256' orelse Op =:= 'SHA256' orelse
Op =:= 'BLAKE2B' orelse Op =:= 'BLAKE2B' orelse
Op =:= 'ECVERIFY' orelse Op =:= 'VERIFY_SIG' orelse
Op =:= 'ECVERIFY_SECP256K1' orelse Op =:= 'VERIFY_SIG_SECP256K1' orelse
Op =:= 'CONTRACT_TO_ADDRESS' orelse Op =:= 'ECVERIFY_SECP256K1' orelse
Op =:= 'AUTH_TX_HASH' orelse Op =:= 'ECRECOVER_SECP256K1' orelse
Op =:= 'BYTES_TO_INT' orelse Op =:= 'CONTRACT_TO_ADDRESS' orelse
Op =:= 'BYTES_TO_STR' orelse Op =:= 'AUTH_TX_HASH' orelse
Op =:= 'ORACLE_CHECK' orelse Op =:= 'BYTES_TO_INT' orelse
Op =:= 'ORACLE_CHECK_QUERY' orelse Op =:= 'BYTES_TO_STR' orelse
Op =:= 'IS_ORACLE' orelse Op =:= 'ORACLE_CHECK' orelse
Op =:= 'IS_CONTRACT' orelse Op =:= 'ORACLE_CHECK_QUERY' orelse
Op =:= 'CREATOR' orelse Op =:= 'IS_ORACLE' orelse
Op =:= 'IS_CONTRACT' orelse
Op =:= 'IS_PAYABLE' orelse
Op =:= 'CREATOR' orelse
false)). false)).
-record(env, { contract, vars = [], locals = [], tailpos = true }). -record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
%% -- Debugging -------------------------------------------------------------- %% -- Debugging --------------------------------------------------------------
debug(Tag, Options, Fmt, Args) -> is_debug(Tag, Options) ->
Tags = proplists:get_value(debug, Options, []), Tags = proplists:get_value(debug, Options, []),
case Tags == all orelse lists:member(Tag, Tags) of Tags == all orelse lists:member(Tag, Tags).
debug(Tag, Options, Fmt, Args) ->
case is_debug(Tag, Options) of
true -> io:format(Fmt, Args); true -> io:format(Fmt, Args);
false -> ok false -> ok
end. end.
@@ -127,12 +133,10 @@ debug(Tag, Options, Fmt, Args) ->
%% @doc Main entry point. %% @doc Main entry point.
compile(FCode, Options) -> compile(FCode, Options) ->
#{ contract_name := ContractName, #{ contract_name := ContractName,
state_type := StateType,
functions := Functions } = FCode, functions := Functions } = FCode,
SFuns = functions_to_scode(ContractName, Functions, Options), SFuns = functions_to_scode(ContractName, Functions, Options),
SFuns1 = optimize_scode(SFuns, Options), SFuns1 = optimize_scode(SFuns, Options),
SFuns2 = add_default_init_function(SFuns1, StateType), FateCode = to_basic_blocks(SFuns1),
FateCode = to_basic_blocks(SFuns2),
debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
FateCode. FateCode.
@@ -147,15 +151,17 @@ make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs,
functions_to_scode(ContractName, Functions, Options) -> functions_to_scode(ContractName, Functions, Options) ->
FunNames = maps:keys(Functions), FunNames = maps:keys(Functions),
maps:from_list( maps:from_list(
[ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Args, Body, Type, Options)} [ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|| {Name, #{args := Args, || {Name, #{args := Args,
body := Body, body := Body,
attrs := Attrs,
return := Type}} <- maps:to_list(Functions)]). return := Type}} <- maps:to_list(Functions)]).
function_to_scode(ContractName, Functions, _Name, Args, Body, ResType, _Options) -> function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) ->
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType), {ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
SCode = to_scode(init_env(ContractName, Functions, Args), Body), Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
{{ArgTypes, ResType1}, SCode}. SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body),
{Attrs, {ArgTypes, ResType1}, SCode}.
-define(tvars, '$tvars'). -define(tvars, '$tvars').
@@ -191,30 +197,16 @@ type_to_scode({tvar, X}) ->
J -> {tvar, J} J -> {tvar, J}
end. end.
add_default_init_function(SFuns, StateType) when StateType /= {tuple, []} ->
%% Only add default if the type is unit.
SFuns;
add_default_init_function(SFuns, {tuple, []}) ->
%% Only add default if the init function is not present
InitName = make_function_name({entrypoint, <<"init">>}),
case maps:find(InitName, SFuns) of
{ok, _} ->
SFuns;
error ->
Sig = {[], {tuple, []}},
Body = [tuple(0)],
SFuns#{ InitName => {Sig, Body} }
end.
%% -- Phase I ---------------------------------------------------------------- %% -- Phase I ----------------------------------------------------------------
%% Icode to structured assembly %% Icode to structured assembly
%% -- Environment functions -- %% -- Environment functions --
init_env(ContractName, FunNames, Args) -> init_env(ContractName, FunNames, Name, Args) ->
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName, contract = ContractName,
locals = FunNames, locals = FunNames,
current_function = Name,
tailpos = true }. tailpos = true }.
next_var(#env{ vars = Vars }) -> next_var(#env{ vars = Vars }) ->
@@ -317,6 +309,24 @@ to_scode(Env, {'let', X, Expr, Body}) ->
aeb_fate_ops:store({var, I}, {stack, 0}), aeb_fate_ops:store({var, I}, {stack, 0}),
to_scode(Env1, Body) ]; to_scode(Env1, Body) ];
to_scode(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) ->
%% Tail-call to current function, f(e0..en). Compile to
%% [ let xi = ei ]
%% [ STORE argi xi ]
%% jump 0
{Vars, Code, _Env} =
lists:foldl(fun(Arg, {Is, Acc, Env1}) ->
{I, Env2} = bind_local("_", Env1),
ArgCode = to_scode(notail(Env2), Arg),
Acc1 = [Acc, ArgCode,
aeb_fate_ops:store({var, I}, ?a)],
{[I | Is], Acc1, Env2}
end, {[], [], Env}, Args),
[ Code,
[ aeb_fate_ops:store({arg, I}, {var, J})
|| {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1),
lists:reverse(Vars)) ],
loop ];
to_scode(Env, {def, Fun, Args}) -> to_scode(Env, {def, Fun, Args}) ->
FName = make_function_id(Fun), FName = make_function_id(Fun),
Lbl = aeb_fate_data:make_string(FName), Lbl = aeb_fate_data:make_string(FName),
@@ -327,17 +337,19 @@ to_scode(Env, {funcall, Fun, Args}) ->
to_scode(Env, {builtin, B, Args}) -> to_scode(Env, {builtin, B, Args}) ->
builtin_to_scode(Env, B, Args); builtin_to_scode(Env, B, Args);
to_scode(Env, {remote, Ct, Fun, [{builtin, call_gas_left, _}, Value | Args]}) -> to_scode(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) ->
%% Gas is not limited.
Lbl = make_function_id(Fun),
Call = aeb_fate_ops:call_r(?a, Lbl, length(Args), ?a), %% No remote tail calls
call_to_scode(Env, Call, [Ct, Value | Args]);
to_scode(Env, {remote, Ct, Fun, [Gas, Value | Args]}) ->
%% Gas is limited.
Lbl = make_function_id(Fun), Lbl = make_function_id(Fun),
Call = aeb_fate_ops:call_gr(?a, Lbl, length(Args), ?a, ?a), %% No remote tail calls {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
call_to_scode(Env, Call, [Ct, Value, Gas | Args]); ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
case Gas of
{builtin, call_gas_left, _} ->
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
call_to_scode(Env, Call, [Ct, Value | Args]);
_ ->
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
end;
to_scode(Env, {closure, Fun, FVs}) -> to_scode(Env, {closure, Fun, FVs}) ->
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]}); to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
@@ -538,6 +550,8 @@ builtin_to_scode(Env, address_is_oracle, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:is_oracle(?a, ?a), Args); call_to_scode(Env, aeb_fate_ops:is_oracle(?a, ?a), Args);
builtin_to_scode(Env, address_is_contract, [_] = Args) -> builtin_to_scode(Env, address_is_contract, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:is_contract(?a, ?a), Args); call_to_scode(Env, aeb_fate_ops:is_contract(?a, ?a), Args);
builtin_to_scode(Env, address_is_payable, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:is_payable(?a, ?a), Args);
builtin_to_scode(Env, aens_resolve, [_Name, _Key, _Type] = Args) -> builtin_to_scode(Env, aens_resolve, [_Name, _Key, _Type] = Args) ->
call_to_scode(Env, aeb_fate_ops:aens_resolve(?a, ?a, ?a, ?a), Args); call_to_scode(Env, aeb_fate_ops:aens_resolve(?a, ?a, ?a, ?a), Args);
builtin_to_scode(Env, aens_preclaim, [_Sign, _Account, _Hash] = Args) -> builtin_to_scode(Env, aens_preclaim, [_Sign, _Account, _Hash] = Args) ->
@@ -591,15 +605,17 @@ op_to_scode(bits_union) -> aeb_fate_ops:bits_or(?a, ?a, ?a);
op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a);
op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a);
op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a);
op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a);
op_to_scode(crypto_ecverify) -> aeb_fate_ops:ecverify(?a, ?a, ?a, ?a); op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a);
op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a); op_to_scode(crypto_verify_sig_secp256k1) -> aeb_fate_ops:verify_sig_secp256k1(?a, ?a, ?a, ?a);
op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a);
op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, ?a, ?a);
op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a);
op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a);
op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a);
op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a). op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a);
op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a);
op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a).
%% PUSH and STORE ?a are the same, so we use STORE to make optimizations %% PUSH and STORE ?a are the same, so we use STORE to make optimizations
%% easier, and specialize to PUSH (which is cheaper) at the end. %% easier, and specialize to PUSH (which is cheaper) at the end.
@@ -624,12 +640,12 @@ flatten_s(I) -> I.
-define(MAX_SIMPL_ITERATIONS, 10). -define(MAX_SIMPL_ITERATIONS, 10).
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) -> optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
Code0 = flatten(Code), Code0 = flatten(Code),
debug(opt, Options, "Optimizing ~s\n", [Name]), debug(opt, Options, "Optimizing ~s\n", [Name]),
Code1 = simpl_loop(0, Code0, Options), Code1 = simpl_loop(0, Code0, Options),
Code2 = desugar(Code1), Code2 = desugar(Code1),
{{Args, Res}, Code2}. {Attrs, Sig, Code2}.
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS -> simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]), debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
@@ -668,12 +684,15 @@ pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
Fmt = fun([]) -> "()"; Fmt = fun([]) -> "()";
(Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ") (Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ")
end, end,
Op = [Ind, aeb_fate_pp:format_op(I, #{})], Op = [Ind, pp_op(I)],
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []], Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
[io_lib:format("~-40s~s\n", [Op, Ann]), [io_lib:format("~-40s~s\n", [Op, Ann]),
pp_ann(Ind, Code)]; pp_ann(Ind, Code)];
pp_ann(_, []) -> []. pp_ann(_, []) -> [].
pp_op(loop) -> "LOOP";
pp_op(I) ->
aeb_fate_pp:format_op(I, #{}).
pp_arg(?i(I)) -> io_lib:format("~w", [I]); pp_arg(?i(I)) -> io_lib:format("~w", [I]);
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]); pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
@@ -740,11 +759,12 @@ attributes(I) ->
Pure = fun(W, R) -> Attr(W, R, true) end, Pure = fun(W, R) -> Attr(W, R, true) end,
Impure = fun(W, R) -> Attr(W, R, false) end, Impure = fun(W, R) -> Attr(W, R, false) end,
case I of case I of
loop -> Impure(pc, []);
'RETURN' -> Impure(pc, []); 'RETURN' -> Impure(pc, []);
{'RETURNR', A} -> Impure(pc, A); {'RETURNR', A} -> Impure(pc, A);
{'CALL', _} -> Impure(?a, []); {'CALL', _} -> Impure(?a, []);
{'CALL_R', A, _, _, B} -> Impure(?a, [A, B]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
{'CALL_GR', A, _, _, B, C} -> Impure(?a, [A, B, C]); {'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
{'CALL_T', _} -> Impure(pc, []); {'CALL_T', _} -> Impure(pc, []);
{'CALL_VALUE', A} -> Pure(A, []); {'CALL_VALUE', A} -> Pure(A, []);
{'JUMP', _} -> Impure(pc, []); {'JUMP', _} -> Impure(pc, []);
@@ -819,8 +839,10 @@ attributes(I) ->
{'SHA3', A, B} -> Pure(A, [B]); {'SHA3', A, B} -> Pure(A, [B]);
{'SHA256', A, B} -> Pure(A, [B]); {'SHA256', A, B} -> Pure(A, [B]);
{'BLAKE2B', A, B} -> Pure(A, [B]); {'BLAKE2B', A, B} -> Pure(A, [B]);
{'ECVERIFY', A, B, C, D} -> Pure(A, [B, C, D]); {'VERIFY_SIG', A, B, C, D} -> Pure(A, [B, C, D]);
{'VERIFY_SIG_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]);
{'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]);
{'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]);
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
{'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX_HASH', A} -> Pure(A, []);
{'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_INT', A, B} -> Pure(A, [B]);
@@ -829,6 +851,7 @@ attributes(I) ->
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
{'IS_ORACLE', A, B} -> Impure(A, [B]); {'IS_ORACLE', A, B} -> Impure(A, [B]);
{'IS_CONTRACT', A, B} -> Impure(A, [B]); {'IS_CONTRACT', A, B} -> Impure(A, [B]);
{'IS_PAYABLE', A, B} -> Impure(A, [B]);
{'CREATOR', A} -> Pure(A, []); {'CREATOR', A} -> Pure(A, []);
{'ADDRESS', A} -> Pure(A, []); {'ADDRESS', A} -> Pure(A, []);
{'BALANCE', A} -> Impure(A, []); {'BALANCE', A} -> Impure(A, []);
@@ -939,15 +962,29 @@ simpl_s({switch, Arg, Type, Alts, Def}, Options) ->
{switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)}; {switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)};
simpl_s(I, _) -> I. simpl_s(I, _) -> I.
simpl_top(I, Code, Options) -> %% Safe-guard against loops in the rewriting. Shouldn't happen so throw an
apply_rules(rules(), I, Code, Options). %% error if we run out.
-define(SIMPL_FUEL, 5000).
apply_rules(Rules, I, Code, Options) -> simpl_top(I, Code, Options) ->
Cons = fun(X, Xs) -> simpl_top(X, Xs, Options) end, simpl_top(?SIMPL_FUEL, I, Code, Options).
simpl_top(0, I, Code, _Options) ->
error({out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) ->
apply_rules(Fuel, rules(), I, Code, Options).
apply_rules(Fuel, Rules, I, Code, Options) ->
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
case apply_rules_once(Rules, I, Code) of case apply_rules_once(Rules, I, Code) of
false -> [I | Code]; false -> [I | Code];
{RName, New, Rest} -> {RName, New, Rest} ->
debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", [I | Code]), pp_ann(" ", New ++ Rest)]), case is_debug(opt_rules, Options) of
true ->
{OldCode, NewCode} = drop_common_suffix([I | Code], New ++ Rest),
debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]);
false -> ok
end,
lists:foldr(Cons, Rest, New) lists:foldr(Cons, Rest, New)
end. end.
@@ -971,6 +1008,7 @@ merge_rules() ->
rules() -> rules() ->
merge_rules() ++ merge_rules() ++
[?RULE(r_swap_push), [?RULE(r_swap_push),
?RULE(r_swap_pop),
?RULE(r_swap_write), ?RULE(r_swap_write),
?RULE(r_constant_propagation), ?RULE(r_constant_propagation),
?RULE(r_prune_impossible_branches), ?RULE(r_prune_impossible_branches),
@@ -1008,11 +1046,12 @@ inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) ->
{As0, As1} = split_stack_arg(Stack, As), {As0, As1} = split_stack_arg(Stack, As),
Acc1 = [{i, merge_ann(Ann1, Ann2), from_op_view(Op, R, As0 ++ [Arg] ++ As1)} | Acc], Acc1 = [{i, merge_ann(Ann1, Ann2), from_op_view(Op, R, As0 ++ [Arg] ++ As1)} | Acc],
{lists:reverse(Acc1), Code}; {lists:reverse(Acc1), Code};
false -> false when Arg /= R ->
{AI1, {i, Ann1b, _}} = swap_instrs({i, Ann1, {'STORE', ?a, Arg}}, AI), {AI1, {i, Ann1b, _}} = swap_instrs({i, Ann1, {'STORE', ?a, Arg}}, AI),
inline_push(Ann1b, Arg, Stack + Produces - Consumes, Code, [AI1 | Acc]) inline_push(Ann1b, Arg, Stack + Produces - Consumes, Code, [AI1 | Acc]);
false -> false
end; end;
false -> false _ -> false
end; end;
inline_push(_, _, _, _, _) -> false. inline_push(_, _, _, _, _) -> false.
@@ -1024,16 +1063,42 @@ split_stack_arg(N, [A | As], Acc) ->
true -> N end, true -> N end,
split_stack_arg(N1, As, [A | Acc]). split_stack_arg(N1, As, [A | Acc]).
%% Move PUSH A past non-stack instructions. %% Move PUSHes past non-stack instructions.
r_swap_push(Push = {i, _, {'STORE', ?a, _}}, [I | Code]) -> r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
case independent(Push, I) of case op_view(PushI) of
true -> {_, ?a, _} ->
{I1, Push1} = swap_instrs(Push, I), case independent(Push, I) of
{[I1, Push1], Code}; true ->
false -> false {I1, Push1} = swap_instrs(Push, I),
{[I1, Push1], Code};
false -> false
end;
_ -> false
end; end;
r_swap_push(_, _) -> false. r_swap_push(_, _) -> false.
%% Move non-stack instruction past POPs.
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
case independent(IA, JA) of
true ->
case {op_view(I), op_view(J)} of
{false, _} -> false;
{_, false} -> false;
{{_, IR, IAs}, {_, RJ, JAs}} ->
NonStackI = not lists:member(?a, [IR | IAs]),
%% RJ /= ?a to not conflict with r_swap_push
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
case NonStackI andalso PopJ of
false -> false;
true ->
{JA1, IA1} = swap_instrs(IA, JA),
{[JA1, IA1], Code}
end
end;
false -> false
end;
r_swap_pop(_, _) -> false.
%% Match up writes to variables with instructions further down. %% Match up writes to variables with instructions further down.
r_swap_write(I = {i, _, _}, [J | Code]) -> r_swap_write(I = {i, _, _}, [J | Code]) ->
case {var_writes(I), independent(I, J)} of case {var_writes(I), independent(I, J)} of
@@ -1169,6 +1234,8 @@ r_float_switch_body(I = {i, _, _}, [switch_body | Code]) ->
r_float_switch_body(_, _) -> false. r_float_switch_body(_, _) -> false.
%% Inline stores %% Inline stores
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
{[], Code};
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) -> r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
%% Not when A is var unless updating the annotations properly. %% Not when A is var unless updating the annotations properly.
Inline = case A of Inline = case A of
@@ -1261,9 +1328,13 @@ unannotate(Code) when is_list(Code) ->
unannotate({i, _Ann, I}) -> [I]. unannotate({i, _Ann, I}) -> [I].
%% Desugar and specialize %% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()]; desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()];
desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()]; desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(A)];
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(A)]; desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()];
desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(A)];
desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()];
desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(A)];
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(A)];
desugar({switch, Arg, Type, Alts, Def}) -> desugar({switch, Arg, Type, Alts, Def}) ->
[{switch, Arg, Type, [desugar(A) || A <- Alts], desugar(Def)}]; [{switch, Arg, Type, [desugar(A) || A <- Alts], desugar(Def)}];
desugar(missing) -> missing; desugar(missing) -> missing;
@@ -1277,9 +1348,9 @@ desugar(I) -> [I].
to_basic_blocks(Funs) -> to_basic_blocks(Funs) ->
to_basic_blocks(maps:to_list(Funs), aeb_fate_code:new()). to_basic_blocks(maps:to_list(Funs), aeb_fate_code:new()).
to_basic_blocks([{Name, {Sig, Code}}|Left], Acc) -> to_basic_blocks([{Name, {Attrs, Sig, Code}}|Left], Acc) ->
BB = bb(Name, Code ++ [aeb_fate_ops:return()]), BB = bb(Name, Code ++ [aeb_fate_ops:return()]),
to_basic_blocks(Left, aeb_fate_code:insert_fun(Name, Sig, BB, Acc)); to_basic_blocks(Left, aeb_fate_code:insert_fun(Name, Attrs, Sig, BB, Acc));
to_basic_blocks([], Acc) -> to_basic_blocks([], Acc) ->
Acc. Acc.
@@ -1463,7 +1534,8 @@ split_calls(Ref, [], Acc, Blocks) ->
split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL'; split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
element(1, I) == 'CALL_R'; element(1, I) == 'CALL_R';
element(1, I) == 'CALL_GR'; element(1, I) == 'CALL_GR';
element(1, I) == 'jumpif' -> element(1, I) == 'jumpif';
I == loop ->
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
lists:reverse([{Ref, lists:reverse([I | Acc])} | Blocks]); lists:reverse([{Ref, lists:reverse([I | Acc])} | Blocks]);
@@ -1476,7 +1548,8 @@ split_calls(Ref, [I | Code], Acc, Blocks) ->
set_labels(Labels, {Ref, Code}) when is_reference(Ref) -> set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]}; {maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
set_labels(Labels, {jump, Ref}) -> aeb_fate_ops:jump(maps:get(Ref, Labels)); set_labels(_Labels, loop) -> aeb_fate_ops:jump(0);
set_labels(Labels, {jump, Ref}) -> aeb_fate_ops:jump(maps:get(Ref, Labels));
set_labels(Labels, {jumpif, Arg, Ref}) -> aeb_fate_ops:jumpif(Arg, maps:get(Ref, Labels)); set_labels(Labels, {jumpif, Arg, Ref}) -> aeb_fate_ops:jumpif(Arg, maps:get(Ref, Labels));
set_labels(Labels, {switch, Arg, Refs}) -> set_labels(Labels, {switch, Arg, Refs}) ->
case [ maps:get(Ref, Labels) || Ref <- Refs ] of case [ maps:get(Ref, Labels) || Ref <- Refs ] of
@@ -1490,3 +1563,11 @@ set_labels(_, I) -> I.
with_ixs(Xs) -> with_ixs(Xs) ->
lists:zip(lists:seq(0, length(Xs) - 1), Xs). lists:zip(lists:seq(0, length(Xs) - 1), Xs).
drop_common_suffix(Xs, Ys) ->
drop_common_suffix_r(lists:reverse(Xs), lists:reverse(Ys)).
drop_common_suffix_r([X | Xs], [X | Ys]) ->
drop_common_suffix_r(Xs, Ys);
drop_common_suffix_r(Xs, Ys) ->
{lists:reverse(Xs), lists:reverse(Ys)}.
+9 -1
View File
@@ -13,6 +13,7 @@
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,
@@ -48,6 +49,7 @@
, 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) ->
@@ -65,7 +67,8 @@ 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,
@@ -75,6 +78,7 @@ 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
@@ -103,6 +107,10 @@ 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 }.
+95 -28
View File
@@ -6,6 +6,8 @@
-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").
@@ -14,15 +16,24 @@
| {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, []). string(String, sets:new(), []).
-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, Opts); expand_includes(AST, Included, Opts);
Err = {error, _} -> Err = {error, _} ->
Err Err
end. end.
@@ -46,6 +57,7 @@ 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, get_ann(_1), _2})
@@ -70,7 +82,7 @@ fun_or_entry() ->
?RULE(keyword(entrypoint), {entrypoint, _1})]). ?RULE(keyword(entrypoint), {entrypoint, _1})]).
modifiers() -> modifiers() ->
many(choice([token(stateful), token(private), token(public)])). many(choice([token(stateful), token(payable), token(private), token(public)])).
add_modifiers(Mods, Entry = {entrypoint, _}, Node) -> add_modifiers(Mods, Entry = {entrypoint, _}, Node) ->
add_modifiers(Mods ++ [Entry], Node); add_modifiers(Mods ++ [Entry], Node);
@@ -138,7 +150,7 @@ type() -> ?LAZY_P(type100()).
type100() -> type200(). type100() -> type200().
type200() -> type200() ->
?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)). ?RULE(many({type300(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type300() ->
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)). ?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
@@ -157,16 +169,15 @@ type400() ->
typeAtom() -> typeAtom() ->
?LAZY_P(choice( ?LAZY_P(choice(
[ parens(type()) [ parens(type())
, args_t()
, id(), token(con), token(qcon), token(qid), tvar() , id(), token(con), token(qcon), token(qid), tvar()
])). ])).
fun_domain() -> ?LAZY_P(choice( args_t() ->
[ ?RULE(tok('('), tok(')'), []) ?LAZY_P(choice(
%% Note avoidance of ambiguity: `(int)` can be treated as: [ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
%% - literally `int` %% Singleton case handled separately
%% - list of arguments with just one element int. This approach is dropped. , ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), [_2|_4])
, ?RULE(type300(), [_1])
])). ])).
%% -- Statements ------------------------------------------------------------- %% -- Statements -------------------------------------------------------------
@@ -230,11 +241,25 @@ exprAtom() ->
, {bool, keyword(false), false} , {bool, keyword(false), false}
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs))
, {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})
@@ -475,12 +500,15 @@ 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({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end, lists:foldr(fun({{args_t, _, Dom}, Ann}, T) -> {fun_t, Ann, [], Dom, T};
({Dom, Ann}, T) -> {fun_t, Ann, [], [Dom], T} end,
Type, Domains). Type, Domains).
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}.
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({app, Ann, Con = {'::', _}, Es}) -> parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)}; {app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
@@ -521,26 +549,37 @@ bad_expr_err(Reason, E) ->
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([], Acc, _Opts) -> expand_includes(AST, Included, 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, _, S = {string, _, File}} | AST], Acc, Opts) -> expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
case read_file(File, Opts) of case get_include_code(File, Ann, Opts) of
{ok, Bin} -> {ok, Code} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), Hashed = hash_include(File, Code),
case string(binary_to_list(Bin), Opts1) of case sets:is_element(Hashed, Included) of
{ok, AST1} -> false ->
expand_includes(AST1 ++ AST, Acc, Opts); Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
Err = {error, _} -> Included1 = sets:add_element(Hashed, Included),
Err case parse_and_scan(file(), Code, Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} ->
Err
end;
true ->
expand_includes(AST, Included, Acc, Opts)
end; end;
{error, _} -> Err = {error, _} ->
{error, {get_pos(S), include_error, File}} Err
end; end;
expand_includes([E | AST], Acc, Opts) -> expand_includes([E | AST], Included, Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts). expand_includes(AST, Included, [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
@@ -555,3 +594,31 @@ 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(_) -> [].
+4
View File
@@ -235,12 +235,16 @@ type(Type, Options) ->
-spec type(aeso_syntax:type()) -> doc(). -spec type(aeso_syntax:type()) -> doc().
type({fun_t, _, Named, Args, Ret}) -> type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret)); follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, 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), args_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, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) -> type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"])); text(lists:concat(["bytes(", Len, ")"]));
+1 -1
View File
@@ -37,7 +37,7 @@ lexer() ->
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"], "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
+17
View File
@@ -0,0 +1,17 @@
%%%-------------------------------------------------------------------
%%% @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"]).
+6
View File
@@ -59,6 +59,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() | any} | {bytes_t, ann(), integer() | any}
| id() | qid() | id() | qid()
| con() | qcon() %% contracts | con() | qcon() %% contracts
@@ -92,6 +93,7 @@
| {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
@@ -104,6 +106,10 @@
| 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
+15 -6
View File
@@ -49,7 +49,7 @@ 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), Scoped(BindExpr(Xs), Expr(E))]); {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
%% typedef() %% typedef()
{alias_t, T} -> Type(T); {alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs); {record_t, Fs} -> Type(Fs);
@@ -71,6 +71,15 @@ 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]);
@@ -83,7 +92,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, _, X, T} -> Plus(Expr(X), Type(T)); {arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
%% alt() %% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E)); {'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim() %% elim()
@@ -115,12 +124,12 @@ used_types([Top] = _CurrentNS, T) ->
entity_alg() -> entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end, IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end, Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end, Remove = fun(Keys, Map) -> maps:without(Keys, Map) end,
Scoped = fun(Xs, Ys) -> Scoped = fun(Xs, Ys) ->
Bound = [E || E <- maps:keys(Ys), IsBound(E)], Bound = [E || E <- maps:keys(Xs), IsBound(E)],
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ], Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
maps:merge(Remove(Bound1, Xs), Others) Others = Remove(Bound1, Ys),
maps:merge(Remove(Bound, Xs), Others)
end, end,
#alg{ zero = #{} #alg{ zero = #{}
, plus = fun maps:merge/2 , plus = fun maps:merge/2
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "3.2.0"}, {vsn, "4.0.0-rc1"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+3 -3
View File
@@ -84,7 +84,7 @@ encode_decode_sophia_string(SophiaType, String) ->
{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} ->
@@ -173,7 +173,7 @@ 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} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]), {ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]),
?assertEqual(RetType, RetVMType), ?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]}, CalldataType = {tuple, [word, {tuple, ArgTypes}]},
@@ -182,7 +182,7 @@ encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
"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,
+12 -9
View File
@@ -14,20 +14,22 @@ test_contract(N) ->
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
test_cases(1) -> test_cases(1) ->
Contract = <<"contract C =\n" Contract = <<"payable contract C =\n"
" entrypoint a(i : int) = i+1\n">>, " payable stateful entrypoint 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}]}},
" entrypoint 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) ->
@@ -35,7 +37,7 @@ test_cases(2) ->
" type allan = int\n" " type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>, " entrypoint a(i : allan) = i+1\n">>,
MapACI = #{contract => MapACI = #{contract =>
#{name => <<"C">>, #{name => <<"C">>, payable => false,
type_defs => type_defs =>
[#{name => <<"allan">>, [#{name => <<"allan">>,
typedef => <<"int">>, typedef => <<"int">>,
@@ -46,7 +48,8 @@ test_cases(2) ->
type => <<"C.allan">>}], type => <<"C.allan">>}],
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" " type allan = int\n"
" entrypoint a : (C.allan) => int\n">>, " entrypoint a : (C.allan) => int\n">>,
@@ -64,8 +67,8 @@ test_cases(3) ->
type => type =>
#{<<"C.bert">> => [<<"string">>]}}], #{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>, name => <<"a">>,returns => <<"int">>,
stateful => false}], stateful => false, payable => false}],
name => <<"C">>, name => <<"C">>, payable => false,
event => #{variant => [#{<<"SingleEventDefined">> => []}]}, event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>, state => <<"unit">>,
type_defs => type_defs =>
+3 -2
View File
@@ -116,10 +116,11 @@ compilable_contracts() ->
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]}, {"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]}, {"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
{"__call" "init", []}, {"__call" "init", []},
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]}, {"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f20212223242526272829303132333435363738"]},
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]}, {"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
{"stub", "foo", ["42"]}, {"stub", "foo", ["42"]},
{"stub", "foo", ["-42"]} {"stub", "foo", ["-42"]},
{"payable", "foo", ["42"]}
]. ].
not_yet_compilable(fate) -> not_yet_compilable(fate) ->
+26 -7
View File
@@ -53,15 +53,17 @@ simple_compile_test_() ->
#{byte_code := Code2} = compile(aevm, "include"), #{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2) ?assertMatch(true, Code1 == Code2)
end} ] ++ end} ] ++
[ {"Testing deadcode elimination", [ {"Testing deadcode elimination for " ++ atom_to_list(Backend),
fun() -> fun() ->
#{ byte_code := NoDeadCode } = compile(aevm, "nodeadcode"), #{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"),
#{ byte_code := DeadCode } = compile(aevm, "deadcode"), #{ byte_code := DeadCode } = compile(Backend, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode), SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode), SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}), Delta = if Backend == aevm -> 40;
Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok ok
end} ]. end} || Backend <- [aevm, fate] ].
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -73,7 +75,8 @@ check_errors(Expect, ErrorString) ->
end. end.
compile(Backend, Name) -> compile(Backend, Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]). compile(Backend, Name,
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
@@ -118,7 +121,13 @@ compilable_contracts() ->
"namespace_bug", "namespace_bug",
"bytes_to_x", "bytes_to_x",
"aens", "aens",
"tuple_match" "tuple_match",
"cyclic_include",
"stdlib_include",
"double_include",
"manual_stdlib_include",
"list_comp",
"payable"
]. ].
not_yet_compilable(fate) -> []; not_yet_compilable(fate) -> [];
@@ -354,4 +363,14 @@ failing_contracts() ->
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>, <<"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 10, column 3):\n entrypoint foo() = ()">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]} <<"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">>
]}
]. ].
+9 -3
View File
@@ -71,8 +71,10 @@ 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.
@@ -84,12 +86,16 @@ parse_expr(Text) ->
round_trip(Text) -> round_trip(Text) ->
Contract = parse_string(Text), Contract = parse_string(Text),
Text1 = prettypr:format(aeso_pretty:decls(Contract)), Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(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]) ->
+3
View File
@@ -31,3 +31,6 @@ contract AddrChain =
entrypoint c_creator() : address = entrypoint c_creator() : address =
Contract.creator Contract.creator
entrypoint is_payable(a : address) : bool =
Address.is_payable(a)
+1 -1
View File
@@ -10,7 +10,7 @@ contract BasicAuth =
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.ecverify(to_sign(tx_hash, n), state.owner, s) Some(tx_hash) => Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) = entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
+3 -3
View File
@@ -1,9 +1,9 @@
contract BitcoinAuth = contract BitcoinAuth =
record state = { nonce : int, owner : bytes(64) } record state = { nonce : int, owner : bytes(20) }
entrypoint init(owner' : bytes(64)) = { nonce = 1, owner = owner' } entrypoint init(owner' : bytes(20)) = { nonce = 1, owner = owner' }
stateful entrypoint authorize(n : int, s : signature) : bool = stateful entrypoint authorize(n : int, s : bytes(65)) : bool =
require(n >= state.nonce, "Nonce too low") require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high") require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 }) put(state{ nonce = n + 1 })
+1 -1
View File
@@ -50,7 +50,7 @@ contract ComplexTypes =
entrypoint remote_pair(n : int, s : string) : int * string = entrypoint 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)
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_forth.aes"
contract CI =
entrypoint ci() = Back.back() + Forth.forth()
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_forth.aes"
namespace Back =
function back() = 2
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_back.aes"
namespace Forth =
function forth() = 3
+3 -3
View File
@@ -1,5 +1,5 @@
namespace List = namespace MyList =
function map1(f : 'a => 'b, xs : list('a)) = function map1(f : 'a => 'b, xs : list('a)) =
switch(xs) switch(xs)
@@ -14,8 +14,8 @@ namespace List =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) MyList.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = entrypoint inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) MyList.map1((x) => x + 1, xs)
+7
View File
@@ -0,0 +1,7 @@
include "included.aes"
include "../contracts/included.aes"
contract Include =
entrypoint foo() =
Included.foo()
+23
View File
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,2 @@
contract BadComp =
entrypoint failing() = [x + 1 | x <- [1,2,3], let x = "XD"]
+2
View File
@@ -0,0 +1,2 @@
contract BadComp =
entrypoint failing() = [x | x <- [], if (3)]
+2
View File
@@ -0,0 +1,2 @@
contract ListCompBad =
entrypoint failing() = [x | x <- 1]
+7
View File
@@ -0,0 +1,7 @@
contract Fail =
entrypoint tttt() : bool * int =
let f(x : 'a) : 'a = x
(f(true), f(1))
+6
View File
@@ -0,0 +1,6 @@
// 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))
+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(List.zip([1..n], caller() :: owners)), owners = Map.from_list(MyList.zip([1..n], caller() :: owners)),
ownerIndex = Map.from_list(List.zip(caller() :: owners, [1..n])) } ownerIndex = Map.from_list(MyList.zip(caller() :: owners, [1..n])) }
function lookup(map, key) = function lookup(map, key) =
switch(Map.get(key, map)) switch(Map.get(key, map))
+3 -3
View File
@@ -1,5 +1,5 @@
namespace List = namespace MyList =
function map1(f : 'a => 'b, xs : list('a)) = function map1(f : 'a => 'b, xs : list('a)) =
switch(xs) switch(xs)
@@ -14,8 +14,8 @@ namespace List =
contract Deadcode = contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) = entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs) MyList.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) = entrypoint inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs) MyList.map2((x) => x + 1, xs)
+3
View File
@@ -0,0 +1,3 @@
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: List.map((name) => {name: name, voteCount: 0}, proposalNames) proposals: MyList.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 = List.nth(state().proposals, candidate); let proposal = MyList.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() =
List.map((p) => (p.name, p.voteCount), state().proposals); MyList.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());
List.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally); MyList.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);
}; };
+6
View File
@@ -0,0 +1,6 @@
include "List.aes"
include "Func.aes"
contract StdInc =
entrypoint test() = List.map((x) => Func.id(x), [1,2,3,4])
+3 -2
View File
@@ -91,10 +91,11 @@ contract Identity =
// } // }
// let id(x) = x // let id(x) = x
// let main(xs) = map(double,xs) // let main(xs) = map(double,xs)
entrypoint z(f,x) = x function z(f,x) = x
function s(n) = (f,x)=>f(n(f,x)) function s(n) = (f,x)=>f(n(f,x))
function add(m,n) = (f,x)=>m(f,n(f,x)) function add(m,n) = (f,x)=>m(f,n(f,x))
entrypoint main(_) =
entrypoint main() =
let three=s(s(s(z))) let three=s(s(s(z)))
add(three,three) add(three,three)
(((i)=>i+1),0) (((i)=>i+1),0)
+1 -1
View File
@@ -1,6 +1,6 @@
contract TuplesMatch = contract TuplesMatch =
entrypoint tuplify3() = (t) => switch(t) function tuplify3() = (t) => switch(t)
(x, y, z) => 3 (x, y, z) => 3
entrypoint fst(p : int * string) = entrypoint fst(p : int * string) =
+3 -3
View File
@@ -36,7 +36,7 @@ contract Voting =
function init(proposalNames: list(string)): state = function init(proposalNames: list(string)): state =
{ chairPerson = caller(), { chairPerson = caller(),
voters = Map.empty, voters = Map.empty,
proposals = List.map((name) => {name = name, voteCount = 0}, proposalNames) } proposals = MyList.map((name) => {name = name, voteCount = 0}, proposalNames) }
function initVoter() = { weight = 1, vote = NotVoted} function initVoter() = { weight = 1, vote = NotVoted}
@@ -53,7 +53,7 @@ contract Voting =
_ => delegate _ => delegate
function addVote(candidate, weight) = function addVote(candidate, weight) =
let proposal = List.nth(state.proposals, candidate) let proposal = MyList.nth(state.proposals, candidate)
proposal{ voteCount = proposal.voteCount + weight } proposal{ voteCount = proposal.voteCount + weight }
function delegateVote(delegateTo: address, weight: uint) = function delegateVote(delegateTo: address, weight: uint) =
@@ -93,5 +93,5 @@ contract Voting =
// const // const
function currentTally() = function currentTally() =
List.map((p) => (p.name, p.voteCount), state.proposals) MyList.map((p) => (p.name, p.voteCount), state.proposals)