Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9335db65d0 | |||
| 0bc9cfb957 | |||
| 89afa9ec8f | |||
| 5575d3cb17 | |||
| 1d6f24965b | |||
| bd726a8902 | |||
| 7277a968f8 | |||
| d8558df6a4 | |||
| 4d6b13bcf1 | |||
| cf7830e4f5 | |||
| 03e53f60cd | |||
| c0330be3b4 | |||
| 701a24553a | |||
| 54c8c50a58 | |||
| cdcc119c9e | |||
| 1aa476318f | |||
| 97db569b9b | |||
| 5c14fc3fe6 | |||
| 85c14c512b | |||
| 126a78455b | |||
| ffc63704fb | |||
| c4243fb1da | |||
| a64e9643fd | |||
| ed934019db | |||
| 0f5daafc29 | |||
| e050618d7a | |||
| 62a0ff2e4c | |||
| 7b8957b46a | |||
| e46226a693 | |||
| b599d581ee | |||
| b3767071a8 | |||
| b0e6418161 | |||
| a894876f56 | |||
| 0af45dfd19 | |||
| c5bfcd3bdc | |||
| 85879f5380 | |||
| 8897cc6cbd | |||
| 0ec7fdc6ac | |||
| 74aff5401b | |||
| cfcf0a8a81 | |||
| ca31db7cad | |||
| 196460a607 | |||
| bf04362f9a | |||
| d4ea7d5d3b | |||
| c1c3c29393 | |||
| b474bb22cd | |||
| c04f66a00a | |||
| 37d86ad45b | |||
| 60f3a484e6 | |||
| 40c78c1707 | |||
| cf08aeee04 | |||
| a04dd6c86d | |||
| f488b35f2e | |||
| cc1de9baba | |||
| fe5f5545d3 | |||
| 98a4049f03 | |||
| 3dce0e627b | |||
| 6b46fc268b | |||
| 30bedad164 | |||
| 4d6938c741 | |||
| 10fc88a21d | |||
| 3218a2c172 |
@@ -3,7 +3,7 @@ version: 2.1
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
- image: aeternity/builder:xenial-otp21
|
||||
user: builder
|
||||
working_directory: ~/aesophia
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: |
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mkdocs==1.2.3
|
||||
mkdocs==1.2.4
|
||||
mkdocs-simple-hooks==0.1.3
|
||||
mkdocs-material==7.1.9
|
||||
mike==1.0.1
|
||||
mike==1.0.1
|
||||
pygments==2.11.2
|
||||
@@ -6,8 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Compiler warnings for the follwing: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
|
||||
- The pipe operator |>
|
||||
```
|
||||
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
|
||||
```
|
||||
- Allow binary operators to be used as lambdas
|
||||
```
|
||||
function sum(l : list(int)) : int = foldl((+), 0, l)
|
||||
function logical_and(x, y) = (&&)(x, y)
|
||||
```
|
||||
### Changed
|
||||
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
|
||||
- Ban empty record definitions (e.g. `record r = {}` would give an error).
|
||||
### Removed
|
||||
- Support for AEVM has been entirely wiped
|
||||
|
||||
## [6.1.0] - 2021-10-20
|
||||
### Added
|
||||
|
||||
@@ -49,12 +49,8 @@ The **pp_** options all print to standard output the following:
|
||||
|
||||
`pp_typed_ast` - print the AST with type information at each node
|
||||
|
||||
`pp_icode` - print the internal code structure
|
||||
|
||||
`pp_assembler` - print the generated assembler code
|
||||
|
||||
`pp_bytecode` - print the bytecode instructions
|
||||
|
||||
#### check_call(ContractString, Options) -> CheckRet
|
||||
|
||||
Types
|
||||
@@ -66,15 +62,6 @@ Type = term()
|
||||
```
|
||||
Check a call in contract through the `__call` function.
|
||||
|
||||
#### sophia_type_to_typerep(String) -> TypeRep
|
||||
|
||||
Types
|
||||
``` erlang
|
||||
{ok,TypeRep} | {error, badtype}
|
||||
```
|
||||
|
||||
Get the type representation of a type declaration.
|
||||
|
||||
#### version() -> {ok, Version} | {error, term()}
|
||||
|
||||
Types
|
||||
|
||||
+8
-14
@@ -99,6 +99,9 @@ running out of gas it is necessary to set a gas limit using the `gas` argument.
|
||||
However, note that errors that would normally consume all the gas in the
|
||||
transaction still only uses up the gas spent running the contract.
|
||||
|
||||
Any side effects (state change, token transfers, etc.) made by a failing
|
||||
protected call is rolled back, just like they would be in the unprotected case.
|
||||
|
||||
|
||||
### Contract factories and child contracts
|
||||
|
||||
@@ -222,9 +225,6 @@ payable stateful entrypoint buy(to : address) =
|
||||
abort("Value too low")
|
||||
```
|
||||
|
||||
Note: In the æternity VM (AEVM) contracts and entrypoints were by default
|
||||
payable until the Lima release.
|
||||
|
||||
## Namespaces
|
||||
|
||||
Code can be split into libraries using the `namespace` construct. Namespaces
|
||||
@@ -402,7 +402,7 @@ Sophia has the following types:
|
||||
|
||||
## Arithmetic
|
||||
|
||||
Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following
|
||||
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
|
||||
arithmetic operations:
|
||||
- addition (`x + y`)
|
||||
- subtraction (`x - y`)
|
||||
@@ -411,22 +411,16 @@ arithmetic operations:
|
||||
- remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y`
|
||||
- exponentiation (`x ^ y`)
|
||||
|
||||
All operations are *safe* with respect to overflow and underflow. On AEVM they behave as the corresponding
|
||||
operations on arbitrary-size integers and fail with `arithmetic_error` if the
|
||||
result cannot be represented by a 256-bit signed word. For example, `2 ^ 255`
|
||||
fails rather than wrapping around to -2²⁵⁵.
|
||||
|
||||
The division and modulo operations also throw an arithmetic error if the
|
||||
second argument is zero.
|
||||
All operations are *safe* with respect to overflow and underflow.
|
||||
The division and modulo operations throw an arithmetic error if the
|
||||
right-hand operand is zero.
|
||||
|
||||
## Bit fields
|
||||
|
||||
Sophia integers do not support bit arithmetic. Instead there is a separate
|
||||
type `bits`. See the standard library [documentation](sophia_stdlib.md#bits).
|
||||
|
||||
On the AEVM a bit field is represented by a 256-bit word and reading or writing
|
||||
a bit outside the 0..255 range fails with an `arithmetic_error`. On FATE a bit
|
||||
field can be of arbitrary size (but it is still represented by the
|
||||
A bit field can be of arbitrary size (but it is still represented by the
|
||||
corresponding integer, so setting very high bits can be expensive).
|
||||
|
||||
## Type aliases
|
||||
|
||||
+1282
-1271
File diff suppressed because it is too large
Load Diff
@@ -200,6 +200,7 @@ switch(f(x))
|
||||
|
||||
```c
|
||||
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
|
||||
| '(' BinOp ')' // Operator lambda (+)
|
||||
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
|
||||
| Expr ':' Type // Type annotation 5 : int
|
||||
| Expr BinOp Expr // Binary operator x + y
|
||||
@@ -234,6 +235,7 @@ Path ::= Id // Record field
|
||||
|
||||
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
|
||||
| '|>'
|
||||
UnOp ::= '-' | '!'
|
||||
```
|
||||
|
||||
@@ -245,6 +247,7 @@ UnOp ::= '-' | '!'
|
||||
| `!` `&&` `\|\|` | logical operators
|
||||
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||
| `::` `++` | list operators
|
||||
| `\|>` | functional operators
|
||||
|
||||
## Operator precendences
|
||||
|
||||
@@ -261,3 +264,4 @@ In order of highest to lowest precedence.
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `&&` | right
|
||||
| `\|\|` | right
|
||||
| `\|>` | left
|
||||
|
||||
@@ -7,13 +7,13 @@ namespace BLS12_381 =
|
||||
record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp,
|
||||
x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }
|
||||
|
||||
function pairing_check(xs : list(g1), ys : list(g2)) =
|
||||
switch((xs, ys))
|
||||
function pairing_check(us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => true
|
||||
(x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys)
|
||||
|
||||
function pairing_check_(acc : gt, xs : list(g1), ys : list(g2)) =
|
||||
switch((xs, ys))
|
||||
function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => gt_is_one(acc)
|
||||
(x :: xs, y :: ys) =>
|
||||
pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys)
|
||||
|
||||
@@ -116,16 +116,6 @@ namespace Bitwise =
|
||||
1 => ubxor__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => ubxor__(a / 2, b / 2, val * 2, acc)
|
||||
|
||||
// Bitwise combined 'and' and 'not' of second argument for positive integers
|
||||
// x 'bnand' y = x 'band' ('bnot' y)
|
||||
// The tricky bit is that after negation the second argument has an infinite number of 1's
|
||||
// use as many as needed!
|
||||
//
|
||||
// NOTE: this function is not symmetric!
|
||||
private function ubnand(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
|
||||
ubnand__(a, b, 1, 0)
|
||||
|
||||
private function ubnand_(a, b) = ubnand__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Func =
|
||||
|
||||
function id(x : 'a) : 'a = x
|
||||
|
||||
function const(x : 'a) : 'b => 'a = (y) => x
|
||||
function const(x : 'a) : 'b => 'a = (_) => x
|
||||
|
||||
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace List =
|
||||
if (n == 0) l
|
||||
else switch(l)
|
||||
[] => []
|
||||
h::t => drop_(n-1, t)
|
||||
_::t => drop_(n-1, t)
|
||||
|
||||
/** Get the longest prefix of a list in which every element
|
||||
* matches predicate `p`
|
||||
@@ -191,7 +191,7 @@ namespace List =
|
||||
/** 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)) = switch(l)
|
||||
function partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst)
|
||||
[] => ([], [])
|
||||
h::t =>
|
||||
let (l, r) = partition(p, t)
|
||||
@@ -313,4 +313,4 @@ namespace List =
|
||||
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0)
|
||||
private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l)
|
||||
[] => []
|
||||
h::t => (n, h)::enumerate_(t, n + 1)
|
||||
h::t => (n, h)::enumerate_(t, n + 1)
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace ListInternal =
|
||||
|
||||
// -- Flatmap ----------------------------------------------------------------
|
||||
|
||||
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
|
||||
switch(xs)
|
||||
function flat_map(f : 'a => list('b), lst : list('a)) : list('b) =
|
||||
switch(lst)
|
||||
[] => []
|
||||
x :: xs => f(x) ++ flat_map(f, xs)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
include "List.aes"
|
||||
|
||||
namespace Option =
|
||||
|
||||
function is_none(o : option('a)) : bool = switch(o)
|
||||
|
||||
+16
-16
@@ -53,21 +53,21 @@ namespace String =
|
||||
|
||||
// Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string
|
||||
// into an integer. If the string doesn't contain a valid number `None` is returned.
|
||||
function to_int(s : string) : option(int) =
|
||||
let s = StringInternal.to_list(s)
|
||||
switch(is_prefix(['-'], s))
|
||||
None => to_int_pos(s)
|
||||
function to_int(str : string) : option(int) =
|
||||
let lst = StringInternal.to_list(str)
|
||||
switch(is_prefix(['-'], lst))
|
||||
None => to_int_pos(lst)
|
||||
Some(s) => switch(to_int_pos(s))
|
||||
None => None
|
||||
Some(x) => Some(-x)
|
||||
|
||||
// Private helper functions below
|
||||
private function to_int_pos(s : list(char)) =
|
||||
switch(is_prefix(['0', 'x'], s))
|
||||
private function to_int_pos(chs : list(char)) =
|
||||
switch(is_prefix(['0', 'x'], chs))
|
||||
None =>
|
||||
to_int_(s, ch_to_int_10, 0, 10)
|
||||
Some(s) =>
|
||||
to_int_(s, ch_to_int_16, 0, 16)
|
||||
to_int_(chs, ch_to_int_10, 0, 10)
|
||||
Some(str) =>
|
||||
to_int_(str, ch_to_int_16, 0, 16)
|
||||
|
||||
private function
|
||||
tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))]
|
||||
@@ -84,8 +84,8 @@ namespace String =
|
||||
contains_(ix, str, substr) =
|
||||
switch(is_prefix(substr, str))
|
||||
None =>
|
||||
let _ :: str = str
|
||||
contains_(ix + 1, str, substr)
|
||||
let _ :: tailstr = str
|
||||
contains_(ix + 1, tailstr, substr)
|
||||
Some(_) =>
|
||||
Some(ix)
|
||||
|
||||
@@ -101,15 +101,15 @@ namespace String =
|
||||
to_int_(i :: is, value, x, b) =
|
||||
switch(value(i))
|
||||
None => None
|
||||
Some(i) => to_int_(is, value, x * b + i, b)
|
||||
Some(n) => to_int_(is, value, x * b + n, b)
|
||||
|
||||
private function ch_to_int_10(c) =
|
||||
let c = Char.to_int(c)
|
||||
private function ch_to_int_10(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
else None
|
||||
|
||||
private function ch_to_int_16(c) =
|
||||
let c = Char.to_int(c)
|
||||
private function ch_to_int_16(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
elif(c >= 65 && c =< 70) Some(c - 55)
|
||||
elif(c >= 97 && c =< 102) Some(c - 87)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"0699f35"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"05dfd7ffc7fb1e07ecc0b1e516da571f56d7dc8f"}},
|
||||
{ref,"0699f35b0398bac6cd4468da654d608375bd853d"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+6
-4
@@ -70,7 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
|
||||
do_contract_interface(Type, ContractString, Options) ->
|
||||
try
|
||||
Ast = aeso_compiler:parse(ContractString, Options),
|
||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
{TypedAst, _, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
from_typed_ast(Type, TypedAst)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
@@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
|
||||
string -> do_render_aci_json(JArray)
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
C0 = #{name => encode_name(Name)},
|
||||
|
||||
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
||||
@@ -341,10 +341,12 @@ stateful(false) -> "".
|
||||
|
||||
%% #contract{Ann, Con, [Declarations]}.
|
||||
|
||||
contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_funcs({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_fun(D)].
|
||||
|
||||
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_types({namespace, _, _, Decls}) ->
|
||||
[ D || D <- Decls, is_type(D) ];
|
||||
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_type(D) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
|
||||
+887
-489
File diff suppressed because it is too large
Load Diff
@@ -75,6 +75,7 @@
|
||||
| {switch, fsplit()}
|
||||
| {set_state, state_reg(), fexpr()}
|
||||
| {get_state, state_reg()}
|
||||
| {loop, fexpr(), var_name(), fexpr()} | {continue, fexpr()} | {break, fexpr()}
|
||||
%% The following (unapplied top-level functions/builtins and
|
||||
%% lambdas) are generated by the fcode compiler, but translated
|
||||
%% to closures by the lambda lifter.
|
||||
@@ -326,7 +327,7 @@ get_option(Opt, Env, Default) ->
|
||||
%% -- Compilation ------------------------------------------------------------
|
||||
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, _Impls, Decls}|Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
@@ -363,7 +364,7 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
to_fcode(Env1, Rest)
|
||||
end;
|
||||
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
|
||||
fcode_error({last_declaration_must_be_contract_def, NotMain});
|
||||
fcode_error({last_declaration_must_be_main_contract, NotMain});
|
||||
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||
to_fcode(Env1, Code).
|
||||
@@ -651,11 +652,22 @@ expr_to_fcode(Env, {record_t, FieldTypes}, {record, _Ann, Rec, Fields}) ->
|
||||
expr_to_fcode(Env, _Type, {list, _, Es}) ->
|
||||
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
|
||||
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)]};
|
||||
|
||||
AV = fresh_name(), % var to keep B
|
||||
WithA = fun(X) -> {'let', AV, expr_to_fcode(Env, A), X} end,
|
||||
St = fresh_name(), % loop state
|
||||
ItProj = {proj, {var, St}, 1},
|
||||
AcProj = {proj, {var, St}, 0},
|
||||
Init = {tuple, [nil, expr_to_fcode(Env, B)]},
|
||||
Loop = {loop, Init, St,
|
||||
make_if(
|
||||
{op, '>=', [ItProj, {var, AV}]},
|
||||
{continue, {tuple, [{op, '::', [ItProj, AcProj]},
|
||||
{op, '-', [ItProj, {lit, {int, 1}}]}
|
||||
]}},
|
||||
{break, AcProj}
|
||||
)},
|
||||
WithA(Loop);
|
||||
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, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) ->
|
||||
@@ -703,8 +715,11 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
|
||||
expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' ->
|
||||
Tree = expr_to_decision_tree(Env, Expr),
|
||||
decision_tree_to_fcode(Tree);
|
||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
|
||||
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
|
||||
expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
|
||||
case Op of
|
||||
'|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]});
|
||||
_ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}
|
||||
end;
|
||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
||||
case Op of
|
||||
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
|
||||
@@ -1335,6 +1350,9 @@ lambda_lift_expr(Layout, Expr) ->
|
||||
{proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I};
|
||||
{set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)};
|
||||
{op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)};
|
||||
{loop, Init, I, Body} -> {loop, lambda_lift_expr(Layout, Init), I, lambda_lift_expr(Layout, Body)};
|
||||
{break, E} -> {break, lambda_lift_expr(Layout, E)};
|
||||
{continue, E} -> {continue, lambda_lift_expr(Layout, E)};
|
||||
{'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)};
|
||||
{funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)};
|
||||
{set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)};
|
||||
@@ -1650,6 +1668,9 @@ read_only({switch, Split}) -> read_only(Split);
|
||||
read_only({split, _, _, Cases}) -> read_only(Cases);
|
||||
read_only({nosplit, E}) -> read_only(E);
|
||||
read_only({'case', _, Split}) -> read_only(Split);
|
||||
read_only({loop, Init, _, Body}) -> read_only(Init) andalso read_only(Body);
|
||||
read_only({break, E}) -> read_only(E);
|
||||
read_only({continue, E}) -> read_only(E);
|
||||
read_only({'let', _, A, B}) -> read_only([A, B]);
|
||||
read_only({funcall, _, _}) -> false;
|
||||
read_only({closure, _, _}) -> internal_error(no_closures_here);
|
||||
@@ -1847,6 +1868,9 @@ free_vars(Expr) ->
|
||||
{proj, A, _} -> free_vars(A);
|
||||
{set_proj, A, _, B} -> free_vars([A, B]);
|
||||
{op, _, As} -> free_vars(As);
|
||||
{loop, Init, Var, Body} -> free_vars(Init) ++ (free_vars(Body) -- [Var]);
|
||||
{break, E} -> free_vars(E);
|
||||
{continue, E} -> free_vars(E);
|
||||
{'let', X, A, B} -> free_vars([A, {lam, [X], B}]);
|
||||
{funcall, A, Bs} -> free_vars([A | Bs]);
|
||||
{set_state, _, A} -> free_vars(A);
|
||||
@@ -1878,6 +1902,9 @@ used_defs(Expr) ->
|
||||
{proj, A, _} -> used_defs(A);
|
||||
{set_proj, A, _, B} -> used_defs([A, B]);
|
||||
{op, _, As} -> used_defs(As);
|
||||
{loop, I, _, B} -> used_defs(I) ++ used_defs(B);
|
||||
{break, E} -> used_defs(E);
|
||||
{continue, E} -> used_defs(E);
|
||||
{'let', _, A, B} -> used_defs([A, B]);
|
||||
{funcall, A, Bs} -> used_defs([A | Bs]);
|
||||
{set_state, _, A} -> used_defs(A);
|
||||
@@ -1914,6 +1941,9 @@ bottom_up(F, Env, Expr) ->
|
||||
{get_state, _} -> Expr;
|
||||
{closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)};
|
||||
{switch, Split} -> {switch, bottom_up(F, Env, Split)};
|
||||
{loop, Init, Var, Body} -> {loop, bottom_up(F, Env, Init), Var, bottom_up(F, Env, Body)};
|
||||
{break, E} -> {break, bottom_up(F, Env, E)};
|
||||
{continue, E} -> {continue, bottom_up(F, Env, E)};
|
||||
{lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)};
|
||||
{'let', X, E, Body} ->
|
||||
E1 = bottom_up(F, Env, E),
|
||||
@@ -1975,6 +2005,11 @@ rename(Ren, Expr) ->
|
||||
{lam, Xs, B} ->
|
||||
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
||||
{lam, Zs, rename(Ren1, B)};
|
||||
{loop, Init, Var, Body} ->
|
||||
{Z, Ren1} = rename_binding(Ren, Var),
|
||||
{loop, rename(Ren, Init), Z, rename(Ren1, Body)};
|
||||
{break, E} -> {break, rename(Ren, E)};
|
||||
{continue, E} -> {continue, rename(Ren, E)};
|
||||
{'let', X, E, Body} ->
|
||||
{Z, Ren1} = rename_binding(Ren, X),
|
||||
{'let', Z, rename(Ren, E), rename(Ren1, Body)}
|
||||
@@ -2166,6 +2201,13 @@ pp_fexpr({tuple, Es}) ->
|
||||
pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es])));
|
||||
pp_fexpr({proj, E, I}) ->
|
||||
pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]);
|
||||
pp_fexpr({loop, Init, Var, Body}) ->
|
||||
pp_par(
|
||||
[ pp_beside([pp_text("loop"), pp_fexpr(Init), pp_text("as"), pp_text(Var)])
|
||||
, pp_fexpr(Body)
|
||||
]);
|
||||
pp_fexpr({break, E}) -> pp_beside([pp_text("break"), pp_fexpr(E)]);
|
||||
pp_fexpr({continue, E}) -> pp_beside([pp_text("continue"), pp_fexpr(E)]);
|
||||
pp_fexpr({lam, Xs, A}) ->
|
||||
pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"),
|
||||
prettypr:nest(2, pp_fexpr(A))]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,684 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler builtin functions for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 20 Dec 2018
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_builtins).
|
||||
|
||||
-export([ builtin_function/1
|
||||
, bytes_to_raw_string/2
|
||||
, check_event_type/1
|
||||
, used_builtins/1 ]).
|
||||
|
||||
-import(aeso_ast_to_icode, [prim_call/5]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
|
||||
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
|
||||
used_builtins([H|T]) ->
|
||||
lists:umerge(used_builtins(H), used_builtins(T));
|
||||
used_builtins(T) when is_tuple(T) ->
|
||||
used_builtins(tuple_to_list(T));
|
||||
used_builtins(M) when is_map(M) ->
|
||||
used_builtins(maps:to_list(M));
|
||||
used_builtins(_) -> [].
|
||||
|
||||
builtin_deps(Builtin) ->
|
||||
lists:usort(builtin_deps1(Builtin)).
|
||||
|
||||
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1(map_member) -> [{map_lookup, word}];
|
||||
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
|
||||
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
|
||||
builtin_deps1(map_from_list) -> [map_put];
|
||||
builtin_deps1(str_equal) -> [str_equal_p];
|
||||
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
|
||||
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
|
||||
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
|
||||
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
|
||||
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
|
||||
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
|
||||
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
|
||||
builtin_deps1(string_reverse) -> [string_reverse_];
|
||||
builtin_deps1(require) -> [abort];
|
||||
builtin_deps1(_) -> [].
|
||||
|
||||
dep_closure(Deps) ->
|
||||
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
|
||||
[] -> Deps;
|
||||
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
|
||||
end.
|
||||
|
||||
%% Helper functions/macros
|
||||
v(X) when is_atom(X) -> v(atom_to_list(X));
|
||||
v(X) when is_list(X) -> #var_ref{name = X}.
|
||||
|
||||
option_none() -> {tuple, [{integer, 0}]}.
|
||||
option_some(X) -> {tuple, [{integer, 1}, X]}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
|
||||
-define(I(X), {integer, X}).
|
||||
-define(V(X), v(X)).
|
||||
-define(A(Op), aeb_opcodes:mnemonic(Op)).
|
||||
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
|
||||
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
|
||||
-define(NXT(Ptr), op('+', Ptr, 32)).
|
||||
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
|
||||
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
|
||||
|
||||
-define(EQ(A, B), op('==', A, B)).
|
||||
-define(LT(A, B), op('<', A, B)).
|
||||
-define(GT(A, B), op('>', A, B)).
|
||||
-define(ADD(A, B), op('+', A, B)).
|
||||
-define(SUB(A, B), op('-', A, B)).
|
||||
-define(MUL(A, B), op('*', A, B)).
|
||||
-define(DIV(A, B), op('div', A, B)).
|
||||
-define(MOD(A, B), op('mod', A, B)).
|
||||
-define(EXP(A, B), op('^', A, B)).
|
||||
-define(AND(A, B), op('&&', A, B)).
|
||||
|
||||
%% Bit shift operations takes their arguments backwards!?
|
||||
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
|
||||
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
|
||||
|
||||
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
|
||||
|
||||
%% We generate a lot of B * 8 for integer B from BSL and BSR.
|
||||
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
|
||||
{integer, A * B};
|
||||
simpl(Op) -> Op.
|
||||
|
||||
|
||||
operand(A) when is_atom(A) -> v(A);
|
||||
operand(I) when is_integer(I) -> {integer, I};
|
||||
operand(T) -> T.
|
||||
|
||||
check_event_type(Icode) ->
|
||||
case maps:get(event_type, Icode) of
|
||||
{variant_t, Cons} ->
|
||||
check_event_type(Cons, Icode);
|
||||
_ ->
|
||||
error({event_should_be_variant_type})
|
||||
end.
|
||||
|
||||
check_event_type(Evts, Icode) ->
|
||||
[ check_event_type(Name, Ix, T, Icode)
|
||||
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
|
||||
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
|
||||
|
||||
check_event_type(EvtName, Ix, Type, Icode) ->
|
||||
VMType =
|
||||
try
|
||||
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
||||
catch _:_ ->
|
||||
error({EvtName, could_not_resolve_type, Type})
|
||||
end,
|
||||
case {Ix, VMType, Type} of
|
||||
{indexed, word, _} -> ok;
|
||||
{notindexed, string, _} -> ok;
|
||||
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
|
||||
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
||||
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
|
||||
end.
|
||||
|
||||
bfun(B, {IArgs, IExpr, IRet}) ->
|
||||
{{builtin, B}, [private], IArgs, IExpr, IRet}.
|
||||
|
||||
builtin_function(BF) ->
|
||||
case BF of
|
||||
{event, EventT} -> bfun(BF, builtin_event(EventT));
|
||||
abort -> bfun(BF, builtin_abort());
|
||||
block_hash -> bfun(BF, builtin_block_hash());
|
||||
require -> bfun(BF, builtin_require());
|
||||
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
|
||||
map_put -> bfun(BF, builtin_map_put());
|
||||
map_delete -> bfun(BF, builtin_map_delete());
|
||||
map_size -> bfun(BF, builtin_map_size());
|
||||
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
|
||||
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
|
||||
map_member -> bfun(BF, builtin_map_member());
|
||||
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
|
||||
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
|
||||
map_from_list -> bfun(BF, builtin_map_from_list());
|
||||
list_concat -> bfun(BF, builtin_list_concat());
|
||||
string_length -> bfun(BF, builtin_string_length());
|
||||
string_concat -> bfun(BF, builtin_string_concat());
|
||||
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
|
||||
string_copy -> bfun(BF, builtin_string_copy());
|
||||
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
|
||||
str_equal_p -> bfun(BF, builtin_str_equal_p());
|
||||
str_equal -> bfun(BF, builtin_str_equal());
|
||||
popcount -> bfun(BF, builtin_popcount());
|
||||
int_to_str -> bfun(BF, builtin_int_to_str());
|
||||
addr_to_str -> bfun(BF, builtin_addr_to_str());
|
||||
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
|
||||
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
|
||||
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
|
||||
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
|
||||
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
|
||||
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
|
||||
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
|
||||
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
|
||||
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
|
||||
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
|
||||
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
|
||||
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
|
||||
string_reverse -> bfun(BF, builtin_string_reverse());
|
||||
string_reverse_ -> bfun(BF, builtin_string_reverse_())
|
||||
end.
|
||||
|
||||
%% Event primitive (dependent on Event type)
|
||||
%%
|
||||
%% We need to switch on the event and prepare the correct #event for icode_to_asm
|
||||
%% NOTE: we assume all errors are already checked!
|
||||
builtin_event(EventT) ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
||||
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
||||
Payload = %% Should put data ptr, length on stack.
|
||||
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
||||
([{{id, _, "string"}, V}]) ->
|
||||
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
||||
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
|
||||
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
|
||||
end,
|
||||
Ix =
|
||||
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
|
||||
(_, V) -> V end,
|
||||
Clause =
|
||||
fun(_Tag, {con, _, Con}, IxTypes) ->
|
||||
Types = [ T || {_Ix, T} <- IxTypes ],
|
||||
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
|
||||
EvtIndex = {integer, EvtIndexN},
|
||||
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
|
||||
end,
|
||||
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
|
||||
|
||||
{variant_t, Cons} = EventT,
|
||||
Tags = lists:seq(0, length(Cons) - 1),
|
||||
|
||||
{[{"e", event}],
|
||||
{switch, v(e),
|
||||
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|
||||
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Abort primitive.
|
||||
builtin_abort() ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
{[{"s", string}],
|
||||
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
|
||||
A(?REVERT)]}, %% Stack: 0,Ptr
|
||||
{tuple,[]}}.
|
||||
|
||||
builtin_block_hash() ->
|
||||
{[{"height", word}],
|
||||
?LET(hash, #prim_block_hash{ height = ?V(height)},
|
||||
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
|
||||
aeso_icode:option_typerep(word)}.
|
||||
|
||||
builtin_require() ->
|
||||
{[{"c", word}, {"msg", string}],
|
||||
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Map primitives
|
||||
builtin_map_lookup(Type) ->
|
||||
Ret = aeso_icode:option_typerep(Type),
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
|
||||
[#var_ref{name = "m"}, #var_ref{name = "k"}],
|
||||
[word, word], Ret),
|
||||
Ret}.
|
||||
|
||||
builtin_map_put() ->
|
||||
%% We don't need the types for put.
|
||||
{[{"m", word}, {"k", word}, {"v", word}],
|
||||
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
|
||||
[v(m), v(k), v(v)], [word, word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_delete() ->
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
|
||||
[v(m), v(k)], [word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_size() ->
|
||||
{[{"m", word}],
|
||||
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
|
||||
[v(m)], [word], word),
|
||||
word}.
|
||||
|
||||
%% Map builtins
|
||||
builtin_map_get(Type) ->
|
||||
%% function map_get(m, k) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_lookup_default(Type) ->
|
||||
%% function map_lookup_default(m, k, default) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% None => default
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}, {"default", Type}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
|
||||
[{option_none(), v(default)},
|
||||
{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_member() ->
|
||||
%% function map_member(m, k) : bool =
|
||||
%% switch(Map.lookup(m, k))
|
||||
%% None => false
|
||||
%% _ => true
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
|
||||
[{option_none(), {integer, 0}},
|
||||
{{var_ref, "_"}, {integer, 1}}]},
|
||||
word}.
|
||||
|
||||
builtin_map_upd(Type) ->
|
||||
%% function map_upd(map, key, fun) =
|
||||
%% map_put(map, key, fun(map_get(map, key)))
|
||||
{[{"map", word}, {"key", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_upd_default(Type) ->
|
||||
%% function map_upd(map, key, val, fun) =
|
||||
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
|
||||
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_from_list() ->
|
||||
%% function map_from_list(xs, acc) =
|
||||
%% switch(xs)
|
||||
%% [] => acc
|
||||
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
|
||||
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
|
||||
{switch, v(xs),
|
||||
[{{list, []}, v(acc)},
|
||||
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
|
||||
?call(map_from_list,
|
||||
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
|
||||
word}.
|
||||
|
||||
%% list_concat
|
||||
%%
|
||||
%% Concatenates two lists.
|
||||
builtin_list_concat() ->
|
||||
{[{"l1", {list, word}}, {"l2", {list, word}}],
|
||||
{switch, v(l1),
|
||||
[{{list, []}, v(l2)},
|
||||
{{binop, '::', v(hd), v(tl)},
|
||||
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
|
||||
]
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_string_length() ->
|
||||
%% function length(str) =
|
||||
%% switch(str)
|
||||
%% {n} -> n // (ab)use the representation
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s, ?V(n)),
|
||||
word}.
|
||||
|
||||
%% str_concat - concatenate two strings
|
||||
%%
|
||||
%% Unless the second string is the empty string, a new string is created at the
|
||||
%% top of the Heap and the address to it is returned. The tricky bit is when
|
||||
%% the words from the second string has to be shifted to fit next to the first
|
||||
%% string.
|
||||
builtin_string_concat() ->
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
{ifte, ?EQ(n1, 0),
|
||||
?V(s2), %% First string is empty return second string
|
||||
{ifte, ?EQ(n2, 0),
|
||||
?V(s1), %% Second string is empty return first string
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
|
||||
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
|
||||
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
|
||||
?V(ret) %% Put the actual return value
|
||||
]})}
|
||||
}
|
||||
)),
|
||||
word}.
|
||||
|
||||
builtin_string_concat_inner1() ->
|
||||
%% Copy all whole words from the first string, and set up for word fusion
|
||||
%% Special case when the length of the first string is divisible by 32.
|
||||
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
|
||||
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
|
||||
?LET(nx, ?MOD(n1, 32),
|
||||
{ifte, ?EQ(nx, 0),
|
||||
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
|
||||
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
|
||||
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
|
||||
})),
|
||||
word}.
|
||||
|
||||
builtin_string_copy() ->
|
||||
{[{"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{ifte, ?GT(n, 31),
|
||||
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
|
||||
?V(w)
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_string_shift_copy() ->
|
||||
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
{ifte, ?GT(n, ?SUB(32, off)),
|
||||
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
|
||||
{inline_asm, [?A(?MSIZE)]}}]
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_str_equal_p() ->
|
||||
%% function str_equal_p(n, p1, p2) =
|
||||
%% if(n =< 0) true
|
||||
%% else
|
||||
%% let w1 = *p1
|
||||
%% let w2 = *p2
|
||||
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
|
||||
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
|
||||
{ifte, ?LT(n, 1),
|
||||
?I(1),
|
||||
?DEREF(w1, p1,
|
||||
?DEREF(w2, p2,
|
||||
?AND(?EQ(w1, w2),
|
||||
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
|
||||
word}.
|
||||
|
||||
builtin_str_equal() ->
|
||||
%% function str_equal(s1, s2) =
|
||||
%% let n1 = length(s1)
|
||||
%% let n2 = length(s2)
|
||||
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
|
||||
)),
|
||||
word}.
|
||||
|
||||
%% Count the number of 1s in a bit field.
|
||||
builtin_popcount() ->
|
||||
%% function popcount(bits, acc) =
|
||||
%% if (bits == 0) acc
|
||||
%% else popcount(bits bsr 1, acc + bits band 1)
|
||||
{[{"bits", word}, {"acc", word}],
|
||||
{ifte, ?EQ(bits, 0),
|
||||
?V(acc),
|
||||
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
|
||||
}, word}.
|
||||
|
||||
builtin_int_to_str() ->
|
||||
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
|
||||
|
||||
builtin_baseX_tab(_X = 10) ->
|
||||
{[{"ix", word}], ?ADD($0, ix), word};
|
||||
builtin_baseX_tab(_X = 58) ->
|
||||
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
|
||||
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
|
||||
{[{"ix", word}],
|
||||
{ifte, ?LT(ix, 32),
|
||||
?BYTE(ix, Fst32),
|
||||
?BYTE(?SUB(ix, 32), Lst26)
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int(X) ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_pad(X = 10) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?LT(src, 0),
|
||||
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 16) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 58) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode(X) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
|
||||
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode_(X) ->
|
||||
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
|
||||
{ifte, ?EQ(fac, 0),
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
{ifte, ?EQ(ix, 32),
|
||||
%% We've filled a word, write it and start on new word
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
|
||||
?call({baseX_int_encode_, X},
|
||||
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
|
||||
?DIV(fac, X), ?ADD(ix, 1)])}
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_digits(X) ->
|
||||
{[{"x0", word}, {"dgts", word}],
|
||||
?LET(x1, ?DIV(x0, X),
|
||||
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_int(32) ->
|
||||
{[{"w", word}], ?V(w), word};
|
||||
builtin_bytes_to_int(N) when N < 32 ->
|
||||
{[{"w", word}], ?BSR(w, 32 - N), word};
|
||||
builtin_bytes_to_int(N) when N > 32 ->
|
||||
LastFullWord = N div 32 - 1,
|
||||
Body = case N rem 32 of
|
||||
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
|
||||
R ->
|
||||
?DEREF(hi, ?ADD(b, LastFullWord * 32),
|
||||
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
|
||||
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
|
||||
end,
|
||||
{[{"b", pointer}], Body, word}.
|
||||
|
||||
%% Two versions of this helper function, worker for sections not even 16 bytes long
|
||||
%% and worker_x for the full sized chunks.
|
||||
builtin_bytes_to_str_worker_x() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}],
|
||||
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_worker() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
|
||||
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_body(Var, N) when N < 16 ->
|
||||
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
|
||||
builtin_bytes_to_str_body(Var, 16) ->
|
||||
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N < 32 ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
|
||||
builtin_bytes_to_str_body(Var, 32) ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N > 32 ->
|
||||
WholeWords = ((N + 31) div 32) - 1,
|
||||
lists:append(
|
||||
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|
||||
|| I <- lists:seq(0, WholeWords - 1) ]) ++
|
||||
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
|
||||
|
||||
builtin_bytes_to_str(N) when N =< 32 ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(w, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string};
|
||||
builtin_bytes_to_str(N) when N > 32 ->
|
||||
{[{"p", pointer}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(p, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string}.
|
||||
|
||||
builtin_string_reverse() ->
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s,
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
|
||||
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
|
||||
word}.
|
||||
|
||||
builtin_string_reverse_() ->
|
||||
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
|
||||
{ifte, ?LT(i2, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
|
||||
?DEREF(w, p1,
|
||||
?LET(b, ?BYTE(?MOD(i2, 32), w),
|
||||
{ifte, ?LT(i1, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
|
||||
word}.
|
||||
|
||||
builtin_addr_to_str() ->
|
||||
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
|
||||
|
||||
%% At most one word
|
||||
%% | ..... | ========= | ........ |
|
||||
%% Offs ^ ^- Len -^ TotalLen ^
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
|
||||
%% Bytes are packed into a single word
|
||||
Masked =
|
||||
case Offs of
|
||||
0 -> Bytes;
|
||||
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
|
||||
end,
|
||||
Unpadded =
|
||||
case 32 - (Offs + Len) of
|
||||
0 -> Masked;
|
||||
N -> ?BSR(Masked, N)
|
||||
end,
|
||||
case Len of
|
||||
32 -> Unpadded;
|
||||
_ -> ?BSL(Unpadded, 32 - Len)
|
||||
end;
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
|
||||
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
|
||||
%% Might read one word more than necessary.
|
||||
Word = op('!', Offs, Bytes),
|
||||
case Len == 32 of
|
||||
true -> Word;
|
||||
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
|
||||
end.
|
||||
|
||||
builtin_bytes_concat(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Words = fun(N) -> (N + 31) div 32 end,
|
||||
WordsRes = Words(A + B),
|
||||
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
|
||||
(I) when 32 * I < A ->
|
||||
Len = A rem 32,
|
||||
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
|
||||
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
|
||||
?ADD(Hi, ?BSR(Lo, Len));
|
||||
(I) ->
|
||||
Offs = 32 * I - A,
|
||||
Len = min(32, B - Offs),
|
||||
bytes_slice(Offs, Len, B, ?V(b))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> ?V(b);
|
||||
{_, 0} -> ?V(a);
|
||||
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
|
||||
end,
|
||||
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
|
||||
|
||||
builtin_bytes_split(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Word = fun(I, Max) ->
|
||||
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> [?I(0), ?V(c)];
|
||||
{_, 0} -> [?V(c), ?I(0)];
|
||||
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
|
||||
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
|
||||
end,
|
||||
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
|
||||
|
||||
bytes_to_raw_string(N, Term) when N =< 32 ->
|
||||
{tuple, [?I(N), Term]};
|
||||
bytes_to_raw_string(N, Term) when N > 32 ->
|
||||
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
|
||||
end,
|
||||
Words = (N + 31) div 32,
|
||||
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
|
||||
|
||||
+10
-42
@@ -11,21 +11,21 @@
|
||||
-export([format/1, pos/1]).
|
||||
|
||||
format({last_declaration_must_be_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
|
||||
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'",
|
||||
[Kind, C]),
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
|
||||
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
|
||||
Msg = io_lib:format("Missing init function for the contract '~s'.", [pp_expr(Con)]),
|
||||
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.",
|
||||
mk_err(pos(Con), Msg, Cxt);
|
||||
format({missing_definition, Id}) ->
|
||||
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
|
||||
Msg = io_lib:format("Missing definition of function '~s'.", [pp_expr(Id)]),
|
||||
mk_err(pos(Id), Msg);
|
||||
format({parameterized_state, Decl}) ->
|
||||
Msg = "The state type cannot be parameterized.\n",
|
||||
Msg = "The state type cannot be parameterized.",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({parameterized_event, Decl}) ->
|
||||
Msg = "The event type cannot be parameterized.\n",
|
||||
Msg = "The event type cannot be parameterized.",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
|
||||
What = case Why of higher_order -> "higher-order (contains function types)";
|
||||
@@ -38,54 +38,22 @@ format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
|
||||
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
|
||||
{result, _} -> io_lib:format("is ~s", [What])
|
||||
end,
|
||||
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
|
||||
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.",
|
||||
[ThingS, Name, Bad]),
|
||||
case Why of
|
||||
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
|
||||
higher_order -> mk_err(pos(Ann), Msg)
|
||||
end;
|
||||
format({cant_compare_type_aevm, Ann, Op, Type}) ->
|
||||
StringAndTuple = [ "- type string\n"
|
||||
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
|
||||
Msg = io_lib:format("Cannot compare values of type\n"
|
||||
"~s\n"
|
||||
"The AEVM only supports '~s' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"~s",
|
||||
[pp_type(2, Type), Op, StringAndTuple]),
|
||||
Cxt = "Use FATE if you need to compare arbitrary types.\n",
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({invalid_aens_resolve_type, Ann, T}) ->
|
||||
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
|
||||
"~s\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).\n",
|
||||
"It must be a string or a pubkey type (address, oracle, etc).",
|
||||
[pp_type(2, T)]),
|
||||
mk_err(pos(Ann), Msg);
|
||||
format({unapplied_contract_call, Contract}) ->
|
||||
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
|
||||
"~s\n", [pp_expr(2, Contract)]),
|
||||
Cxt = "Use FATE if you need this.\n",
|
||||
mk_err(pos(Contract), Msg, Cxt);
|
||||
format({unapplied_builtin, Id}) ->
|
||||
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
|
||||
Cxt = "Use FATE if you need this.\n",
|
||||
mk_err(pos(Id), Msg, Cxt);
|
||||
format({invalid_map_key_type, Why, Ann, Type}) ->
|
||||
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
|
||||
Cxt = case Why of
|
||||
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
|
||||
function -> "Map keys cannot be higher-order.\n"
|
||||
end,
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
WhyS = case Why of higher_order -> "higher-order (contain function types)";
|
||||
polymorphic -> "polymorphic (contain type variables)" end,
|
||||
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
|
||||
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
|
||||
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
|
||||
Msg = io_lib:format("Invalid oracle type\n~s", [pp_type(2, Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s.", [What, WhyS]),
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({var_args_not_set, Expr}) ->
|
||||
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
|
||||
|
||||
+113
-343
@@ -2,7 +2,7 @@
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm.
|
||||
%%% Compiler from Aeterinty Sophia language to FATE.
|
||||
%%% @end
|
||||
%%% Created : 12 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
@@ -12,14 +12,13 @@
|
||||
, file/2
|
||||
, from_string/2
|
||||
, check_call/4
|
||||
, create_calldata/3 %% deprecated
|
||||
, create_calldata/3
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
, numeric_version/0
|
||||
, sophia_type_to_typerep/1
|
||||
, to_sophia_value/4 %% deprecated, need a backend
|
||||
, to_sophia_value/4
|
||||
, to_sophia_value/5
|
||||
, decode_calldata/3 %% deprecated
|
||||
, decode_calldata/3
|
||||
, decode_calldata/4
|
||||
, parse/2
|
||||
, add_include_path/2
|
||||
@@ -27,7 +26,6 @@
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
|
||||
@@ -35,13 +33,10 @@
|
||||
| pp_ast
|
||||
| pp_types
|
||||
| pp_typed_ast
|
||||
| pp_icode
|
||||
| pp_assembler
|
||||
| pp_bytecode
|
||||
| no_code
|
||||
| keep_included
|
||||
| debug_mode
|
||||
| {backend, aevm | fate}
|
||||
| {include, {file_system, [string()]} |
|
||||
{explicit_files, #{string() => binary()}}}
|
||||
| {src_file, string()}
|
||||
@@ -106,42 +101,22 @@ add_include_path(File, Options) ->
|
||||
end.
|
||||
|
||||
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
|
||||
from_string(Contract, Options) ->
|
||||
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
|
||||
|
||||
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(Backend, binary_to_list(ContractBin), Options);
|
||||
from_string(Backend, ContractString, Options) ->
|
||||
from_string(ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(binary_to_list(ContractBin), Options);
|
||||
from_string(ContractString, Options) ->
|
||||
try
|
||||
from_string1(Backend, ContractString, Options)
|
||||
from_string1(ContractString, Options)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
from_string1(aevm, ContractString, Options) ->
|
||||
#{ icode := Icode
|
||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||
TypeInfo = extract_type_info(Icode),
|
||||
Assembler = assemble(Icode, Options),
|
||||
pp_assembler(aevm, Assembler, Options),
|
||||
ByteCodeList = to_bytecode(Assembler, Options),
|
||||
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
|
||||
pp_bytecode(ByteCode, Options),
|
||||
{ok, Version} = version(),
|
||||
Res = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => TypeInfo,
|
||||
abi_version => aeb_aevm_abi:abi_version(),
|
||||
payable => maps:get(payable, Icode)
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||
from_string1(fate, ContractString, Options) ->
|
||||
from_string1(ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||
, folded_typed_ast := FoldedTypedAst
|
||||
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||
pp_assembler(fate, FateCode, Options),
|
||||
pp_assembler(FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
Res = #{byte_code => ByteCode,
|
||||
@@ -150,7 +125,8 @@ from_string1(fate, ContractString, Options) ->
|
||||
type_info => [],
|
||||
fate_code => FateCode,
|
||||
abi_version => aeb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode)
|
||||
payable => maps:get(payable, FCode),
|
||||
warnings => Warnings
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
||||
|
||||
@@ -168,29 +144,18 @@ string_to_code(ContractString, Options) ->
|
||||
Ast = parse(ContractString, Options),
|
||||
pp_sophia_code(Ast, Options),
|
||||
pp_ast(Ast, Options),
|
||||
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst, Warnings} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
pp_typed_ast(UnfoldedTypedAst, Options),
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = ast_to_icode(UnfoldedTypedAst, Options),
|
||||
pp_icode(Icode, Options),
|
||||
#{ icode => Icode
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast };
|
||||
fate ->
|
||||
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
||||
#{ fcode => Fcode
|
||||
, fcode_env => Env
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast }
|
||||
end.
|
||||
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
||||
#{ fcode => Fcode
|
||||
, fcode_env => Env
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast
|
||||
, warnings => Warnings }.
|
||||
|
||||
-define(CALL_NAME, "__call").
|
||||
-define(DECODE_NAME, "__decode").
|
||||
|
||||
%% Takes a string containing a contract with a declaration/prototype of a
|
||||
%% function (foo, say) and adds function __call() = foo(args) calling this
|
||||
@@ -198,10 +163,8 @@ string_to_code(ContractString, Options) ->
|
||||
%% terms for the arguments.
|
||||
%% NOTE: Special treatment for "init" since it might be implicit and has
|
||||
%% a special return type (typerep, T)
|
||||
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
|
||||
| {ok, string(), [term()]}
|
||||
| {error, [aeso_errors:error()]}
|
||||
when Type :: term().
|
||||
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), [term()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
check_call(Source, "init" = FunName, Args, Options) ->
|
||||
case check_call1(Source, FunName, Args, Options) of
|
||||
Err = {error, _} when Args == [] ->
|
||||
@@ -218,44 +181,20 @@ check_call(Source, FunName, Args, Options) ->
|
||||
|
||||
check_call1(ContractString0, FunName, Args, Options) ->
|
||||
try
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
%% First check the contract without the __call function
|
||||
#{ast := Ast} = string_to_code(ContractString0, Options),
|
||||
ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
|
||||
#{unfolded_typed_ast := TypedAst,
|
||||
icode := Icode} = string_to_code(ContractString, Options),
|
||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||
RetVMType = case RetType of
|
||||
{id, _, "_"} -> any;
|
||||
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
|
||||
end,
|
||||
#{ functions := Funs } = Icode,
|
||||
ArgIcode = get_arg_icode(Funs),
|
||||
ArgTerms = [ icode_to_term(T, Arg) ||
|
||||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
|
||||
RetVMType1 =
|
||||
case FunName of
|
||||
"init" -> {tuple, [typerep, RetVMType]};
|
||||
_ -> RetVMType
|
||||
end,
|
||||
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
|
||||
fate ->
|
||||
%% First check the contract without the __call function
|
||||
#{ fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast } = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
{ok, FunName, CallArgs}
|
||||
end
|
||||
%% First check the contract without the __call function
|
||||
#{fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast} = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
|
||||
{ok, FunName, CallArgs}
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
@@ -299,36 +238,25 @@ insert_init_function(Code, Options) ->
|
||||
|
||||
last_contract_indent(Decls) ->
|
||||
case lists:last(Decls) of
|
||||
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
{_, _, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
|
||||
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
to_sophia_value(_, _, error, Err, _Options) ->
|
||||
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||
to_sophia_value(_, _, revert, Data, Options) ->
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
case aeb_heap:from_binary(string, Data) of
|
||||
{ok, Err} ->
|
||||
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
||||
{error, _} ->
|
||||
Msg = "Could not interpret the revert message\n",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
try aeb_fate_encoding:deserialize(Data) of
|
||||
Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
|
||||
catch _:_ ->
|
||||
Msg = "Could not deserialize the revert message\n",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||
try aeso_vm_decode:from_fate({id, [], "string"}, aeb_fate_encoding:deserialize(Data)) of
|
||||
Err ->
|
||||
{ok, {app, [], {id, [], "abort"}, [Err]}}
|
||||
catch _:_ ->
|
||||
Msg = "Could not deserialize the revert message",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
@@ -338,74 +266,44 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = maps:get(icode, Code),
|
||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||
case aeb_heap:from_binary(VmType, Data) of
|
||||
{ok, VmValue} ->
|
||||
try
|
||||
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||
[Data, VmType, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _Err} ->
|
||||
Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
|
||||
[aeb_fate_encoding:deserialize(Data), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
|
||||
[aeb_fate_encoding:deserialize(Data), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
|
||||
-spec create_calldata(string(), string(), [string()]) ->
|
||||
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
{ok, binary()} | {error, [aeso_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args) ->
|
||||
create_calldata(Code, Fun, Args, [{backend, aevm}]).
|
||||
|
||||
create_calldata(Code, Fun, Args, []).
|
||||
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
|
||||
{ok, binary()} | {error, [aeso_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||
{error, _} = Err -> Err
|
||||
end;
|
||||
fate ->
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, FateArgs} ->
|
||||
aeb_fate_abi:create_calldata(FunName, FateArgs);
|
||||
{error, _} = Err -> Err
|
||||
end
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, FateArgs} ->
|
||||
aeb_fate_abi:create_calldata(FunName, FateArgs);
|
||||
{error, _} = Err -> Err
|
||||
end.
|
||||
|
||||
-spec decode_calldata(string(), string(), binary()) ->
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata) ->
|
||||
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
|
||||
|
||||
decode_calldata(ContractString, FunName, Calldata, []).
|
||||
-spec decode_calldata(string(), string(), binary(), options()) ->
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
@@ -418,75 +316,29 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
Type0 = {tuple_t, [], ArgTypes},
|
||||
%% user defined data types such as variants needed to match against
|
||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = maps:get(icode, Code),
|
||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
||||
{ok, {_, VmValue}} ->
|
||||
try
|
||||
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
|
||||
%% Values are Sophia expressions in AST format
|
||||
{ok, ArgTypes, Values}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||
[VmValue, VmType, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _Err} ->
|
||||
Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
|
||||
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||
{ok, FateArgs} ->
|
||||
try
|
||||
{tuple_t, [], ArgTypes1} = Type,
|
||||
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|
||||
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||
{ok, ArgTypes, AstArgs}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s",
|
||||
[FateArgs, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||
{ok, FateArgs} ->
|
||||
try
|
||||
{tuple_t, [], ArgTypes1} = Type,
|
||||
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|
||||
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||
{ok, ArgTypes, AstArgs}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
|
||||
[FateArgs, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _} ->
|
||||
Msg = io_lib:format("Failed to decode calldata binary\n", []),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
{error, _} ->
|
||||
Msg = io_lib:format("Failed to decode calldata binary", []),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
get_arg_icode(Funs) ->
|
||||
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||
[Args] -> Args;
|
||||
[] -> error_missing_call_function()
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, error_missing_call_function/0}).
|
||||
error_missing_call_function() ->
|
||||
Msg = "Internal error: missing '__call'-function",
|
||||
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
||||
|
||||
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case [ {lists:last(QFunName), FunType}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
[{guarded, _, [], {typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}}]} <- Defs ] of
|
||||
[Call] -> {ok, Call};
|
||||
[] -> error_missing_call_function()
|
||||
end;
|
||||
get_call_type([_ | Contracts]) ->
|
||||
%% The __call should be in the final contract
|
||||
get_call_type(Contracts).
|
||||
|
||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||
get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
|
||||
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
|
||||
(_) -> [] end,
|
||||
@@ -496,7 +348,7 @@ get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Cont
|
||||
case FunName of
|
||||
"init" -> {ok, [], {tuple_t, [], []}};
|
||||
_ ->
|
||||
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
|
||||
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
|
||||
Pos = aeso_code_errors:pos(Ann),
|
||||
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
|
||||
end
|
||||
@@ -505,87 +357,17 @@ get_decode_type(FunName, [_ | Contracts]) ->
|
||||
%% The __decode should be in the final contract
|
||||
get_decode_type(FunName, Contracts).
|
||||
|
||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||
%% consumed by aeb_heap:to_binary().
|
||||
icode_to_term(word, {integer, N}) -> N;
|
||||
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
|
||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||
Str;
|
||||
icode_to_term({list, T}, {list, Vs}) ->
|
||||
[ icode_to_term(T, V) || V <- Vs ];
|
||||
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
|
||||
list_to_tuple(icodes_to_terms(Ts, Vs));
|
||||
icode_to_term({variant, Cs}, {tuple, [{integer, Tag} | Args]}) ->
|
||||
Ts = lists:nth(Tag + 1, Cs),
|
||||
{variant, Tag, icodes_to_terms(Ts, Args)};
|
||||
icode_to_term(T = {map, KT, VT}, M) ->
|
||||
%% Maps are compiled to builtin and primop calls, so this gets a little hairy
|
||||
case M of
|
||||
{funcall, {var_ref, {builtin, map_put}}, [M1, K, V]} ->
|
||||
Map = icode_to_term(T, M1),
|
||||
Key = icode_to_term(KT, K),
|
||||
Val = icode_to_term(VT, V),
|
||||
Map#{ Key => Val };
|
||||
#prim_call_contract{ address = {integer, 0},
|
||||
arg = {tuple, [{integer, ?PRIM_CALL_MAP_EMPTY}, _, _]} } ->
|
||||
#{};
|
||||
_ -> throw({todo, M})
|
||||
end;
|
||||
icode_to_term(word, {unop, 'bnot', A}) ->
|
||||
bnot icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'bor', A, B}) ->
|
||||
icode_to_term(word, A) bor icode_to_term(word, B);
|
||||
icode_to_term(word, {binop, 'bsl', A, B}) ->
|
||||
icode_to_term(word, B) bsl icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'band', A, B}) ->
|
||||
icode_to_term(word, A) band icode_to_term(word, B);
|
||||
icode_to_term(typerep, _) ->
|
||||
throw({todo, typerep});
|
||||
icode_to_term(T, V) ->
|
||||
throw({not_a_value, T, V}).
|
||||
|
||||
icodes_to_terms(Ts, Vs) ->
|
||||
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
|
||||
|
||||
ast_to_icode(TypedAst, Options) ->
|
||||
aeso_ast_to_icode:convert_typed(TypedAst, Options).
|
||||
|
||||
assemble(Icode, Options) ->
|
||||
aeso_icode_to_asm:convert(Icode, Options).
|
||||
|
||||
|
||||
to_bytecode(['COMMENT',_|Rest],_Options) ->
|
||||
to_bytecode(Rest,_Options);
|
||||
to_bytecode([Op|Rest], Options) ->
|
||||
[aeb_opcodes:m_to_op(Op)|to_bytecode(Rest, Options)];
|
||||
to_bytecode([], _) -> [].
|
||||
|
||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||
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)),
|
||||
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|
||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||
not is_tuple(Name),
|
||||
not lists:member(private, Attrs)
|
||||
],
|
||||
lists:sort(TypeInfo).
|
||||
|
||||
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
|
||||
io:format("~s\n", [prettypr:format(aeso_pretty:decls(Code))])
|
||||
end).
|
||||
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
|
||||
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
|
||||
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
|
||||
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
|
||||
|
||||
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
|
||||
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
|
||||
pp_assembler(C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
|
||||
|
||||
pp(Code, Options, Option, PPFun) ->
|
||||
case proplists:lookup(Option, Options) of
|
||||
{Option, true} ->
|
||||
{Option1, true} when Option1 =:= Option ->
|
||||
PPFun(Code);
|
||||
none ->
|
||||
ok
|
||||
@@ -598,31 +380,27 @@ pp(Code, Options, Option, PPFun) ->
|
||||
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
|
||||
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
|
||||
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
|
||||
fate ->
|
||||
try
|
||||
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||
{FCode2, SrcPayable} =
|
||||
?protect(compile,
|
||||
begin
|
||||
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||
from_string1(fate, Source, Options),
|
||||
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||
end),
|
||||
case compare_fate_code(FCode1, FCode2) of
|
||||
ok when SrcPayable /= Payable ->
|
||||
Not = fun(true) -> ""; (false) -> " not" end,
|
||||
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||
[Not(Payable), Not(SrcPayable)]));
|
||||
ok -> ok;
|
||||
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||
end
|
||||
catch
|
||||
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||
end
|
||||
try
|
||||
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||
{FCode2, SrcPayable} =
|
||||
?protect(compile,
|
||||
begin
|
||||
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||
from_string1(Source, Options),
|
||||
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||
end),
|
||||
case compare_fate_code(FCode1, FCode2) of
|
||||
ok when SrcPayable /= Payable ->
|
||||
Not = fun(true) -> ""; (false) -> " not" end,
|
||||
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||
[Not(Payable), Not(SrcPayable)]));
|
||||
ok -> ok;
|
||||
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||
end
|
||||
catch
|
||||
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||
end.
|
||||
|
||||
compare_fate_code(FCode1, FCode2) ->
|
||||
@@ -674,14 +452,6 @@ pp_fate_type(T) -> io_lib:format("~w", [T]).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}.
|
||||
sophia_type_to_typerep(String) ->
|
||||
Ast = aeso_parser:run_parser(aeso_parser:type(), String),
|
||||
try aeso_ast_to_icode:ast_typerep(Ast) of
|
||||
Type -> {ok, Type}
|
||||
catch _:_ -> {error, bad_type}
|
||||
end.
|
||||
|
||||
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
|
||||
parse(Text, Options) ->
|
||||
parse(Text, sets:new(), Options).
|
||||
|
||||
+14
-3
@@ -30,12 +30,15 @@
|
||||
|
||||
-export([ err_msg/1
|
||||
, msg/1
|
||||
, msg_oneline/1
|
||||
, new/2
|
||||
, new/3
|
||||
, new/4
|
||||
, pos/2
|
||||
, pos/3
|
||||
, pp/1
|
||||
, pp_oneline/1
|
||||
, pp_pos/1
|
||||
, to_json/1
|
||||
, throw/1
|
||||
, type/1
|
||||
@@ -65,10 +68,13 @@ throw(#err{} = Err) ->
|
||||
erlang:throw({error, [Err]}).
|
||||
|
||||
msg(#err{ message = Msg, context = none }) -> Msg;
|
||||
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
|
||||
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ "\n" ++ Ctxt.
|
||||
|
||||
msg_oneline(#err{ message = Msg, context = none }) -> Msg;
|
||||
msg_oneline(#err{ message = Msg, context = Ctxt }) -> Msg ++ " - " ++ Ctxt.
|
||||
|
||||
err_msg(#err{ pos = Pos } = Err) ->
|
||||
lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])).
|
||||
lists:flatten(io_lib:format("~s~s\n", [str_pos(Pos), msg(Err)])).
|
||||
|
||||
str_pos(#pos{file = no_file, line = L, col = C}) ->
|
||||
io_lib:format("~p:~p:", [L, C]);
|
||||
@@ -78,7 +84,12 @@ str_pos(#pos{file = F, line = L, col = C}) ->
|
||||
type(#err{ type = Type }) -> Type.
|
||||
|
||||
pp(#err{ type = Kind, pos = Pos } = Err) ->
|
||||
lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
|
||||
lists:flatten(io_lib:format("~s~s:\n~s\n", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
|
||||
|
||||
pp_oneline(#err{ type = Kind, pos = Pos } = Err) ->
|
||||
Msg = msg_oneline(Err),
|
||||
OneLineMsg = re:replace(Msg, "[\s\\n]+", " ", [global]),
|
||||
lists:flatten(io_lib:format("~s~s: ~s", [pp_kind(Kind), pp_pos(Pos), OneLineMsg])).
|
||||
|
||||
pp_kind(type_error) -> "Type error";
|
||||
pp_kind(parse_error) -> "Parse error";
|
||||
|
||||
+90
-26
@@ -19,6 +19,7 @@
|
||||
|
||||
-type scode() :: [sinstr()].
|
||||
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
||||
| {loop, scode(), var(), scode(), reference(), reference()}
|
||||
| switch_body
|
||||
| loop
|
||||
| tuple() | atom(). %% FATE instruction
|
||||
@@ -45,7 +46,7 @@
|
||||
-define(s(N), {store, N}).
|
||||
-define(void, {var, 9999}).
|
||||
|
||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||
-record(env, { contract, vars = [], locals = [], break_ref = none, cont_ref = none, loop_it = none, current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
|
||||
@@ -159,6 +160,9 @@ init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
||||
|
||||
bind_loop(ContRef, BreakRef, It, Env) ->
|
||||
Env#env{break_ref = BreakRef, cont_ref = ContRef, loop_it = It}.
|
||||
|
||||
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
|
||||
Env#env{ vars = [{Name, Var} | Vars] }.
|
||||
|
||||
@@ -176,18 +180,14 @@ lookup_var(#env{vars = Vars}, X) ->
|
||||
|
||||
%% -- The compiler --
|
||||
|
||||
lit_to_fate(Env, L) ->
|
||||
case L of
|
||||
{int, N} -> aeb_fate_data:make_integer(N);
|
||||
{string, S} -> aeb_fate_data:make_string(S);
|
||||
{bytes, B} -> aeb_fate_data:make_bytes(B);
|
||||
{bool, B} -> aeb_fate_data:make_boolean(B);
|
||||
{account_pubkey, K} -> aeb_fate_data:make_address(K);
|
||||
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
|
||||
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
|
||||
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
|
||||
{contract_code, C} ->
|
||||
Options = Env#env.options,
|
||||
serialize_contract_code(Env, C) ->
|
||||
Cache = case get(contract_code_cache) of
|
||||
undefined -> put(contract_code_cache, #{}), #{};
|
||||
Res -> Res
|
||||
end,
|
||||
case maps:get(C, Cache, none) of
|
||||
none ->
|
||||
Options = Env#env.options,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
FateCode = compile(Env#env.child_contracts, FCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
@@ -201,7 +201,22 @@ lit_to_fate(Env, L) ->
|
||||
payable => maps:get(payable, FCode)
|
||||
},
|
||||
Serialized = aeser_contract_code:serialize(Code),
|
||||
aeb_fate_data:make_contract_bytearray(Serialized);
|
||||
put(contract_code_cache, maps:put(C, Serialized, Cache)),
|
||||
Serialized;
|
||||
Serialized -> Serialized
|
||||
end.
|
||||
|
||||
lit_to_fate(Env, L) ->
|
||||
case L of
|
||||
{int, N} -> aeb_fate_data:make_integer(N);
|
||||
{string, S} -> aeb_fate_data:make_string(S);
|
||||
{bytes, B} -> aeb_fate_data:make_bytes(B);
|
||||
{bool, B} -> aeb_fate_data:make_boolean(B);
|
||||
{account_pubkey, K} -> aeb_fate_data:make_address(K);
|
||||
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
|
||||
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
|
||||
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
|
||||
{contract_code, C} -> aeb_fate_data:make_contract_bytearray(serialize_contract_code(Env, C));
|
||||
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
|
||||
end.
|
||||
|
||||
@@ -357,7 +372,21 @@ to_scode1(Env, {set_state, Reg, Val}) ->
|
||||
|
||||
to_scode1(Env, {closure, Fun, FVs}) ->
|
||||
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
|
||||
|
||||
to_scode1(Env, {loop, Init, It, Expr}) ->
|
||||
ContRef = make_ref(),
|
||||
BreakRef = make_ref(),
|
||||
{ItV, Env1} = bind_local(It, Env),
|
||||
InitS = [to_scode(notail(Env), Init),
|
||||
{jump, ContRef}],
|
||||
ExprS = [aeb_fate_ops:store({var, ItV}, {stack, 0}),
|
||||
to_scode(bind_loop(ContRef, BreakRef, ItV, Env1), Expr),
|
||||
{jump, BreakRef}],
|
||||
[{loop, InitS, It, ExprS, ContRef, BreakRef}];
|
||||
to_scode1(Env = #env{cont_ref = ContRef}, {continue, Expr}) ->
|
||||
[to_scode1(notail(Env), Expr),
|
||||
{jump, ContRef}];
|
||||
to_scode1(Env, {break, Expr}) ->
|
||||
to_scode1(Env, Expr);
|
||||
to_scode1(Env, {switch, Case}) ->
|
||||
split_to_scode(Env, Case).
|
||||
|
||||
@@ -701,6 +730,8 @@ flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
||||
|
||||
flatten_s({switch, Arg, Type, Alts, Catch}) ->
|
||||
{switch, Arg, Type, [flatten(Alt) || Alt <- Alts], flatten(Catch)};
|
||||
flatten_s({loop, Init, It, Body, BRef, CRef}) ->
|
||||
{loop, flatten(Init), It, flatten(Body), BRef, CRef};
|
||||
flatten_s(I) -> I.
|
||||
|
||||
-define(MAX_SIMPL_ITERATIONS, 10).
|
||||
@@ -797,6 +828,11 @@ ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) ->
|
||||
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
|
||||
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
|
||||
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
|
||||
ann_live1(LiveTop, {loop, Init, It, Body, BRef, CRef}, LiveOut) ->
|
||||
{Init1, LiveInit} = ann_live(LiveTop, Init, LiveOut),
|
||||
{Body1, LiveBody} = ann_live(LiveTop, Body, LiveOut),
|
||||
LiveIn = ordsets:union([It, LiveInit, LiveBody]), % TODO not sure about this
|
||||
{{loop, Init1, It, Body1, BRef, CRef}, LiveIn};
|
||||
ann_live1(_LiveTop, I, LiveOut) ->
|
||||
#{ read := Reads0, write := W } = attributes(I),
|
||||
Reads = lists:filter(fun is_reg/1, Reads0),
|
||||
@@ -823,6 +859,7 @@ attributes(I) ->
|
||||
case I of
|
||||
loop -> Impure(pc, []);
|
||||
switch_body -> Pure(none, []);
|
||||
{jump, _} -> Impure(pc, []);
|
||||
'RETURN' -> Impure(pc, []);
|
||||
{'RETURNR', A} -> Impure(pc, A);
|
||||
{'CALL', A} -> Impure(?a, [A]);
|
||||
@@ -1012,6 +1049,7 @@ var_writes(I) ->
|
||||
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
||||
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
||||
independent(_, {switch, _, _, _, _}) -> false;
|
||||
independent(_, {loop, _, _, _, _, _}) -> false;
|
||||
independent({i, _, I}, {i, _, J}) ->
|
||||
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
||||
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
||||
@@ -1050,6 +1088,8 @@ live_in(R, {i, Ann, _}) -> live_in(R, Ann);
|
||||
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
|
||||
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
|
||||
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
||||
live_in(R, [{loop, Init, Var, Expr, _, _}]) ->
|
||||
live_in(Var, Init) orelse (R /= Var andalso live_in(R, Expr));
|
||||
live_in(_, missing) -> false;
|
||||
live_in(_, []) -> false.
|
||||
|
||||
@@ -1065,6 +1105,8 @@ simplify([I | Code], Options) ->
|
||||
|
||||
simpl_s({switch, Arg, Type, Alts, Def}, Options) ->
|
||||
{switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)};
|
||||
simpl_s({loop, Init, Var, Expr, ContRef, BreakRef}, Options) ->
|
||||
{loop, simplify(Init, Options), Var, simplify(Expr, Options), ContRef, BreakRef};
|
||||
simpl_s(I, _) -> I.
|
||||
|
||||
%% Safe-guard against loops in the rewriting. Shouldn't happen so throw an
|
||||
@@ -1367,6 +1409,8 @@ does_abort({i, _, {'EXIT', _}}) -> true;
|
||||
does_abort(missing) -> true;
|
||||
does_abort({switch, _, _, Alts, Def}) ->
|
||||
lists:all(fun does_abort/1, [Def | Alts]);
|
||||
does_abort({loop, Init, _, Expr, _, _}) ->
|
||||
does_abort(Init) orelse does_abort(Expr);
|
||||
does_abort(_) -> false.
|
||||
|
||||
%% STORE R A, SWITCH R --> SWITCH A
|
||||
@@ -1494,6 +1538,8 @@ from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
|
||||
(missing) -> missing.
|
||||
unannotate({switch, Arg, Type, Alts, Def}) ->
|
||||
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
||||
unannotate({loop, Init, It, Body, BRef, CRef}) ->
|
||||
[{loop, unannotate(Init), It, unannotate(Body), BRef, CRef}];
|
||||
unannotate(missing) -> missing;
|
||||
unannotate(Code) when is_list(Code) ->
|
||||
lists:flatmap(fun unannotate/1, Code);
|
||||
@@ -1510,6 +1556,8 @@ desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
|
||||
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
|
||||
desugar({switch, Arg, Type, Alts, Def}) ->
|
||||
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
||||
desugar({loop, Init, Var, Expr, ContRef, BreakRef}) ->
|
||||
[{loop, desugar(Init), Var, desugar(Expr), ContRef, BreakRef}];
|
||||
desugar(missing) -> missing;
|
||||
desugar(Code) when is_list(Code) ->
|
||||
lists:flatmap(fun desugar/1, Code);
|
||||
@@ -1556,7 +1604,7 @@ bb(_Name, Code) ->
|
||||
-type bb() :: {bbref(), bcode()}.
|
||||
-type bcode() :: [binstr()].
|
||||
-type binstr() :: {jump, bbref()}
|
||||
| {jumpif, bbref()}
|
||||
| {jumpif, term(), bbref()}
|
||||
| tuple(). %% FATE instruction
|
||||
|
||||
-spec blocks(scode()) -> [bb()].
|
||||
@@ -1570,25 +1618,29 @@ blocks([], Acc) ->
|
||||
blocks([Blk | Blocks], Acc) ->
|
||||
block(Blk, [], Blocks, Acc).
|
||||
|
||||
fresh_block(C, Ca) ->
|
||||
R = make_ref(),
|
||||
{R, [#blk{ref = R, code = C, catchall = Ca}]}.
|
||||
|
||||
-spec block(#blk{}, bcode(), [#blk{}], [bb()]) -> [bb()].
|
||||
block(#blk{ref = Ref, code = []}, CodeAcc, Blocks, BlockAcc) ->
|
||||
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
|
||||
block(Blk = #blk{code = [{loop, Init, _, Expr, ContRef, BreakRef} | Code], catchall = Catchall}, Acc, Blocks, BlockAcc) ->
|
||||
LoopBlock = #blk{ref = ContRef, code = Expr, catchall = none},
|
||||
BreakBlock = #blk{ref = BreakRef, code = Code, catchall = Catchall},
|
||||
block(Blk#blk{code = Init}, Acc, [LoopBlock, BreakBlock | Blocks], BlockAcc);
|
||||
block(Blk = #blk{code = [switch_body | Code]}, Acc, Blocks, BlockAcc) ->
|
||||
%% Reached the body of a switch. Clear catchall ref.
|
||||
block(Blk#blk{code = Code, catchall = none}, Acc, Blocks, BlockAcc);
|
||||
block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
catchall = Catchall}, Acc, Blocks, BlockAcc) ->
|
||||
FreshBlk = fun(C, Ca) ->
|
||||
R = make_ref(),
|
||||
{R, [#blk{ref = R, code = C, catchall = Ca}]}
|
||||
end,
|
||||
{RestRef, RestBlk} = FreshBlk(Code, Catchall),
|
||||
{RestRef, RestBlk} = fresh_block(Code, Catchall),
|
||||
{DefRef, DefBlk} =
|
||||
case Default of
|
||||
missing when Catchall == none ->
|
||||
FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
||||
fresh_block([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
||||
missing -> {Catchall, []};
|
||||
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
|
||||
_ -> fresh_block(Default ++ [{jump, RestRef}], Catchall)
|
||||
%% ^ fall-through to the outer catchall
|
||||
end,
|
||||
%% If we don't generate a switch, we need to pop the argument if on the stack.
|
||||
@@ -1600,7 +1652,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
{ThenRef, ThenBlk} =
|
||||
case TrueCode of
|
||||
missing -> {DefRef, []};
|
||||
_ -> FreshBlk(TrueCode ++ [{jump, RestRef}], DefRef)
|
||||
_ -> fresh_block(TrueCode ++ [{jump, RestRef}], DefRef)
|
||||
end,
|
||||
ElseCode =
|
||||
case FalseCode of
|
||||
@@ -1635,7 +1687,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||
false ->
|
||||
MkBlk = fun(missing) -> {DefRef, []};
|
||||
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
|
||||
(ACode) -> fresh_block(ACode ++ [{jump, RestRef}], DefRef)
|
||||
end,
|
||||
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
||||
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
||||
@@ -1651,7 +1703,7 @@ block(Blk = #blk{code = [I | Code]}, Acc, Blocks, BlockAcc) ->
|
||||
optimize_blocks(Blocks) ->
|
||||
%% We need to look at the last instruction a lot, so reverse all blocks.
|
||||
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
|
||||
RBlocks = Rev(Blocks),
|
||||
RBlocks = [{Ref, crop_jumps(Code)} || {Ref, Code} <- Blocks],
|
||||
RBlockMap = maps:from_list(RBlocks),
|
||||
RBlocks1 = reorder_blocks(RBlocks, []),
|
||||
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
||||
@@ -1733,6 +1785,18 @@ tweak_returns(['RETURN' | Code = [{'EXIT', _} | _]]) -> Code;
|
||||
tweak_returns(['RETURN' | Code = [loop | _]]) -> Code;
|
||||
tweak_returns(Code) -> Code.
|
||||
|
||||
%% -- Remove instructions that appear after jumps. Returns reversed code.
|
||||
%% This is useful for example when bb emitter adds continuation jumps
|
||||
%% for switch expressions, but some of the branches
|
||||
crop_jumps(Code) ->
|
||||
crop_jumps(Code, []).
|
||||
crop_jumps([], Acc) ->
|
||||
Acc;
|
||||
crop_jumps([I = {jump, _}|_], Acc) ->
|
||||
[I|Acc];
|
||||
crop_jumps([I|Code], Acc) ->
|
||||
crop_jumps(Code, [I|Acc]).
|
||||
|
||||
%% -- Split basic blocks at CALL instructions --
|
||||
%% Calls can only return to a new basic block. Also splits at JUMPIF instructions.
|
||||
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Intermediate Code for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode).
|
||||
|
||||
-export([new/1,
|
||||
pp/1,
|
||||
set_name/2,
|
||||
set_namespace/2,
|
||||
set_payable/2,
|
||||
enter_namespace/2,
|
||||
get_namespace/1,
|
||||
in_main_contract/1,
|
||||
qualify/2,
|
||||
set_functions/2,
|
||||
map_typerep/2,
|
||||
option_typerep/1,
|
||||
get_constructor_tag/2]).
|
||||
|
||||
-export_type([icode/0]).
|
||||
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
|
||||
|
||||
-type bindings() :: any().
|
||||
-type fun_dec() :: { string()
|
||||
, [modifier()]
|
||||
, arg_list()
|
||||
, expr()
|
||||
, aeb_aevm_data:type()}.
|
||||
|
||||
-type modifier() :: private | stateful.
|
||||
|
||||
-type type_name() :: string() | [string()].
|
||||
|
||||
-type icode() :: #{ contract_name => string()
|
||||
, functions => [fun_dec()]
|
||||
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||
, env => [bindings()]
|
||||
, state_type => aeb_aevm_data:type()
|
||||
, event_type => aeb_aevm_data:type()
|
||||
, types => #{ type_name() => type_def() }
|
||||
, type_vars => #{ string() => aeb_aevm_data:type() }
|
||||
, constructors => #{ [string()] => integer() } %% name to tag
|
||||
, options => [any()]
|
||||
, payable => boolean()
|
||||
}.
|
||||
|
||||
pp(Icode) ->
|
||||
%% TODO: Actually do *Pretty* printing.
|
||||
io:format("~p~n", [Icode]).
|
||||
|
||||
-spec new([any()]) -> icode().
|
||||
new(Options) ->
|
||||
#{ contract_name => ""
|
||||
, functions => []
|
||||
, env => new_env()
|
||||
%% Default to unit type for state and event
|
||||
, state_type => {tuple, []}
|
||||
, event_type => {tuple, []}
|
||||
, types => builtin_types()
|
||||
, type_vars => #{}
|
||||
, constructors => builtin_constructors()
|
||||
, options => Options
|
||||
, payable => false }.
|
||||
|
||||
builtin_types() ->
|
||||
Word = fun([]) -> word end,
|
||||
#{ "bool" => Word
|
||||
, "int" => Word
|
||||
, "char" => Word
|
||||
, "bits" => Word
|
||||
, "string" => fun([]) -> string end
|
||||
, "address" => Word
|
||||
, "hash" => Word
|
||||
, "unit" => fun([]) -> {tuple, []} end
|
||||
, "signature" => fun([]) -> {tuple, [word, word]} end
|
||||
, "oracle" => fun([_, _]) -> word end
|
||||
, "oracle_query" => fun([_, _]) -> word end
|
||||
, "list" => fun([A]) -> {list, A} end
|
||||
, "option" => fun([A]) -> {variant, [[], [A]]} end
|
||||
, "map" => fun([K, V]) -> map_typerep(K, V) end
|
||||
, ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end
|
||||
, ["AENS", "pointee"] => fun([]) -> {variant, [[word], [word], [word]]} end
|
||||
}.
|
||||
|
||||
builtin_constructors() ->
|
||||
#{ ["RelativeTTL"] => 0
|
||||
, ["FixedTTL"] => 1
|
||||
, ["None"] => 0
|
||||
, ["Some"] => 1
|
||||
, ["AccountPointee"] => 0
|
||||
, ["OraclePointee"] => 1
|
||||
, ["ContractPointee"] => 2
|
||||
}.
|
||||
|
||||
map_typerep(K, V) ->
|
||||
{map, K, V}.
|
||||
|
||||
option_typerep(A) ->
|
||||
{variant, [[], [A]]}.
|
||||
|
||||
new_env() ->
|
||||
[].
|
||||
|
||||
-spec set_name(string(), icode()) -> icode().
|
||||
set_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().
|
||||
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
||||
|
||||
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
|
||||
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
|
||||
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
|
||||
enter_namespace(NS, Icode) ->
|
||||
Icode#{ namespace => NS }.
|
||||
|
||||
-spec in_main_contract(icode()) -> boolean().
|
||||
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
|
||||
in_main_contract(_Icode) -> false.
|
||||
|
||||
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
get_namespace(Icode) -> maps:get(namespace, Icode, false).
|
||||
|
||||
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
qualify(X, Icode) ->
|
||||
case get_namespace(Icode) of
|
||||
false -> X;
|
||||
NS -> aeso_syntax:qualify(NS, X)
|
||||
end.
|
||||
|
||||
-spec set_functions([fun_dec()], icode()) -> icode().
|
||||
set_functions(NewFuns, Icode) ->
|
||||
maps:put(functions, NewFuns, Icode).
|
||||
|
||||
-spec get_constructor_tag([string()], icode()) -> integer().
|
||||
get_constructor_tag(Name, #{constructors := Constructors}) ->
|
||||
case maps:get(Name, Constructors, undefined) of
|
||||
undefined -> error({undefined_constructor, Name});
|
||||
Tag -> Tag
|
||||
end.
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
|
||||
|
||||
-record(arg, {name::string(), type::?Type()}).
|
||||
|
||||
-type expr() :: term().
|
||||
-type arg() :: #arg{name::string(), type::?Type()}.
|
||||
-type arg_list() :: [arg()].
|
||||
|
||||
-record(fun_dec, { name :: string()
|
||||
, args :: arg_list()
|
||||
, body :: expr()}).
|
||||
|
||||
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
|
||||
|
||||
-record(prim_call_contract,
|
||||
{ gas :: expr()
|
||||
, address :: expr()
|
||||
, value :: expr()
|
||||
, arg :: expr()
|
||||
, type_hash:: expr()
|
||||
}).
|
||||
|
||||
-record(prim_balance, { address :: expr() }).
|
||||
-record(prim_block_hash, { height :: expr() }).
|
||||
-record(prim_put, { state :: expr() }).
|
||||
|
||||
-record(integer, {value :: integer()}).
|
||||
|
||||
-record(tuple, {cpts :: [expr()]}).
|
||||
|
||||
-record(list, {elems :: [expr()]}).
|
||||
|
||||
-record(unop, { op :: term()
|
||||
, rand :: expr()}).
|
||||
|
||||
-record(binop, { op :: term()
|
||||
, left :: expr()
|
||||
, right :: expr()}).
|
||||
|
||||
-record(ifte, { decision :: expr()
|
||||
, then :: expr()
|
||||
, else :: expr()}).
|
||||
|
||||
-record(switch, { expr :: expr()
|
||||
, cases :: [{expr(),expr()}]}).
|
||||
|
||||
-record(funcall, { function :: expr()
|
||||
, args :: [expr()]}).
|
||||
|
||||
-record(lambda, { args :: arg_list(),
|
||||
body :: expr()}).
|
||||
|
||||
-record(missing_field, { format :: string()
|
||||
, args :: [term()]}).
|
||||
|
||||
-record(seq, {exprs :: [expr()]}).
|
||||
|
||||
-record(event, {topics :: [expr()], payload :: expr()}).
|
||||
@@ -1,983 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Translator from Aesophia Icode to Aevm Assebly
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode_to_asm).
|
||||
|
||||
-export([convert/2]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
i(Code) -> aeb_opcodes:mnemonic(Code).
|
||||
|
||||
%% We don't track purity or statefulness in the type checker yet.
|
||||
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
|
||||
|
||||
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
|
||||
|
||||
convert(#{ contract_name := _ContractName
|
||||
, state_type := StateType
|
||||
, functions := Functions
|
||||
},
|
||||
_Options) ->
|
||||
%% Create a function dispatcher
|
||||
DispatchFun = {"%main", [], [{"arg", "_"}],
|
||||
{switch, {var_ref, "arg"},
|
||||
[{{tuple, [fun_hash(Fun),
|
||||
{tuple, make_args(Args)}]},
|
||||
icode_seq([ hack_return_address(Fun, length(Args) + 1) ] ++
|
||||
[ {funcall, {var_ref, FName}, make_args(Args)}]
|
||||
)}
|
||||
|| Fun={FName, _, Args, _,_TypeRep} <- Functions, is_public(Fun) ]},
|
||||
word},
|
||||
NewFunctions = Functions ++ [DispatchFun],
|
||||
%% Create a function environment
|
||||
Funs = [{Name, length(Args), make_ref()}
|
||||
|| {Name, _Attrs, Args, _Body, _Type} <- NewFunctions],
|
||||
%% Create dummy code to call the main function with one argument
|
||||
%% taken from the stack
|
||||
StopLabel = make_ref(),
|
||||
StatefulStopLabel = make_ref(),
|
||||
MainFunction = lookup_fun(Funs, "%main"),
|
||||
|
||||
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
|
||||
|
||||
DispatchCode = [%% push two return addresses to stop, one for stateful
|
||||
%% functions and one for non-stateful functions.
|
||||
push_label(StatefulStopLabel),
|
||||
push_label(StopLabel),
|
||||
%% The calldata is already on the stack when we start. Put
|
||||
%% it on top (also reorders StatefulStop and Stop).
|
||||
swap(2),
|
||||
|
||||
jump(MainFunction),
|
||||
jumpdest(StatefulStopLabel),
|
||||
|
||||
%% We need to encode the state type and put it
|
||||
%% underneath the return value.
|
||||
assemble_expr(Funs, [], nontail, StateTypeValue), %% StateT Ret
|
||||
swap(1), %% Ret StateT
|
||||
|
||||
%% We should also change the state value at address 0 to a
|
||||
%% pointer to the state value (to allow 0 to represent an
|
||||
%% unchanged state).
|
||||
i(?MSIZE), %% Ptr
|
||||
push(0), i(?MLOAD), %% Val Ptr
|
||||
i(?MSIZE), i(?MSTORE), %% Ptr Mem[Ptr] := Val
|
||||
push(0), i(?MSTORE), %% Mem[0] := Ptr
|
||||
|
||||
%% The pointer to the return value is on top of
|
||||
%% the stack, but the return instruction takes two
|
||||
%% stack arguments.
|
||||
push(0),
|
||||
i(?RETURN),
|
||||
jumpdest(StopLabel),
|
||||
%% Set state pointer to 0 to indicate that we didn't change state
|
||||
push(0), dup(1), i(?MSTORE),
|
||||
%% Same as StatefulStopLabel above
|
||||
push(0),
|
||||
i(?RETURN)
|
||||
],
|
||||
%% Code is a deep list of instructions, containing labels and
|
||||
%% references to them. Labels take the form {'JUMPDEST', Ref}, and
|
||||
%% references take the form {push_label, Ref}, which is translated
|
||||
%% into a PUSH instruction.
|
||||
Code = [assemble_function(Funs, Name, Args, Body)
|
||||
|| {Name, _, Args, Body, _Type} <- NewFunctions],
|
||||
resolve_references(
|
||||
[%% i(?COMMENT), "CONTRACT: " ++ ContractName,
|
||||
DispatchCode,
|
||||
Code]).
|
||||
|
||||
%% Generate error on correct format.
|
||||
|
||||
gen_error(Error) ->
|
||||
error({code_errors, [Error]}).
|
||||
|
||||
make_args(Args) ->
|
||||
[{var_ref, [I-1 + $a]} || I <- lists:seq(1, length(Args))].
|
||||
|
||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||
{integer, Hash}.
|
||||
|
||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||
%% one for stateful functions and the bottom one for non-stateful.
|
||||
hack_return_address(Fun, N) ->
|
||||
case is_stateful(Fun) of
|
||||
true -> {inline_asm, [i(?MSIZE)]};
|
||||
false ->
|
||||
{inline_asm, %% X1 .. XN State NoState
|
||||
[ dup(N + 2) %% NoState X1 .. XN State NoState
|
||||
, swap(N + 1) %% State X1 .. XN NoState NoState
|
||||
]} %% Top of the stack will be discarded.
|
||||
end.
|
||||
|
||||
assemble_function(Funs, Name, Args, Body) ->
|
||||
[jumpdest(lookup_fun(Funs, Name)),
|
||||
assemble_expr(Funs, lists:reverse(Args), tail, Body),
|
||||
%% swap return value and first argument
|
||||
pop_args(length(Args)),
|
||||
swap(1),
|
||||
i(?JUMP)].
|
||||
|
||||
%% {seq, Es} - should be "one" operation in terms of stack content
|
||||
%% i.e. after the `seq` there should be one new element on the stack.
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E]}) ->
|
||||
assemble_expr(Funs, Stack, Tail, E);
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E | Es]}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_expr(Funs, Stack, Tail, {seq, Es})];
|
||||
assemble_expr(_Funs, _Stack, _Tail, {inline_asm, Code}) ->
|
||||
Code; %% Unsafe! Code should take care to respect the stack!
|
||||
assemble_expr(Funs, Stack, _TailPosition, {var_ref, Id}) ->
|
||||
case lists:keymember(Id, 1, Stack) of
|
||||
true ->
|
||||
dup(lookup_var(Id, Stack));
|
||||
false ->
|
||||
%% Build a closure
|
||||
%% When a top-level fun is called directly, we do not
|
||||
%% reach this case.
|
||||
Eta = make_ref(),
|
||||
Continue = make_ref(),
|
||||
[i(?MSIZE),
|
||||
push_label(Eta),
|
||||
dup(2),
|
||||
i(?MSTORE),
|
||||
jump(Continue),
|
||||
%% the code of the closure
|
||||
jumpdest(Eta),
|
||||
%% pop the pointer to the function
|
||||
pop(1),
|
||||
jump(lookup_fun(Funs, Id)),
|
||||
jumpdest(Continue)]
|
||||
end;
|
||||
assemble_expr(_, _, _, {missing_field, Format, Args}) ->
|
||||
io:format(Format, Args),
|
||||
gen_error(missing_field);
|
||||
assemble_expr(_Funs, _Stack, _, {integer, N}) ->
|
||||
push(N);
|
||||
assemble_expr(Funs, Stack, _, {tuple, Cpts}) ->
|
||||
%% We build tuples right-to-left, so that the first write to the
|
||||
%% tuple extends the memory size. Because we use ?MSIZE as the
|
||||
%% heap pointer, we must allocate the tuple AFTER computing the
|
||||
%% first element.
|
||||
%% We store elements into the tuple as soon as possible, to avoid
|
||||
%% keeping them for a long time on the stack.
|
||||
case lists:reverse(Cpts) of
|
||||
[] ->
|
||||
i(?MSIZE);
|
||||
[Last|Rest] ->
|
||||
[assemble_expr(Funs, Stack, nontail, Last),
|
||||
%% allocate the tuple memory
|
||||
i(?MSIZE),
|
||||
%% compute address of last word
|
||||
push(32 * (length(Cpts) - 1)), i(?ADD),
|
||||
%% Stack: <last-value> <pointer>
|
||||
%% Write value to memory (allocates the tuple)
|
||||
swap(1), dup(2), i(?MSTORE),
|
||||
%% Stack: pointer to last word written
|
||||
[[%% Update pointer to next word to be written
|
||||
push(32), swap(1), i(?SUB),
|
||||
%% Compute element
|
||||
assemble_expr(Funs, [pointer|Stack], nontail, A),
|
||||
%% Write element to memory
|
||||
dup(2), i(?MSTORE)]
|
||||
%% And we leave a pointer to the last word written on
|
||||
%% the stack
|
||||
|| A <- Rest]]
|
||||
%% The pointer to the entire tuple is on the stack
|
||||
end;
|
||||
assemble_expr(_Funs, _Stack, _, {list, []}) ->
|
||||
%% Use Erik's value of -1 for []
|
||||
[push(0), i(?NOT)];
|
||||
assemble_expr(Funs, Stack, _, {list, [A|B]}) ->
|
||||
assemble_expr(Funs, Stack, nontail, {tuple, [A, {list, B}]});
|
||||
assemble_expr(Funs, Stack, _, {unop, '!', A}) ->
|
||||
case A of
|
||||
{binop, Logical, _, _} when Logical=='&&'; Logical=='||' ->
|
||||
assemble_expr(Funs, Stack, nontail, {ifte, A, {integer, 0}, {integer, 1}});
|
||||
_ ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
i(?ISZERO)
|
||||
]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, _, {event, Topics, Payload}) ->
|
||||
[assemble_exprs(Funs, Stack, Topics ++ [Payload]),
|
||||
case length(Topics) of
|
||||
0 -> i(?LOG0);
|
||||
1 -> i(?LOG1);
|
||||
2 -> i(?LOG2);
|
||||
3 -> i(?LOG3);
|
||||
4 -> i(?LOG4)
|
||||
end, i(?MSIZE)];
|
||||
assemble_expr(Funs, Stack, _, {unop, Op, A}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_prefix(Op)];
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '&&', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, B, {integer, 0}});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '||', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, {integer, 1}, B});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '::', A, B}) ->
|
||||
%% Take advantage of optimizations in tuple construction.
|
||||
assemble_expr(Funs, Stack, Tail, {tuple, [A, B]});
|
||||
assemble_expr(Funs, Stack, _, {binop, Op, A, B}) ->
|
||||
%% EEVM binary instructions take their first argument from the top
|
||||
%% of the stack, so to get operands on the stack in the right
|
||||
%% order, we evaluate from right to left.
|
||||
[assemble_expr(Funs, Stack, nontail, B),
|
||||
assemble_expr(Funs, [dummy|Stack], nontail, A),
|
||||
assemble_infix(Op)];
|
||||
assemble_expr(Funs, Stack, _, {lambda, Args, Body}) ->
|
||||
Function = make_ref(),
|
||||
FunBody = make_ref(),
|
||||
Continue = make_ref(),
|
||||
NoMatch = make_ref(),
|
||||
FreeVars = free_vars({lambda, Args, Body}),
|
||||
{NewVars, MatchingCode} = assemble_pattern(FunBody, NoMatch, {tuple, [{var_ref, "_"}|FreeVars]}),
|
||||
BodyCode = assemble_expr(Funs, NewVars ++ lists:reverse([ {Arg#arg.name, Arg#arg.type} || Arg <- Args ]), tail, Body),
|
||||
[assemble_expr(Funs, Stack, nontail, {tuple, [{label, Function}|FreeVars]}),
|
||||
jump(Continue), %% will be optimized away
|
||||
jumpdest(Function),
|
||||
%% A pointer to the closure is on the stack
|
||||
MatchingCode,
|
||||
jumpdest(FunBody),
|
||||
BodyCode,
|
||||
pop_args(length(Args)+length(NewVars)),
|
||||
swap(1),
|
||||
i(?JUMP),
|
||||
jumpdest(NoMatch), %% dead code--raise an exception just in case
|
||||
push(0),
|
||||
i(?NOT),
|
||||
i(?MLOAD),
|
||||
i(?STOP),
|
||||
jumpdest(Continue)];
|
||||
assemble_expr(_, _, _, {label, Label}) ->
|
||||
push_label(Label);
|
||||
assemble_expr(Funs, Stack, nontail, {funcall, Fun, Args}) ->
|
||||
Return = make_ref(),
|
||||
%% This is the obvious code:
|
||||
%% [{push_label, Return},
|
||||
%% assemble_exprs(Funs, [return_address|Stack], Args++[Fun]),
|
||||
%% 'JUMP',
|
||||
%% {'JUMPDEST', Return}];
|
||||
%% Its problem is that it stores the return address on the stack
|
||||
%% while the arguments are computed, which is unnecessary. To
|
||||
%% avoid that, we compute the last argument FIRST, and replace it
|
||||
%% with the return address using a SWAP.
|
||||
%%
|
||||
%% assemble_function leaves the code pointer of the function to
|
||||
%% call on top of the stack, and--if the function is not a
|
||||
%% top-level name--a pointer to its tuple of free variables. In
|
||||
%% either case a JUMP is the right way to call it.
|
||||
case Args of
|
||||
[] ->
|
||||
[push_label(Return),
|
||||
assemble_function(Funs, [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)];
|
||||
_ ->
|
||||
{Init, [Last]} = lists:split(length(Args) - 1, Args),
|
||||
[assemble_exprs(Funs, Stack, [Last|Init]),
|
||||
%% Put the return address in the right place, which also
|
||||
%% reorders the args correctly.
|
||||
push_label(Return),
|
||||
swap(length(Args)),
|
||||
assemble_function(Funs, [dummy || _ <- Args] ++ [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, tail, {funcall, Fun, Args}) ->
|
||||
IsTopLevel = is_top_level_fun(Stack, Fun),
|
||||
%% If the fun is not top-level, then it may refer to local
|
||||
%% variables and must be computed before stack shuffling.
|
||||
ArgsAndFun = Args++[Fun || not IsTopLevel],
|
||||
ComputeArgsAndFun = assemble_exprs(Funs, Stack, ArgsAndFun),
|
||||
%% Copy arguments back down the stack to the start of the frame
|
||||
ShuffleSpec = lists:seq(length(ArgsAndFun), 1, -1) ++ [discard || _ <- Stack],
|
||||
Shuffle = shuffle_stack(ShuffleSpec),
|
||||
[ComputeArgsAndFun, Shuffle,
|
||||
if IsTopLevel ->
|
||||
%% still need to compute function
|
||||
assemble_function(Funs, [], Fun);
|
||||
true ->
|
||||
%% need to unpack a closure
|
||||
[dup(1), i(?MLOAD)]
|
||||
end,
|
||||
i(?JUMP)];
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, Decision, Then, Else}) ->
|
||||
%% This compilation scheme introduces a lot of labels and
|
||||
%% jumps. Unnecessary ones are removed later in
|
||||
%% resolve_references.
|
||||
Close = make_ref(),
|
||||
ThenL = make_ref(),
|
||||
ElseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, Decision, ThenL, ElseL),
|
||||
jumpdest(ElseL),
|
||||
assemble_expr(Funs, Stack, Tail, Else),
|
||||
jump(Close),
|
||||
jumpdest(ThenL),
|
||||
assemble_expr(Funs, Stack, Tail, Then),
|
||||
jumpdest(Close)
|
||||
];
|
||||
assemble_expr(Funs, Stack, Tail, {switch, A, Cases}) ->
|
||||
Close = make_ref(),
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases),
|
||||
{'JUMPDEST', Close}];
|
||||
%% State primitives
|
||||
%% (A pointer to) the contract state is stored at address 0.
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_state) ->
|
||||
[push(0), i(?MLOAD)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, State),
|
||||
push(0), i(?MSTORE), %% We need something for the unit value on the stack,
|
||||
i(?MSIZE)]; %% MSIZE is the cheapest instruction.
|
||||
%% Environment primitives
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
||||
[i(?ADDRESS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
|
||||
[i(?CREATOR)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
||||
[i(?ORIGIN)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
||||
[i(?CALLER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_value) ->
|
||||
[i(?CALLVALUE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_price) ->
|
||||
[i(?GASPRICE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_left) ->
|
||||
[i(?GAS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_coinbase) ->
|
||||
[i(?COINBASE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_timestamp) ->
|
||||
[i(?TIMESTAMP)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_block_height) ->
|
||||
[i(?NUMBER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_difficulty) ->
|
||||
[i(?DIFFICULTY)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_limit) ->
|
||||
[i(?GASLIMIT)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_balance{ address = Addr }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Addr),
|
||||
i(?BALANCE)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_block_hash{ height = Height }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Height),
|
||||
i(?BLOCKHASH)];
|
||||
assemble_expr(Funs, Stack, _Tail,
|
||||
#prim_call_contract{ gas = Gas
|
||||
, address = To
|
||||
, value = Value
|
||||
, arg = Arg
|
||||
, type_hash= TypeHash
|
||||
}) ->
|
||||
%% ?CALL takes (from the top)
|
||||
%% Gas, To, Value, Arg, TypeHash, _OOffset,_OSize
|
||||
%% So assemble these in reverse order.
|
||||
[ assemble_exprs(Funs, Stack, [ {integer, 0}, {integer, 0}, TypeHash
|
||||
, Arg, Value, To, Gas ])
|
||||
, i(?CALL)
|
||||
].
|
||||
|
||||
|
||||
assemble_exprs(_Funs, _Stack, []) ->
|
||||
[];
|
||||
assemble_exprs(Funs, Stack, [E|Es]) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_exprs(Funs, [dummy|Stack], Es)].
|
||||
|
||||
assemble_decision(Funs, Stack, {binop, '&&', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Label, Else),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {binop, '||', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Then, Label),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {unop, '!', A}, Then, Else) ->
|
||||
assemble_decision(Funs, Stack, A, Else, Then);
|
||||
assemble_decision(Funs, Stack, {ifte, A, B, C}, Then, Else) ->
|
||||
TrueL = make_ref(),
|
||||
FalseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, TrueL, FalseL),
|
||||
jumpdest(TrueL), assemble_decision(Funs, Stack, B, Then, Else),
|
||||
jumpdest(FalseL), assemble_decision(Funs, Stack, C, Then, Else)];
|
||||
assemble_decision(Funs, Stack, Decision, Then, Else) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Decision),
|
||||
jump_if(Then), jump(Else)].
|
||||
|
||||
%% Entered with value to switch on on top of the stack
|
||||
%% Evaluate selected case, then jump to Close with result on the
|
||||
%% stack.
|
||||
assemble_cases(_Funs, _Stack, _Tail, _Close, []) ->
|
||||
%% No match! What should be do? There's no real way to raise an
|
||||
%% exception, except consuming all the gas.
|
||||
%% There should not be enough gas to do this:
|
||||
[push(1), i(?NOT),
|
||||
i(?MLOAD),
|
||||
%% now stop, so that jump optimizer realizes we will not fall
|
||||
%% through this code.
|
||||
i(?STOP)];
|
||||
assemble_cases(Funs, Stack, Tail, Close, [{Pattern, Body}|Cases]) ->
|
||||
Succeed = make_ref(),
|
||||
Fail = make_ref(),
|
||||
{NewVars, MatchingCode} =
|
||||
assemble_pattern(Succeed, Fail, Pattern),
|
||||
%% In the code that follows, if this is NOT the last case, then we
|
||||
%% save the value being switched on, and discard it on
|
||||
%% success. The code is simpler if this IS the last case.
|
||||
[[dup(1) || Cases /= []], %% save value for next case, if there is one
|
||||
MatchingCode,
|
||||
jumpdest(Succeed),
|
||||
%% Discard saved value, if we saved one
|
||||
[case NewVars of
|
||||
[] ->
|
||||
pop(1);
|
||||
[_] ->
|
||||
%% Special case for peep-hole optimization
|
||||
pop_args(1);
|
||||
_ ->
|
||||
[swap(length(NewVars)), pop(1)]
|
||||
end
|
||||
|| Cases/=[]],
|
||||
assemble_expr(Funs,
|
||||
case Cases of
|
||||
[] -> NewVars;
|
||||
_ -> reorder_vars(NewVars)
|
||||
end
|
||||
++Stack, Tail, Body),
|
||||
%% If the Body makes a tail call, then we will not return
|
||||
%% here--but it doesn't matter, because
|
||||
%% (a) the NewVars will be popped before the tailcall
|
||||
%% (b) the code below will be deleted since it is dead
|
||||
pop_args(length(NewVars)),
|
||||
jump(Close),
|
||||
jumpdest(Fail),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases)].
|
||||
|
||||
%% Entered with value to match on top of the stack.
|
||||
%% Generated code removes value, and
|
||||
%% - jumps to Fail if no match, or
|
||||
%% - binds variables, leaves them on the stack, and jumps to Succeed
|
||||
%% Result is a list of variables to add to the stack, and the matching
|
||||
%% code.
|
||||
assemble_pattern(Succeed, Fail, {integer, N}) ->
|
||||
{[], [push(N),
|
||||
i(?EQ),
|
||||
jump_if(Succeed),
|
||||
jump(Fail)]};
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, "_"}) ->
|
||||
{[], [i(?POP), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {missing_field, _, _}) ->
|
||||
%% Missing record fields are quite ok in patterns.
|
||||
assemble_pattern(Succeed, Fail, {var_ref, "_"});
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, Id}) ->
|
||||
{[{Id, "_"}], jump(Succeed)};
|
||||
assemble_pattern(Succeed, _Fail, {tuple, []}) ->
|
||||
{[], [pop(1), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A]}) ->
|
||||
%% Treat this case specially, because we don't need to save the
|
||||
%% pointer to the tuple.
|
||||
{AVars, ACode} = assemble_pattern(Succeed, Fail, A),
|
||||
{AVars, [i(?MLOAD),
|
||||
ACode]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A|B]}) ->
|
||||
%% Entered with the address of the tuple on the top of the
|
||||
%% stack. We will duplicate the address before matching on A.
|
||||
Continue = make_ref(), %% the label for matching B
|
||||
Pop1Fail = make_ref(), %% pop 1 word and goto Fail
|
||||
PopNFail = make_ref(), %% pop length(AVars) words and goto Fail
|
||||
{AVars, ACode} =
|
||||
assemble_pattern(Continue, Pop1Fail, A),
|
||||
{BVars, BCode} =
|
||||
assemble_pattern(Succeed, PopNFail, {tuple, B}),
|
||||
{BVars ++ reorder_vars(AVars),
|
||||
[%% duplicate the pointer so we don't lose it when we match on A
|
||||
dup(1),
|
||||
i(?MLOAD),
|
||||
ACode,
|
||||
jumpdest(Continue),
|
||||
%% Bring the pointer to the top of the stack--this reorders AVars!
|
||||
swap(length(AVars)),
|
||||
push(32),
|
||||
i(?ADD),
|
||||
BCode,
|
||||
case AVars of
|
||||
[] ->
|
||||
[jumpdest(Pop1Fail), pop(1),
|
||||
jumpdest(PopNFail),
|
||||
jump(Fail)];
|
||||
_ ->
|
||||
[{'JUMPDEST', PopNFail}, pop(length(AVars)-1),
|
||||
{'JUMPDEST', Pop1Fail}, pop(1),
|
||||
{push_label, Fail}, 'JUMP']
|
||||
end]};
|
||||
assemble_pattern(Succeed, Fail, {list, []}) ->
|
||||
%% [] is represented by -1.
|
||||
{[], [push(1),
|
||||
i(?ADD),
|
||||
jump_if(Fail),
|
||||
jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {list, [A|B]}) ->
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, {list, B}});
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, B}) ->
|
||||
%% Make sure it's not [], then match as tuple.
|
||||
NotNil = make_ref(),
|
||||
{Vars, Code} = assemble_pattern(Succeed, Fail, {tuple, [A, B]}),
|
||||
{Vars, [dup(1), push(1), i(?ADD), %% Check for [] without consuming the value
|
||||
jump_if(NotNil), %% so it's still there when matching the tuple.
|
||||
pop(1), %% It was [] so discard the saved value.
|
||||
jump(Fail),
|
||||
jumpdest(NotNil),
|
||||
Code]}.
|
||||
|
||||
%% When Vars are on the stack, with a value we want to discard
|
||||
%% below them, then we swap the top variable with that value and pop.
|
||||
%% This reorders the variables on the stack, as follows:
|
||||
reorder_vars([]) ->
|
||||
[];
|
||||
reorder_vars([V|Vs]) ->
|
||||
Vs ++ [V].
|
||||
|
||||
assemble_prefix('sha3') -> [i(?DUP1), i(?MLOAD), %% length, ptr
|
||||
i(?SWAP1), push(32), i(?ADD), %% ptr+32, length
|
||||
i(?SHA3)];
|
||||
assemble_prefix('-') -> [push(0), i(?SUB)];
|
||||
assemble_prefix('bnot') -> i(?NOT).
|
||||
|
||||
assemble_infix('+') -> i(?ADD);
|
||||
assemble_infix('-') -> i(?SUB);
|
||||
assemble_infix('*') -> i(?MUL);
|
||||
assemble_infix('/') -> i(?SDIV);
|
||||
assemble_infix('div') -> i(?DIV);
|
||||
assemble_infix('mod') -> i(?MOD);
|
||||
assemble_infix('^') -> i(?EXP);
|
||||
assemble_infix('bor') -> i(?OR);
|
||||
assemble_infix('band') -> i(?AND);
|
||||
assemble_infix('bxor') -> i(?XOR);
|
||||
assemble_infix('bsl') -> i(?SHL);
|
||||
assemble_infix('bsr') -> i(?SHR);
|
||||
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
|
||||
assemble_infix('>') -> i(?SGT);
|
||||
assemble_infix('==') -> i(?EQ);
|
||||
assemble_infix('<=') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('=<') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('>=') -> [i(?SLT), i(?ISZERO)];
|
||||
assemble_infix('!=') -> [i(?EQ), i(?ISZERO)];
|
||||
assemble_infix('!') -> [i(?ADD), i(?MLOAD)];
|
||||
assemble_infix('byte') -> i(?BYTE).
|
||||
%% assemble_infix('::') -> [i(?MSIZE), write_word(0), write_word(1)].
|
||||
|
||||
%% a function may either refer to a top-level function, in which case
|
||||
%% we fetch the code label from Funs, or it may be a lambda-expression
|
||||
%% (including a top-level function passed as a parameter). In the
|
||||
%% latter case, the function value is a pointer to a tuple of the code
|
||||
%% pointer and the free variables: we keep the pointer and push the
|
||||
%% code pointer onto the stack. In either case, we are ready to enter
|
||||
%% the function with JUMP.
|
||||
assemble_function(Funs, Stack, Fun) ->
|
||||
case is_top_level_fun(Stack, Fun) of
|
||||
true ->
|
||||
{var_ref, Name} = Fun,
|
||||
{push_label, lookup_fun(Funs, Name)};
|
||||
false ->
|
||||
[assemble_expr(Funs, Stack, nontail, Fun),
|
||||
dup(1),
|
||||
i(?MLOAD)]
|
||||
end.
|
||||
|
||||
free_vars(V={var_ref, _}) ->
|
||||
[V];
|
||||
free_vars({switch, E, Cases}) ->
|
||||
lists:umerge(free_vars(E),
|
||||
lists:umerge([free_vars(Body)--free_vars(Pattern)
|
||||
|| {Pattern, Body} <- Cases]));
|
||||
free_vars({lambda, Args, Body}) ->
|
||||
free_vars(Body) -- [{var_ref, Arg#arg.name} || Arg <- Args];
|
||||
free_vars(T) when is_tuple(T) ->
|
||||
free_vars(tuple_to_list(T));
|
||||
free_vars([H|T]) ->
|
||||
lists:umerge(free_vars(H), free_vars(T));
|
||||
free_vars(_) ->
|
||||
[].
|
||||
|
||||
|
||||
|
||||
%% shuffle_stack reorders the stack, for example before a tailcall. It is called
|
||||
%% with a description of the current stack, and how the final stack
|
||||
%% should appear. The argument is a list containing
|
||||
%% a NUMBER for each element that should be kept, the number being
|
||||
%% the position this element should occupy in the final stack
|
||||
%% discard, for elements that can be discarded.
|
||||
%% The positions start at 1, referring to the variable to be placed at
|
||||
%% the bottom of the stack, and ranging up to the size of the final stack.
|
||||
shuffle_stack([]) ->
|
||||
[];
|
||||
shuffle_stack([discard|Stack]) ->
|
||||
[i(?POP) | shuffle_stack(Stack)];
|
||||
shuffle_stack([N|Stack]) ->
|
||||
case length(Stack) + 1 - N of
|
||||
0 ->
|
||||
%% the job should be finished
|
||||
CorrectStack = lists:seq(N - 1, 1, -1),
|
||||
CorrectStack = Stack,
|
||||
[];
|
||||
MoveBy ->
|
||||
{Pref, [_|Suff]} = lists:split(MoveBy - 1, Stack),
|
||||
[swap(MoveBy) | shuffle_stack([lists:nth(MoveBy, Stack) | Pref ++ [N|Suff]])]
|
||||
end.
|
||||
|
||||
|
||||
|
||||
lookup_fun(Funs, Name) ->
|
||||
case [Ref || {Name1, _, Ref} <- Funs,
|
||||
Name == Name1] of
|
||||
[Ref] -> Ref;
|
||||
[] -> gen_error({undefined_function, Name})
|
||||
end.
|
||||
|
||||
is_top_level_fun(Stack, {var_ref, Id}) ->
|
||||
not lists:keymember(Id, 1, Stack);
|
||||
is_top_level_fun(_, _) ->
|
||||
false.
|
||||
|
||||
lookup_var(Id, Stack) ->
|
||||
lookup_var(1, Id, Stack).
|
||||
|
||||
lookup_var(N, Id, [{Id, _Type}|_]) ->
|
||||
N;
|
||||
lookup_var(N, Id, [_|Stack]) ->
|
||||
lookup_var(N + 1, Id, Stack);
|
||||
lookup_var(_, Id, []) ->
|
||||
gen_error({var_not_in_scope, Id}).
|
||||
|
||||
%% Smart instruction generation
|
||||
|
||||
%% TODO: handle references to the stack beyond depth 16. Perhaps the
|
||||
%% best way is to repush variables that will be needed in
|
||||
%% subexpressions before evaluating he subexpression... i.e. fix the
|
||||
%% problem in assemble_expr, rather than here. A fix here would have
|
||||
%% to save the top elements of the stack in memory, duplicate the
|
||||
%% targetted element, and then repush the values from memory.
|
||||
dup(N) when 1 =< N, N =< 16 ->
|
||||
i(?DUP1 + N - 1).
|
||||
|
||||
push(N) ->
|
||||
Bytes = binary:encode_unsigned(N),
|
||||
true = size(Bytes) =< 32,
|
||||
[i(?PUSH1 + size(Bytes) - 1) |
|
||||
binary_to_list(Bytes)].
|
||||
|
||||
%% Pop N values from UNDER the top element of the stack.
|
||||
%% This is a pseudo-instruction so peephole optimization can
|
||||
%% combine pop_args(M), pop_args(N) to pop_args(M+N)
|
||||
pop_args(0) ->
|
||||
[];
|
||||
pop_args(N) ->
|
||||
{pop_args, N}.
|
||||
%% [swap(N), pop(N)].
|
||||
|
||||
pop(N) ->
|
||||
[i(?POP) || _ <- lists:seq(1, N)].
|
||||
|
||||
swap(0) ->
|
||||
%% Doesn't exist, but is logically a no-op.
|
||||
[];
|
||||
swap(N) when 1 =< N, N =< 16 ->
|
||||
i(?SWAP1 + N - 1).
|
||||
|
||||
jumpdest(Label) -> {i(?JUMPDEST), Label}.
|
||||
push_label(Label) -> {push_label, Label}.
|
||||
|
||||
jump(Label) -> [push_label(Label), i(?JUMP)].
|
||||
jump_if(Label) -> [push_label(Label), i(?JUMPI)].
|
||||
|
||||
%% ICode utilities (TODO: move to separate module)
|
||||
|
||||
icode_noname() -> #var_ref{name = "_"}.
|
||||
|
||||
icode_seq([A]) -> A;
|
||||
icode_seq([A | As]) ->
|
||||
icode_seq(A, icode_seq(As)).
|
||||
|
||||
icode_seq(A, B) ->
|
||||
#switch{ expr = A, cases = [{icode_noname(), B}] }.
|
||||
|
||||
%% Stack: <N elements> ADDR
|
||||
%% Write elements at addresses ADDR, ADDR+32, ADDR+64...
|
||||
%% Stack afterwards: ADDR
|
||||
% write_words(N) ->
|
||||
% [write_word(I) || I <- lists:seq(N-1, 0, -1)].
|
||||
|
||||
%% Unused at the moment. Comment out to please dialyzer.
|
||||
%% write_word(I) ->
|
||||
%% [%% Stack: elements e ADDR
|
||||
%% swap(1),
|
||||
%% dup(2),
|
||||
%% %% Stack: elements ADDR e ADDR
|
||||
%% push(32*I),
|
||||
%% i(?ADD),
|
||||
%% %% Stack: elements ADDR e ADDR+32I
|
||||
%% i(?MSTORE)].
|
||||
|
||||
%% Resolve references, and convert code from deep list to flat list.
|
||||
%% List elements are:
|
||||
%% Opcodes
|
||||
%% Byte values
|
||||
%% {'JUMPDEST', Ref} -- assembles to ?JUMPDEST and sets Ref
|
||||
%% {push_label, Ref} -- assembles to ?PUSHN address bytes
|
||||
|
||||
%% For now, we assemble all code addresses as three bytes.
|
||||
|
||||
resolve_references(Code) ->
|
||||
Peephole = peep_hole(lists:flatten(Code)),
|
||||
%% WARNING: Optimizing jumps reorders the code and deletes
|
||||
%% instructions. When debugging the assemble_ functions, it can be
|
||||
%% useful to replace the next line by:
|
||||
%% Instrs = lists:flatten(Code),
|
||||
%% thus disabling the optimization.
|
||||
OptimizedJumps = optimize_jumps(Peephole),
|
||||
Instrs = lists:reverse(peep_hole_backwards(lists:reverse(OptimizedJumps))),
|
||||
Labels = define_labels(0, Instrs),
|
||||
lists:flatten([use_labels(Labels, I) || I <- Instrs]).
|
||||
|
||||
define_labels(Addr, [{'JUMPDEST', Lab}|More]) ->
|
||||
[{Lab, Addr}|define_labels(Addr + 1, More)];
|
||||
define_labels(Addr, [{push_label, _}|More]) ->
|
||||
define_labels(Addr + 4, More);
|
||||
define_labels(Addr, [{pop_args, N}|More]) ->
|
||||
define_labels(Addr + N + 1, More);
|
||||
define_labels(Addr, [_|More]) ->
|
||||
define_labels(Addr + 1, More);
|
||||
define_labels(_, []) ->
|
||||
[].
|
||||
|
||||
use_labels(_, {'JUMPDEST', _}) ->
|
||||
'JUMPDEST';
|
||||
use_labels(Labels, {push_label, Ref}) ->
|
||||
case proplists:get_value(Ref, Labels) of
|
||||
undefined ->
|
||||
gen_error({undefined_label, Ref});
|
||||
Addr when is_integer(Addr) ->
|
||||
[i(?PUSH3),
|
||||
Addr div 65536, (Addr div 256) rem 256, Addr rem 256]
|
||||
end;
|
||||
use_labels(_, {pop_args, N}) ->
|
||||
[swap(N), pop(N)];
|
||||
use_labels(_, I) ->
|
||||
I.
|
||||
|
||||
%% Peep-hole optimization.
|
||||
%% The compilation of conditionals can introduce jumps depending on
|
||||
%% constants 1 and 0. These are removed by peep-hole optimization.
|
||||
|
||||
peep_hole(['PUSH1', 0, {push_label, _}, 'JUMPI'|More]) ->
|
||||
peep_hole(More);
|
||||
peep_hole(['PUSH1', 1, {push_label, Lab}, 'JUMPI'|More]) ->
|
||||
[{push_label, Lab}, 'JUMP'|peep_hole(More)];
|
||||
peep_hole([{pop_args, M}, {pop_args, N}|More]) when M + N =< 16 ->
|
||||
peep_hole([{pop_args, M + N}|More]);
|
||||
peep_hole([I|More]) ->
|
||||
[I|peep_hole(More)];
|
||||
peep_hole([]) ->
|
||||
[].
|
||||
|
||||
%% Peep-hole optimization on reversed instructions lists.
|
||||
|
||||
peep_hole_backwards(Code) ->
|
||||
NewCode = peep_hole_backwards1(Code),
|
||||
if Code == NewCode -> Code;
|
||||
true -> peep_hole_backwards(NewCode)
|
||||
end.
|
||||
|
||||
peep_hole_backwards1(['ADD', 0, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1(['POP', UnOp|Code]) when UnOp=='MLOAD';UnOp=='ISZERO';UnOp=='NOT' ->
|
||||
peep_hole_backwards1(['POP'|Code]);
|
||||
peep_hole_backwards1(['POP', BinOp|Code]) when
|
||||
%% TODO: more binary operators
|
||||
BinOp=='ADD';BinOp=='SUB';BinOp=='MUL';BinOp=='SDIV' ->
|
||||
peep_hole_backwards1(['POP', 'POP'|Code]);
|
||||
peep_hole_backwards1(['POP', _, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1([I|Code]) ->
|
||||
[I|peep_hole_backwards1(Code)];
|
||||
peep_hole_backwards1([]) ->
|
||||
[].
|
||||
|
||||
%% Jump optimization:
|
||||
%% Replaces a jump to a jump with a jump to the final destination
|
||||
%% Moves basic blocks to eliminate an unconditional jump to them.
|
||||
|
||||
%% The compilation of conditionals generates a lot of labels and
|
||||
%% jumps, some of them unnecessary. This optimization phase reorders
|
||||
%% code so that as many jumps as possible can be eliminated, and
|
||||
%% replaced by just falling through to the destination label. This
|
||||
%% both optimizes the code generated by conditionals, and converts one
|
||||
%% call of a function into falling through into its code--so it
|
||||
%% reorders code quite aggressively. Function returns are indirect
|
||||
%% jumps, however, and are never optimized away.
|
||||
|
||||
%% IMPORTANT: since execution begins at address zero, then the first
|
||||
%% block of code must never be moved elsewhere. The code below has
|
||||
%% this property, because it processes blocks from left to right, and
|
||||
%% because the first block does not begin with a label, and so can
|
||||
%% never be jumped to--hence no code can be inserted before it.
|
||||
|
||||
%% The optimization works by taking one block of code at a time, and
|
||||
%% then prepending blocks that jump directly to it, and appending
|
||||
%% blocks that it jumps directly to, resulting in a jump-free sequence
|
||||
%% that is as long as possible. To do so, we store blocks in the form
|
||||
%% {OptionalLabel, Body, OptionalJump} which represents the code block
|
||||
%% OptionalLabel++Body++OptionalJump; the optional parts are the empty
|
||||
%% list of instructions if not present. Two blocks can be merged if
|
||||
%% the first ends in an OptionalJump to the OptionalLabel beginning
|
||||
%% the second; the OptionalJump can then be removed (and the
|
||||
%% OptionalLabel if there are no other references to it--this happens
|
||||
%% during dead code elimination.
|
||||
|
||||
%% TODO: the present implementation is QUADRATIC, because we search
|
||||
%% repeatedly for matching blocks to merge with the first one, storing
|
||||
%% the blocks in a list. A near linear time implementation could use
|
||||
%% two ets tables, one keyed on the labels, and the other keyed on the
|
||||
%% final jumps.
|
||||
|
||||
optimize_jumps(Code) ->
|
||||
JJs = jumps_to_jumps(Code),
|
||||
ShortCircuited = [short_circuit_jumps(JJs, Instr) || Instr <- Code],
|
||||
NoDeadCode = eliminate_dead_code(ShortCircuited),
|
||||
MovedCode = merge_blocks(moveable_blocks(NoDeadCode)),
|
||||
%% Moving code may have made some labels superfluous.
|
||||
eliminate_dead_code(MovedCode).
|
||||
|
||||
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {push_label, Target}, 'JUMP'|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps(More)];
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {'JUMPDEST', Target}|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps([{'JUMPDEST', Target}|More])];
|
||||
jumps_to_jumps([_|More]) ->
|
||||
jumps_to_jumps(More);
|
||||
jumps_to_jumps([]) ->
|
||||
[].
|
||||
|
||||
short_circuit_jumps(JJs, {push_label, Lab}) ->
|
||||
case proplists:get_value(Lab, JJs) of
|
||||
undefined ->
|
||||
{push_label, Lab};
|
||||
Target ->
|
||||
%% I wonder if this will ever loop infinitely?
|
||||
short_circuit_jumps(JJs, {push_label, Target})
|
||||
end;
|
||||
short_circuit_jumps(_JJs, Instr) ->
|
||||
Instr.
|
||||
|
||||
eliminate_dead_code(Code) ->
|
||||
Jumps = lists:usort([Lab || {push_label, Lab} <- Code]),
|
||||
NewCode = live_code(Jumps, Code),
|
||||
if Code==NewCode ->
|
||||
Code;
|
||||
true ->
|
||||
eliminate_dead_code(NewCode)
|
||||
end.
|
||||
|
||||
live_code(Jumps, ['JUMP'|More]) ->
|
||||
['JUMP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, ['STOP'|More]) ->
|
||||
['STOP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
live_code(Jumps, More)
|
||||
end;
|
||||
live_code(Jumps, [I|More]) ->
|
||||
[I|live_code(Jumps, More)];
|
||||
live_code(_, []) ->
|
||||
[].
|
||||
|
||||
dead_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
dead_code(Jumps, More)
|
||||
end;
|
||||
dead_code(Jumps, [_I|More]) ->
|
||||
dead_code(Jumps, More);
|
||||
dead_code(_, []) ->
|
||||
[].
|
||||
|
||||
%% Split the code into "moveable blocks" that control flow only
|
||||
%% reaches via jumps.
|
||||
moveable_blocks([]) ->
|
||||
[];
|
||||
moveable_blocks([I]) ->
|
||||
[[I]];
|
||||
moveable_blocks([Jump|More]) when Jump=='JUMP'; Jump=='STOP' ->
|
||||
[[Jump]|moveable_blocks(More)];
|
||||
moveable_blocks([I|More]) ->
|
||||
[Block|MoreBlocks] = moveable_blocks(More),
|
||||
[[I|Block]|MoreBlocks].
|
||||
|
||||
%% Merge blocks to eliminate jumps where possible.
|
||||
merge_blocks(Blocks) ->
|
||||
BlocksAndTargets = [label_and_jump(B) || B <- Blocks],
|
||||
[I || {Pref, Body, Suff} <- merge_after(BlocksAndTargets),
|
||||
I <- Pref++Body++Suff].
|
||||
|
||||
%% Merge the first block with other blocks that come after it
|
||||
merge_after(All=[{Label, Body, [{push_label, Target}, 'JUMP']}|BlocksAndTargets]) ->
|
||||
case [{B, J} || {[{'JUMPDEST', L}], B, J} <- BlocksAndTargets,
|
||||
L == Target] of
|
||||
[{B, J}|_] ->
|
||||
merge_after([{Label, Body ++ [{'JUMPDEST', Target}] ++ B, J}|
|
||||
lists:delete({[{'JUMPDEST', Target}], B, J},
|
||||
BlocksAndTargets)]);
|
||||
[] ->
|
||||
merge_before(All)
|
||||
end;
|
||||
merge_after(All) ->
|
||||
merge_before(All).
|
||||
|
||||
%% The first block cannot be merged with any blocks that it jumps
|
||||
%% to... but maybe it can be merged with a block that jumps to it!
|
||||
merge_before([Block={[{'JUMPDEST', Label}], Body, Jump}|BlocksAndTargets]) ->
|
||||
case [{L, B, T} || {L, B, [{push_label, T}, 'JUMP']} <- BlocksAndTargets,
|
||||
T == Label] of
|
||||
[{L, B, T}|_] ->
|
||||
merge_before([{L, B ++ [{'JUMPDEST', Label}] ++ Body, Jump}
|
||||
|lists:delete({L, B, [{push_label, T}, 'JUMP']}, BlocksAndTargets)]);
|
||||
_ ->
|
||||
[Block | merge_after(BlocksAndTargets)]
|
||||
end;
|
||||
merge_before([Block|BlocksAndTargets]) ->
|
||||
[Block | merge_after(BlocksAndTargets)];
|
||||
merge_before([]) ->
|
||||
[].
|
||||
|
||||
%% Convert each block to a PREFIX, which is a label or empty, a
|
||||
%% middle, and a SUFFIX which is a JUMP to a label, or empty.
|
||||
label_and_jump(B) ->
|
||||
{Label, B1} = case B of
|
||||
[{'JUMPDEST', L}|More1] ->
|
||||
{[{'JUMPDEST', L}], More1};
|
||||
_ ->
|
||||
{[], B}
|
||||
end,
|
||||
{Target, B2} = case lists:reverse(B1) of
|
||||
['JUMP', {push_label, T}|More2] ->
|
||||
{[{push_label, T}, 'JUMP'], lists:reverse(More2)};
|
||||
_ ->
|
||||
{[], B1}
|
||||
end,
|
||||
{Label, B2, Target}.
|
||||
@@ -15,7 +15,8 @@
|
||||
many/1, many1/1, sep/2, sep1/2,
|
||||
infixl/2, infixr/2]).
|
||||
|
||||
-export([current_file/0, set_current_file/1]).
|
||||
-export([current_file/0, set_current_file/1,
|
||||
current_include_type/0, set_current_include_type/1]).
|
||||
|
||||
%% -- Types ------------------------------------------------------------------
|
||||
|
||||
@@ -465,6 +466,13 @@ merge_with(Fun, Map1, Map2) ->
|
||||
end, Map2, maps:to_list(Map1))
|
||||
end.
|
||||
|
||||
%% Current include type
|
||||
current_include_type() ->
|
||||
get('$current_include_type').
|
||||
|
||||
set_current_include_type(IncludeType) ->
|
||||
put('$current_include_type', IncludeType).
|
||||
|
||||
%% Current source file
|
||||
current_file() ->
|
||||
get('$current_file').
|
||||
|
||||
+51
-13
@@ -18,7 +18,8 @@
|
||||
run_parser/3]).
|
||||
|
||||
-include("aeso_parse_lib.hrl").
|
||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
|
||||
current_include_type/0, set_current_include_type/1]).
|
||||
|
||||
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
|
||||
|
||||
@@ -57,6 +58,7 @@ run_parser(P, Inp, Opts) ->
|
||||
|
||||
parse_and_scan(P, S, Opts) ->
|
||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||
set_current_include_type(proplists:get_value(include_type, Opts, none)),
|
||||
case aeso_scan:scan(S) of
|
||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||
{error, {{Input, Pos}, _}} ->
|
||||
@@ -94,17 +96,29 @@ decl() ->
|
||||
choice(
|
||||
%% Contract declaration
|
||||
[ ?RULE(token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, [], _5})
|
||||
, ?RULE(token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
|
||||
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
@@ -306,17 +320,18 @@ expr() -> expr100().
|
||||
|
||||
expr100() ->
|
||||
Expr100 = ?LAZY_P(expr100()),
|
||||
Expr200 = ?LAZY_P(expr200()),
|
||||
Expr150 = ?LAZY_P(expr150()),
|
||||
choice(
|
||||
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
|
||||
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
|
||||
, ?RULE(Expr200, optional(right(tok(':'), type())),
|
||||
, {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)}
|
||||
, ?RULE(Expr150, optional(right(tok(':'), type())),
|
||||
case _2 of
|
||||
none -> _1;
|
||||
{ok, Type} -> {typed, get_ann(_1), _1, Type}
|
||||
end)
|
||||
]).
|
||||
|
||||
expr150() -> infixl(expr200(), binop('|>')).
|
||||
expr200() -> infixr(expr300(), binop('||')).
|
||||
expr300() -> infixr(expr400(), binop('&&')).
|
||||
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
|
||||
@@ -332,7 +347,7 @@ exprAtom() ->
|
||||
?LAZY_P(begin
|
||||
Expr = ?LAZY_P(expr()),
|
||||
choice(
|
||||
[ id_or_addr(), con(), token(qid), token(qcon)
|
||||
[ id_or_addr(), con(), token(qid), token(qcon), binop_as_lam()
|
||||
, token(bytes), token(string), token(char)
|
||||
, token(int)
|
||||
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
||||
@@ -469,6 +484,19 @@ id() -> token(id).
|
||||
tvar() -> token(tvar).
|
||||
str() -> token(string).
|
||||
|
||||
binop_as_lam() ->
|
||||
BinOps = ['&&', '||',
|
||||
'+', '-', '*', '/', '^', 'mod',
|
||||
'==', '!=', '<', '>', '<=', '=<', '>=',
|
||||
'::', '++', '|>'],
|
||||
OpToLam = fun(Op = {_, Ann}) ->
|
||||
IdL = {id, Ann, "l"},
|
||||
IdR = {id, Ann, "r"},
|
||||
Arg = fun(Id) -> {arg, Ann, Id, type_wildcard(Ann)} end,
|
||||
{lam, Ann, [Arg(IdL), Arg(IdR)], infix(IdL, Op, IdR)}
|
||||
end,
|
||||
?RULE(parens(choice(lists:map(fun token/1, BinOps))), OpToLam(_1)).
|
||||
|
||||
token(Tag) ->
|
||||
?RULE(tok(Tag),
|
||||
case _1 of
|
||||
@@ -523,7 +551,11 @@ bracket_list(P) -> brackets(comma_sep(P)).
|
||||
-type ann_col() :: aeso_syntax:ann_col().
|
||||
|
||||
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
||||
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
|
||||
pos_ann(Line, Col) ->
|
||||
[ {file, current_file()}
|
||||
, {include_type, current_include_type()}
|
||||
, {line, Line}
|
||||
, {col, Col} ].
|
||||
|
||||
ann_pos(Ann) ->
|
||||
{proplists:get_value(file, Ann),
|
||||
@@ -665,9 +697,15 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op
|
||||
Hashed = hash_include(File, Code),
|
||||
case sets:is_element(Hashed, Included) of
|
||||
false ->
|
||||
SrcFile = proplists:get_value(src_file, Opts, no_file),
|
||||
IncludeType = case proplists:get_value(file, Ann) of
|
||||
SrcFile -> direct;
|
||||
_ -> indirect
|
||||
end,
|
||||
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
||||
Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}),
|
||||
Included1 = sets:add_element(Hashed, Included),
|
||||
case parse_and_scan(file(), Code, Opts1) of
|
||||
case parse_and_scan(file(), Code, Opts2) of
|
||||
{ok, AST1} ->
|
||||
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
||||
Err = {error, _} ->
|
||||
|
||||
+6
-2
@@ -151,12 +151,16 @@ decl(D, Options) ->
|
||||
with_options(Options, fun() -> decl(D) end).
|
||||
|
||||
-spec decl(aeso_syntax:decl()) -> doc().
|
||||
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
decl({Con, Attrs, C, Is, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
Mod = fun({Mod, true}) when Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
ImplsList = case Is of
|
||||
[] -> [empty()];
|
||||
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
|
||||
end,
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||
, hsep(name(C), text("="))), decls(Ds));
|
||||
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({pragma, _, Pragma}) -> pragma(Pragma);
|
||||
|
||||
+3
-3
@@ -38,9 +38,9 @@
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||
| {contract_child, ann(), con(), [decl()]}
|
||||
| {contract_interface, ann(), con(), [decl()]}
|
||||
-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
|
||||
| {contract_child, ann(), con(), [con()], [decl()]}
|
||||
| {contract_interface, ann(), con(), [con()], [decl()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
|
||||
+9
-62
@@ -1,75 +1,15 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Decoding aevm and fate data to AST
|
||||
%%%
|
||||
%%% @doc Decoding fate data to AST
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_vm_decode).
|
||||
|
||||
-export([ from_aevm/3, from_fate/2 ]).
|
||||
-export([ from_fate/2 ]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_fate_data.hrl").
|
||||
|
||||
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||
|
||||
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
||||
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
|
||||
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
||||
from_aevm(word, {id, _, "int"}, N0) ->
|
||||
<<N:256/signed>> = <<N0:256>>,
|
||||
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
|
||||
true -> {int, [], N} end;
|
||||
from_aevm(word, {id, _, "bits"}, N0) ->
|
||||
<<N:256/signed>> = <<N0:256>>,
|
||||
make_bits(N);
|
||||
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
|
||||
{bytes, [], <<Bytes:Len/unit:8>>};
|
||||
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
|
||||
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
|
||||
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
|
||||
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
||||
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
|
||||
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||
case Val of
|
||||
{variant, 0, []} -> {con, [], "None"};
|
||||
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
|
||||
end;
|
||||
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
|
||||
when length(VmTypes) == length(Types),
|
||||
length(VmTypes) == tuple_size(Val) ->
|
||||
{tuple, [], [from_aevm(VmType, Type, X)
|
||||
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
|
||||
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
|
||||
when length(VmTypes) == length(Fields),
|
||||
length(VmTypes) == tuple_size(Val) ->
|
||||
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|
||||
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
|
||||
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||
when is_map(Map) ->
|
||||
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
|
||||
from_aevm(VmValType, ValType, Val)}
|
||||
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
|
||||
when length(VmCons) == length(Cons),
|
||||
length(VmCons) > Tag ->
|
||||
VmTypes = lists:nth(Tag + 1, VmCons),
|
||||
ConType = lists:nth(Tag + 1, Cons),
|
||||
from_aevm(VmTypes, ConType, Args);
|
||||
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
|
||||
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
|
||||
when length(VmTypes) == length(Types),
|
||||
length(VmTypes) == length(Args) ->
|
||||
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|
||||
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
|
||||
from_aevm(_VmType, _Type, _Data) ->
|
||||
throw(cannot_translate_to_sophia).
|
||||
|
||||
|
||||
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
|
||||
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
|
||||
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
|
||||
@@ -136,6 +76,8 @@ from_fate_builtin(QType, Val) ->
|
||||
Str = {id, [], "string"},
|
||||
Adr = {id, [], "address"},
|
||||
Hsh = {bytes_t, [], 32},
|
||||
I32 = {bytes_t, [], 32},
|
||||
I48 = {bytes_t, [], 48},
|
||||
Qid = fun(Name) -> {qid, [], Name} end,
|
||||
Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end,
|
||||
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
@@ -208,6 +150,11 @@ from_fate_builtin(QType, Val) ->
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} ->
|
||||
App(["Chain","GAAttachTx"], []);
|
||||
|
||||
{["MCL_BLS12_381", "fp"], X} ->
|
||||
App(["MCL_BLS12_381", "fp"], [Chk(I32, X)]);
|
||||
{["MCL_BLS12_381", "fr"], X} ->
|
||||
App(["MCL_BLS12_381", "fr"], [Chk(I48, X)]);
|
||||
|
||||
_ ->
|
||||
throw(cannot_translate_to_sophia)
|
||||
end.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
-module(aeso_warnings).
|
||||
|
||||
-record(warn, { pos :: aeso_errors:pos()
|
||||
, message :: iolist()
|
||||
}).
|
||||
|
||||
-opaque warning() :: #warn{}.
|
||||
|
||||
-export_type([warning/0]).
|
||||
|
||||
-export([ new/1
|
||||
, new/2
|
||||
, warn_to_err/2
|
||||
, sort_warnings/1
|
||||
, pp/1
|
||||
]).
|
||||
|
||||
new(Msg) ->
|
||||
new(aeso_errors:pos(0, 0), Msg).
|
||||
|
||||
new(Pos, Msg) ->
|
||||
#warn{ pos = Pos, message = Msg }.
|
||||
|
||||
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
|
||||
aeso_errors:new(Kind, Pos, lists:flatten(Msg)).
|
||||
|
||||
sort_warnings(Warnings) ->
|
||||
lists:sort(fun(W1, W2) -> W1#warn.pos =< W2#warn.pos end, Warnings).
|
||||
|
||||
pp(#warn{ pos = Pos, message = Msg }) ->
|
||||
lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])).
|
||||
+78
-90
@@ -5,7 +5,6 @@
|
||||
|
||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
||||
-define(DUMMY_HASH_WORD, 16#123).
|
||||
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
|
||||
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
|
||||
|
||||
sandbox(Code) ->
|
||||
@@ -20,12 +19,6 @@ sandbox(Code) ->
|
||||
{error, loop}
|
||||
end.
|
||||
|
||||
malicious_from_binary_test() ->
|
||||
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
||||
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
|
||||
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
||||
ok.
|
||||
|
||||
from_words(Ws) ->
|
||||
<< <<(from_word(W))/binary>> || W <- Ws >>.
|
||||
|
||||
@@ -37,23 +30,14 @@ from_word(S) when is_list(S) ->
|
||||
<<Len:256, Bin/binary>>.
|
||||
|
||||
encode_decode_test() ->
|
||||
encode_decode(word, 42),
|
||||
42 = encode_decode(word, 42),
|
||||
-1 = encode_decode(signed_word, -1),
|
||||
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
|
||||
{} = encode_decode({tuple, []}, {}),
|
||||
{42} = encode_decode({tuple, [word]}, {42}),
|
||||
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
|
||||
[] = encode_decode({list, word}, []),
|
||||
[32] = encode_decode({list, word}, [32]),
|
||||
none = encode_decode({option, word}, none),
|
||||
{some, 1} = encode_decode({option, word}, {some, 1}),
|
||||
string = encode_decode(typerep, string),
|
||||
word = encode_decode(typerep, word),
|
||||
{list, word} = encode_decode(typerep, {list, word}),
|
||||
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
|
||||
1 = encode_decode(word, 1),
|
||||
0 = encode_decode(word, 0),
|
||||
Tests =
|
||||
[42, 1, 0 -1, <<"Hello">>,
|
||||
{tuple, {}}, {tuple, {42}}, {tuple, {21, 37}},
|
||||
[], [42], [21, 37],
|
||||
{variant, [0, 1], 0, {}}, {variant, [0, 1], 1, {42}}, {variant, [2], 0, {21, 37}},
|
||||
{typerep, string}, {typerep, integer}, {typerep, {list, integer}}, {typerep, {tuple, [integer]}}
|
||||
],
|
||||
[?assertEqual(Test, encode_decode(Test)) || Test <- Tests],
|
||||
ok.
|
||||
|
||||
encode_decode_sophia_test() ->
|
||||
@@ -72,55 +56,67 @@ encode_decode_sophia_test() ->
|
||||
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
|
||||
ok.
|
||||
|
||||
to_sophia_value_mcl_bls12_381_test() ->
|
||||
Code = "include \"BLS12_381.aes\"\n"
|
||||
"contract C =\n"
|
||||
" entrypoint test_bls12_381_fp(x : int) = BLS12_381.int_to_fp(x)\n"
|
||||
" entrypoint test_bls12_381_fr(x : int) = BLS12_381.int_to_fr(x)\n"
|
||||
" entrypoint test_bls12_381_g1(x : int) = BLS12_381.mk_g1(x, x, x)\n",
|
||||
|
||||
Opts = [{backend, fate}],
|
||||
|
||||
CallValue32 = aeb_fate_encoding:serialize({bytes, <<20:256>>}),
|
||||
CallValue48 = aeb_fate_encoding:serialize({bytes, <<55:384>>}),
|
||||
CallValueTp = aeb_fate_encoding:serialize({tuple, {{bytes, <<15:256>>}, {bytes, <<160:256>>}, {bytes, <<1234:256>>}}}),
|
||||
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue32, Opts),
|
||||
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue48, Opts),
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue48, Opts),
|
||||
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue32, Opts),
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_g1", ok, CallValueTp, Opts),
|
||||
|
||||
ok.
|
||||
|
||||
to_sophia_value_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
|
||||
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
|
||||
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
|
||||
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
|
||||
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "f", ok, encode(12)),
|
||||
?assertEqual("Data error:\nCannot translate FATE value 12\n of Sophia type string\n", aeso_errors:pp(Err1)),
|
||||
|
||||
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
|
||||
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
|
||||
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
|
||||
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
|
||||
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "f", revert, encode(12)),
|
||||
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err2)),
|
||||
ok.
|
||||
|
||||
encode_calldata_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
|
||||
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
|
||||
"when checking the application at line 5, column 34 of\n"
|
||||
" x : (int) => string\nto arguments\n true : bool\n",
|
||||
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
|
||||
ExpErr1 = "Type error at line 5, col 34:\nCannot unify `int` and `bool`\n"
|
||||
"when checking the application of\n"
|
||||
" `f : (int) => string`\n"
|
||||
"to arguments\n"
|
||||
" `true : bool`\n",
|
||||
{error, [Err1]} = aeso_compiler:create_calldata(Code, "f", ["true"]),
|
||||
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
|
||||
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
|
||||
|
||||
ok.
|
||||
|
||||
decode_calldata_neg_test() ->
|
||||
Code1 = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
Code2 = [ "contract Foo =\n"
|
||||
" entrypoint x(y : string) : int = 42\n" ],
|
||||
" entrypoint f(x : string) : int = 42\n" ],
|
||||
|
||||
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
|
||||
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
|
||||
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "f", ["42"]),
|
||||
|
||||
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
|
||||
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
|
||||
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
|
||||
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
|
||||
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
|
||||
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "f", <<1,2,3>>),
|
||||
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "f", CallDataFATE),
|
||||
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err2)),
|
||||
|
||||
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
|
||||
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
|
||||
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'x' is missing in contract\n", aeso_errors:pp(Err3)),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -133,8 +129,7 @@ encode_decode_sophia_string(SophiaType, String) ->
|
||||
, " datatype variant = Red | Blue(map(string, int))\n"
|
||||
, " entrypoint foo : arg_type => arg_type\n" ],
|
||||
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
|
||||
{ok, _, {[Type], _}, [Arg]} ->
|
||||
io:format("Type ~p~n", [Type]),
|
||||
{ok, _, [Arg]} ->
|
||||
Data = encode(Arg),
|
||||
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
|
||||
{ok, Sophia} ->
|
||||
@@ -150,30 +145,32 @@ encode_decode_sophia_string(SophiaType, String) ->
|
||||
|
||||
calldata_test() ->
|
||||
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
|
||||
Map = #{ <<"a">> => 4 },
|
||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||
[{variant, [0,1], 1, {#{ <<"a">> := 4 }}}, {tuple, {{tuple, {<<"b">>, 5}}, {variant, [0,1], 0, {}}}}] =
|
||||
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
||||
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
|
||||
[{bytes, <<291:256>>}, {address, <<1110:256>>}] =
|
||||
encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||
[{bytes, <<291:256>>}, {bytes, <<291:256>>}] =
|
||||
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
|
||||
|
||||
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||
[119, {bytes, <<0:64/unit:8>>}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||
|
||||
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
[{contract, <<1110:256>>}] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_init_test() ->
|
||||
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
|
||||
encode_decode_calldata("init", ["int"], ["42"]),
|
||||
|
||||
Code = parameterized_contract("foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
|
||||
encode_decode_calldata_(Code, "init", []),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_indent_test() ->
|
||||
Test = fun(Extra) ->
|
||||
Code = parameterized_contract(Extra, "foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "foo", ["42"], word)
|
||||
encode_decode_calldata_(Code, "foo", ["42"])
|
||||
end,
|
||||
Test(" stateful entrypoint bla() = ()"),
|
||||
Test(" type x = int"),
|
||||
@@ -202,9 +199,9 @@ oracle_test() ->
|
||||
"contract OracleTest =\n"
|
||||
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n"
|
||||
" Oracle.get_question(o, q)\n",
|
||||
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
||||
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
|
||||
?assertEqual({ok, "question", [{oracle, <<291:256>>}, {oracle_query, <<1110:256>>}]},
|
||||
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||
"oq_1111111111111111111111111111113AFEFpt5"], [no_code])),
|
||||
|
||||
ok.
|
||||
|
||||
@@ -220,35 +217,26 @@ permissive_literals_fail_test() ->
|
||||
ok.
|
||||
|
||||
encode_decode_calldata(FunName, Types, Args) ->
|
||||
encode_decode_calldata(FunName, Types, Args, word).
|
||||
|
||||
encode_decode_calldata(FunName, Types, Args, RetType) ->
|
||||
Code = parameterized_contract(FunName, Types),
|
||||
encode_decode_calldata_(Code, FunName, Args, RetType).
|
||||
encode_decode_calldata_(Code, FunName, Args).
|
||||
|
||||
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
||||
encode_decode_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}, no_code]),
|
||||
?assertEqual(RetType, RetVMType),
|
||||
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
|
||||
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
||||
{ok, _, _} = aeso_compiler:check_call(Code, FunName, Args, [no_code]),
|
||||
case FunName of
|
||||
"init" ->
|
||||
ok;
|
||||
[];
|
||||
_ ->
|
||||
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
|
||||
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
|
||||
?assertMatch({X, X}, {Args, Values})
|
||||
end,
|
||||
tuple_to_list(ArgTuple).
|
||||
{ok, FateArgs} = aeb_fate_abi:decode_calldata(FunName, Calldata),
|
||||
FateArgs
|
||||
end.
|
||||
|
||||
encode_decode(T, D) ->
|
||||
?assertEqual(D, decode(T, encode(D))),
|
||||
encode_decode(D) ->
|
||||
?assertEqual(D, decode(encode(D))),
|
||||
D.
|
||||
|
||||
encode(D) ->
|
||||
aeb_heap:to_binary(D).
|
||||
aeb_fate_encoding:serialize(D).
|
||||
|
||||
decode(T,B) ->
|
||||
{ok, D} = aeb_heap:from_binary(T, B),
|
||||
D.
|
||||
decode(B) ->
|
||||
aeb_fate_encoding:deserialize(B).
|
||||
|
||||
@@ -108,7 +108,7 @@ aci_test_contract(Name) ->
|
||||
{error, ErrorStringJ} when is_binary(ErrorStringJ) -> error(ErrorStringJ);
|
||||
{error, ErrorJ} -> aeso_compiler_tests:print_and_throw(ErrorJ)
|
||||
end,
|
||||
case aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]) of
|
||||
case aeso_compiler:from_string(String, [{aci, json} | Opts]) of
|
||||
{ok, #{aci := JSON1}} ->
|
||||
?assertEqual(JSON, JSON1),
|
||||
io:format("JSON:\n~p\n", [JSON]),
|
||||
|
||||
@@ -19,19 +19,9 @@ calldata_test_() ->
|
||||
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||
fun() ->
|
||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
||||
AevmExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(aevm)) of
|
||||
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(fate)) of
|
||||
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs = ast_exprs(ContractString, Fun, Args),
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
?assertEqual(ParsedExprs, FateExprs),
|
||||
ok
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
@@ -42,24 +32,14 @@ calldata_aci_test_() ->
|
||||
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
|
||||
ContractACI = binary_to_list(ContractACIBin),
|
||||
io:format("ACI:\n~s\n", [ContractACIBin]),
|
||||
AevmExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(aevm)) of
|
||||
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(fate)) of
|
||||
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs = ast_exprs(ContractACI, Fun, Args),
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
?assertEqual(ParsedExprs, FateExprs),
|
||||
ok
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
[{contract_main, _, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
strip_ann(AST).
|
||||
|
||||
@@ -75,6 +55,8 @@ strip_ann1(L) when is_list(L) ->
|
||||
lists:map(fun strip_ann/1, L);
|
||||
strip_ann1(X) -> X.
|
||||
|
||||
ast_exprs(ContractString, Fun, Args) ->
|
||||
ast_exprs(ContractString, Fun, Args, []).
|
||||
ast_exprs(ContractString, Fun, Args, Opts) ->
|
||||
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
|
||||
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
|
||||
@@ -159,8 +141,3 @@ compilable_contracts() ->
|
||||
{"stub", "foo", ["-42"]},
|
||||
{"payable", "foo", ["42"]}
|
||||
].
|
||||
|
||||
not_yet_compilable(fate) ->
|
||||
[];
|
||||
not_yet_compilable(aevm) ->
|
||||
["funargs", "strings"].
|
||||
|
||||
+556
-410
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ simple_contracts_test_() ->
|
||||
Text = "main contract Identity =\n"
|
||||
" function id(x) = x\n",
|
||||
?assertMatch(
|
||||
[{contract_main, _, {con, _, "Identity"},
|
||||
[{contract_main, _, {con, _, "Identity"}, _,
|
||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
|
||||
ok
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
## Requires ocaml >= 4.02, < 4.06
|
||||
## and reason-3.0.0 (opam install reason).
|
||||
|
||||
default : voting_test
|
||||
|
||||
%.ml : %.re
|
||||
refmt -p ml $< > $@
|
||||
|
||||
|
||||
voting_test : rte.ml voting.ml voting_test.ml
|
||||
ocamlopt -o $@ $^
|
||||
|
||||
clean :
|
||||
rm -f *.cmi *.cmx *.ml *.o voting_test
|
||||
@@ -1,31 +0,0 @@
|
||||
// A simple test of the abort built-in function.
|
||||
|
||||
contract AbortTest =
|
||||
|
||||
record state = { value : int }
|
||||
|
||||
public function init(v : int) =
|
||||
{ value = v }
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) : unit =
|
||||
put_value(v)
|
||||
revert_abort(s)
|
||||
|
||||
// Accessing the value
|
||||
public function get_value() = state.value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_values() : list(int) = [state.value]
|
||||
public function put_values(v : int) = put(state{value = v})
|
||||
|
||||
// Some basic statistics
|
||||
public function get_stats(acct : address) =
|
||||
( Contract.balance, Chain.balance(acct) )
|
||||
|
||||
// Abort functions.
|
||||
private function revert_abort(s : string) =
|
||||
abort(s)
|
||||
|
||||
// This is still legal but will be stripped out.
|
||||
// TODO: This function confuses the type inference, so it cannot be present.
|
||||
//private function abort(s : string) = 42
|
||||
@@ -1,27 +0,0 @@
|
||||
contract Interface =
|
||||
function do_abort : (int, string) => unit
|
||||
function get_value : () => int
|
||||
function put_value : (int) => unit
|
||||
function get_values : () => list(int)
|
||||
function put_values : (int) => unit
|
||||
|
||||
contract AbortTestInt =
|
||||
|
||||
record state = {r : Interface, value : int}
|
||||
|
||||
public function init(r : Interface, value : int) =
|
||||
{r = r, value = value}
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) =
|
||||
put_value(v)
|
||||
state.r.do_abort(v + 100, s)
|
||||
|
||||
// Accessing the value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_value() = state.value
|
||||
public function get_values() : list(int) =
|
||||
state.value :: state.r.get_values()
|
||||
public function put_values(v : int) =
|
||||
put_value(v)
|
||||
state.r.put_values(v + 1000)
|
||||
@@ -1,8 +0,0 @@
|
||||
contract ChannelEnv =
|
||||
public function coinbase() : address = Chain.coinbase
|
||||
|
||||
public function timestamp() : int = Chain.timestamp
|
||||
|
||||
public function block_height() : int = Chain.block_height
|
||||
|
||||
public function difficulty() : int = Chain.difficulty
|
||||
@@ -1,7 +0,0 @@
|
||||
contract ChannelOnChainContractNameResolution =
|
||||
|
||||
public function can_resolve(name: string, key: string) : bool =
|
||||
switch(AENS.resolve(name, key) : option(string))
|
||||
None => false
|
||||
Some(_address) => true
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
contract ChannelOnChainContractOracle =
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
record state = { oracle : oracle_id,
|
||||
question : string,
|
||||
bets : map(string, address)
|
||||
}
|
||||
|
||||
|
||||
public function init(oracle: oracle_id, question: string) : state =
|
||||
{ oracle = oracle,
|
||||
question = question,
|
||||
bets = {}
|
||||
}
|
||||
|
||||
public stateful function place_bet(answer: string) =
|
||||
switch(Map.lookup(answer, state.bets))
|
||||
None =>
|
||||
put(state{ bets = state.bets{[answer] = Call.caller}})
|
||||
"ok"
|
||||
Some(_value) =>
|
||||
"bet_already_taken"
|
||||
|
||||
public function expiry() =
|
||||
Oracle.expiry(state.oracle)
|
||||
|
||||
public function query_fee() =
|
||||
Oracle.query_fee(state.oracle)
|
||||
|
||||
public function get_question(q: query_id) =
|
||||
Oracle.get_question(state.oracle, q)
|
||||
|
||||
public stateful function resolve(q: query_id) =
|
||||
switch(Oracle.get_answer(state.oracle, q))
|
||||
None =>
|
||||
"no response"
|
||||
Some(result) =>
|
||||
if(state.question == Oracle.get_question(state.oracle, q))
|
||||
switch(Map.lookup(result, state.bets))
|
||||
None =>
|
||||
"no winning bet"
|
||||
Some(winner) =>
|
||||
Chain.spend(winner, Contract.balance)
|
||||
"ok"
|
||||
else
|
||||
"different question"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
contract Remote =
|
||||
function get : () => int
|
||||
function can_resolve : (string, string) => bool
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
function remote_resolve(r : Remote, name: string, key: string) : bool =
|
||||
r.can_resolve(name, key)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
contract Chess =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type state = board
|
||||
|
||||
private function get_row(r, m : board) =
|
||||
Map.lookup_default(r, m, {})
|
||||
|
||||
private function set_piece(r, c, p, m : board) =
|
||||
m { [r] = get_row(r, m) { [c] = p } }
|
||||
|
||||
private function get_piece(r, c, m : board) =
|
||||
Map.lookup(c, get_row(r, m))
|
||||
|
||||
private function from_list(xs, m : board) =
|
||||
switch(xs)
|
||||
[] => m
|
||||
(r, c, p) :: xs => from_list(xs, set_piece(r, c, p, m))
|
||||
|
||||
function init() =
|
||||
from_list([ (2, 1, "white pawn"), (7, 1, "black pawn")
|
||||
, (2, 2, "white pawn"), (7, 2, "black pawn")
|
||||
, (2, 3, "white pawn"), (7, 3, "black pawn")
|
||||
, (2, 4, "white pawn"), (7, 4, "black pawn")
|
||||
, (2, 5, "white pawn"), (7, 5, "black pawn")
|
||||
, (2, 6, "white pawn"), (7, 6, "black pawn")
|
||||
, (2, 7, "white pawn"), (7, 7, "black pawn")
|
||||
, (2, 8, "white pawn"), (7, 8, "black pawn")
|
||||
, (1, 1, "white rook"), (8, 1, "black rook")
|
||||
, (1, 2, "white knight"), (8, 2, "black knight")
|
||||
, (1, 3, "white bishop"), (8, 3, "black bishop")
|
||||
, (1, 4, "white queen"), (8, 4, "black queen")
|
||||
, (1, 5, "white king"), (8, 5, "black king")
|
||||
, (1, 6, "white bishop"), (8, 6, "black bishop")
|
||||
, (1, 7, "white knight"), (8, 7, "black knight")
|
||||
, (1, 8, "white rook"), (8, 8, "black rook")
|
||||
], {})
|
||||
|
||||
function piece(r, c) = get_piece(r, c, state)
|
||||
|
||||
function move_piece(r, c, r1, c1) =
|
||||
switch(piece(r, c))
|
||||
Some(p) => put(set_piece(r1, c1, p, state))
|
||||
|
||||
function destroy_piece(r, c) =
|
||||
put(state{ [r] = Map.delete(c, get_row(r, state)) })
|
||||
|
||||
function delete_row(r) =
|
||||
put(Map.delete(r, state))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
contract UnappliedNamedArgBuiltin =
|
||||
// Allowed in FATE, but not AEVM
|
||||
stateful entrypoint main_fun(s) =
|
||||
let reg = Oracle.register
|
||||
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
contract OtherContract =
|
||||
|
||||
function multiply : (int, int) => int
|
||||
|
||||
contract ThisContract =
|
||||
|
||||
record state = { server : OtherContract, n : int }
|
||||
|
||||
function init(server : OtherContract) =
|
||||
{ server = server, n = 2 }
|
||||
|
||||
function square() =
|
||||
put(state{ n @ n = state.server.multiply(value = 100, n, n) })
|
||||
|
||||
function get_n() = state.n
|
||||
|
||||
function tip_server() =
|
||||
Chain.spend(state.server.address, Call.value)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
contract C =
|
||||
record r = {}
|
||||
entrypoint init() = ()
|
||||
@@ -1,87 +0,0 @@
|
||||
contract ERC20Token =
|
||||
record state = {
|
||||
totalSupply : int,
|
||||
decimals : int,
|
||||
name : string,
|
||||
symbol : string,
|
||||
balances : map(address, int),
|
||||
allowed : map(address, map(address,int)),
|
||||
// Logs, remove when native Events are there
|
||||
transfer_log : list((address,address,int)),
|
||||
approval_log : list((address,address,int))}
|
||||
|
||||
// init(100000000, 10, "Token Name", "TKN")
|
||||
public stateful function init(_totalSupply : int, _decimals : int, _name : string, _symbol : string ) = {
|
||||
totalSupply = _totalSupply,
|
||||
decimals = _decimals,
|
||||
name = _name,
|
||||
symbol = _symbol,
|
||||
balances = {[Call.caller] = _totalSupply }, // creator gets all Tokens
|
||||
allowed = {},
|
||||
// Logs, remove when native Events are there
|
||||
transfer_log = [],
|
||||
approval_log = []}
|
||||
|
||||
public stateful function totalSupply() : int = state.totalSupply
|
||||
public stateful function decimals() : int = state.decimals
|
||||
public stateful function name() : string = state.name
|
||||
public stateful function symbol() : string = state.symbol
|
||||
|
||||
public stateful function balanceOf(tokenOwner : address ) : int =
|
||||
Map.lookup_default(tokenOwner, state.balances, 0)
|
||||
|
||||
public stateful function transfer(to : address, tokens : int) =
|
||||
put( state{balances[Call.caller] = sub(state.balances[Call.caller], tokens) })
|
||||
put( state{balances[to] = add(Map.lookup_default(to, state.balances, 0), tokens) })
|
||||
transferEvent(Call.caller, to, tokens)
|
||||
true
|
||||
|
||||
public stateful function approve(spender : address, tokens : int) =
|
||||
// allowed[Call.caller] field must have a value!
|
||||
ensure_allowed(Call.caller)
|
||||
put( state{allowed[Call.caller][spender] = tokens} )
|
||||
approvalEvent(Call.caller, spender, tokens)
|
||||
true
|
||||
|
||||
public stateful function transferFrom(from : address, to : address, tokens : int) =
|
||||
put( state{ balances[from] = sub(state.balances[from], tokens) })
|
||||
put( state{ allowed[from][Call.caller] = sub(state.allowed[from][Call.caller], tokens) })
|
||||
put( state{ balances[to] = add(balanceOf(to), tokens) })
|
||||
transferEvent(from, to, tokens)
|
||||
true
|
||||
|
||||
public function allowance(_owner : address, _spender : address) : int =
|
||||
state.allowed[_owner][_spender]
|
||||
|
||||
public stateful function getTransferLog() : list((address,address,int)) =
|
||||
state.transfer_log
|
||||
public stateful function getApprovalLog() : list((address,address,int)) =
|
||||
state.approval_log
|
||||
|
||||
//
|
||||
// Private Functions
|
||||
//
|
||||
|
||||
private function ensure_allowed(key : address) =
|
||||
switch(Map.lookup(key, state.allowed))
|
||||
None => put(state{allowed[key] = {}})
|
||||
Some(_) => ()
|
||||
|
||||
private function transferEvent(from : address, to : address, tokens : int) =
|
||||
let e = (from, to, tokens)
|
||||
put( state{transfer_log = e :: state.transfer_log })
|
||||
e
|
||||
|
||||
private function approvalEvent(from : address, to : address, tokens : int) =
|
||||
let e = (from, to, tokens)
|
||||
put( state{approval_log = e :: state.approval_log })
|
||||
e
|
||||
|
||||
private function sub(_a : int, _b : int) : int =
|
||||
require(_b =< _a, "Error")
|
||||
_a - _b
|
||||
|
||||
private function add(_a : int, _b : int) : int =
|
||||
let c : int = _a + _b
|
||||
require(c >= _a, "Error")
|
||||
c
|
||||
@@ -1,6 +0,0 @@
|
||||
|
||||
contract Exploits =
|
||||
|
||||
// We'll hack the bytecode of this changing the return type to string.
|
||||
function pair(n : int) = (n, 0)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
contract Remote =
|
||||
function missing : (int) => int
|
||||
|
||||
contract Init_error =
|
||||
|
||||
record state = {value : int}
|
||||
|
||||
function init(r : Remote, x : int) =
|
||||
{value = r.missing(x)}
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
contract Fail =
|
||||
|
||||
entrypoint tttt() : bool * int =
|
||||
let f(x : 'a) : 'a = x
|
||||
(f(true), f(1))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This should include Lists.aes implicitly, since Option.aes does.
|
||||
include "List.aes"
|
||||
include "Option.aes"
|
||||
|
||||
contract Test =
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
|
||||
contract MapOfMaps =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type map2('a, 'b, 'c) = map('a, map('b, 'c))
|
||||
|
||||
record state = { big1 : map2(string, string, string),
|
||||
big2 : map2(string, string, string),
|
||||
small1 : map(string, string),
|
||||
small2 : map(string, string) }
|
||||
|
||||
private function empty_state() =
|
||||
{ big1 = {}, big2 = {},
|
||||
small1 = {}, small2 = {} }
|
||||
|
||||
function init() = empty_state()
|
||||
|
||||
function setup_state() =
|
||||
let small = {["key"] = "val"}
|
||||
put({ big1 = {["one"] = small},
|
||||
big2 = {["two"] = small},
|
||||
small1 = small,
|
||||
small2 = small })
|
||||
|
||||
// -- Garbage collection of inner map when outer map is garbage collected
|
||||
function test1_setup() =
|
||||
let inner = {["key"] = "val"}
|
||||
put(empty_state() { big1 = {["one"] = inner} })
|
||||
|
||||
function test1_execute() =
|
||||
put(state{ big1 = {} })
|
||||
|
||||
function test1_check() =
|
||||
state.big1
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
contract MapUpdater =
|
||||
function update_map : (int, string, map(int, string)) => map(int, string)
|
||||
|
||||
contract Benchmark =
|
||||
|
||||
record state = { updater : MapUpdater,
|
||||
map : map(int, string) }
|
||||
|
||||
function init(u, m) = { updater = u, map = m }
|
||||
|
||||
function set_updater(u) = put(state{ updater = u })
|
||||
|
||||
function update_map(k : int, v : string, m) = m{ [k] = v }
|
||||
|
||||
function update(a : int, b : int, v : string) =
|
||||
if (a > b) ()
|
||||
else
|
||||
put(state{ map[a] = v })
|
||||
update(a + 1, b, v)
|
||||
|
||||
function get(k) = state.map[k]
|
||||
function noop() = ()
|
||||
|
||||
function benchmark(k, v) =
|
||||
let m = state.updater.update_map(k, v, state.map)
|
||||
put(state{ map = m })
|
||||
m
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
contract MinimalInit =
|
||||
|
||||
record state = {foo : int}
|
||||
|
||||
function init() =
|
||||
{ foo = 0 }
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
contract MultiplicationServer =
|
||||
|
||||
function multiply(x : int, y : int) =
|
||||
switch(Call.value >= 100)
|
||||
true => x * y
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@compiler >= 6
|
||||
|
||||
include "BLS12_381.aes"
|
||||
|
||||
namespace BLS12_381 =
|
||||
type fp = int
|
||||
|
||||
main contract Bug =
|
||||
type number = int
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Nmsp =
|
||||
type x = int
|
||||
|
||||
namespace Nmsp =
|
||||
type y = string
|
||||
|
||||
main contract Bug =
|
||||
type number = int
|
||||
@@ -0,0 +1,14 @@
|
||||
include "List.aes"
|
||||
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
function any(l : list(bool)) : bool = List.foldl((||), false, l)
|
||||
|
||||
entrypoint init() =
|
||||
let bad_application = (+)(1)
|
||||
let good_application = (-)(3, 4)
|
||||
let op_var = (+)
|
||||
let op_var_application = op_var(3, 4)
|
||||
|
||||
good_application + op_var_application
|
||||
@@ -1,11 +0,0 @@
|
||||
contract OraclesErr =
|
||||
|
||||
public function unsafeCreateQueryThenErr(
|
||||
o : oracle(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
||||
let x = Oracle.query(o, q, qfee, qttl, rttl)
|
||||
switch(0) 1 => ()
|
||||
x // Never reached.
|
||||
@@ -1,22 +0,0 @@
|
||||
contract OraclesGas =
|
||||
|
||||
type fee = int
|
||||
type question_t = string
|
||||
type answer_t = int
|
||||
|
||||
public function happyPathWithAllBuiltinsAtSameHeight(
|
||||
qfee : fee,
|
||||
ottl : Chain.ttl,
|
||||
ettl : Chain.ttl,
|
||||
qttl : Chain.ttl,
|
||||
rttl : Chain.ttl
|
||||
) =
|
||||
let question = "why"
|
||||
let answer = 42
|
||||
let o = Oracle.register(Contract.address, qfee, ottl) : oracle(question_t, answer_t)
|
||||
Oracle.extend(o, ettl)
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
let q = Oracle.query(o, question, qfee, qttl, rttl)
|
||||
Oracle.respond(o, q, answer)
|
||||
()
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
contract Oracles =
|
||||
|
||||
type fee = int
|
||||
type ttl = Chain.ttl
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
function createQuery(o : oracle_id,
|
||||
q : query_t,
|
||||
qfee : fee,
|
||||
qttl : ttl,
|
||||
rttl : ttl) : query_id =
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
Oracle.query(o, q, qfee, qttl, rttl)
|
||||
|
||||
|
||||
function respond(o : oracle_id,
|
||||
q : query_id,
|
||||
sign : signature,
|
||||
r : answer_t) : unit =
|
||||
Oracle.respond(o, q, signature = sign, r)
|
||||
|
||||
|
||||
function getQuestion(o : oracle_id,
|
||||
q : query_id) : query_t =
|
||||
Oracle.get_question(o, q)
|
||||
|
||||
function getAnswer(o : oracle_id,
|
||||
q : query_id) : option(answer_t) =
|
||||
Oracle.get_answer(o, q)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
contract Main =
|
||||
function is_negative(x : int) =
|
||||
if (x < 0)
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
function inc_by_one(x : int) = x + 1
|
||||
function inc_by_two(x : int) = x + 2
|
||||
|
||||
type state = bool
|
||||
|
||||
entrypoint init(x : int) = x
|
||||
|> inc_by_one
|
||||
|> inc_by_one
|
||||
|> inc_by_two
|
||||
|> ((x) => x * 5)
|
||||
|> is_negative
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface Strokable =
|
||||
entrypoint stroke : () => string
|
||||
|
||||
contract Cat : Strokable =
|
||||
entrypoint stroke() = "Cat stroke"
|
||||
@@ -0,0 +1,10 @@
|
||||
contract interface II =
|
||||
entrypoint f : () => unit
|
||||
|
||||
contract interface I : II =
|
||||
entrypoint f : () => unit
|
||||
entrypoint g : () => unit
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = ()
|
||||
entrypoint g() = ()
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I0 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I1 : I0 =
|
||||
entrypoint f : () => int
|
||||
entrypoint something_else : () => int
|
||||
|
||||
main contract C =
|
||||
entrypoint f(x : I1) = x.f() // Here we should know that x has f
|
||||
@@ -0,0 +1,13 @@
|
||||
contract interface X : Z =
|
||||
entrypoint x : () => int
|
||||
|
||||
contract interface Y : X =
|
||||
entrypoint y : () => int
|
||||
|
||||
contract interface Z : Y =
|
||||
entrypoint z : () => int
|
||||
|
||||
contract C : Z =
|
||||
entrypoint x() = 1
|
||||
entrypoint y() = 1
|
||||
entrypoint z() = 1
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface II : I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : II =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint f : () => char
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint f() = 1
|
||||
entrypoint f() = 'c'
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I : H =
|
||||
entrypoint f : () => unit
|
||||
|
||||
contract C =
|
||||
entrypoint g() = ()
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I1 =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface I2 : I1 =
|
||||
entrypoint g : () => int
|
||||
|
||||
contract C : I2 =
|
||||
entrypoint g() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint g : () => char
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
entrypoint g() = 'c'
|
||||
@@ -0,0 +1,8 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,9 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract interface J =
|
||||
entrypoint f : () => char
|
||||
|
||||
contract C : I, J =
|
||||
entrypoint f() = 1
|
||||
entrypoint f() = 'c'
|
||||
@@ -0,0 +1,2 @@
|
||||
contract C : I =
|
||||
entrypoint f() = ()
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
contract Identity =
|
||||
|
||||
function zip_with(f, xs, ys) =
|
||||
switch((xs, ys))
|
||||
(x :: xs, y :: ys) => f(x, y) :: zip_with(f, xs, ys)
|
||||
_ => []
|
||||
|
||||
// Check that we can use zip_with at different types
|
||||
|
||||
function foo() =
|
||||
zip_with((x, y) => x + y, [1, 2, 3], [4, 5, 6, 7])
|
||||
|
||||
function bar() =
|
||||
zip_with((x, y) => if(x) y else 0, [true, false, true, false], [1, 2, 3])
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
contract interface Creature =
|
||||
entrypoint is_alive : () => bool
|
||||
|
||||
contract interface Animal : Creature =
|
||||
entrypoint is_alive : () => bool
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
entrypoint is_alive() = true
|
||||
|
||||
main contract Main =
|
||||
entrypoint init() = ()
|
||||
|
||||
stateful function g0(_ : Creature) : Cat = Chain.create()
|
||||
stateful function f0(x : Cat) : Creature = g0(x)
|
||||
stateful function h0() =
|
||||
let a : Animal = (Chain.create() : Cat)
|
||||
let c : Creature = (Chain.create() : Cat)
|
||||
let c1 : Creature = a
|
||||
()
|
||||
|
||||
stateful function g1(x : Animal) : Cat = Chain.create()
|
||||
stateful function f1(x : Cat) : Animal = g1(x)
|
||||
|
||||
stateful function g11(x : list(Animal)) : list(Cat) = [Chain.create()]
|
||||
stateful function f11(x : list(Cat)) : list(Animal) = g11(x)
|
||||
|
||||
stateful function g12(x : Animal * Animal) : Cat * Cat = (Chain.create(), Chain.create())
|
||||
stateful function f12(x : Cat * Cat) : Animal * Animal = g12(x)
|
||||
|
||||
stateful function g13() : map(Cat, Cat) = { [Chain.create()] = Chain.create() }
|
||||
stateful function f13() : map(Animal, Animal) = g13()
|
||||
|
||||
stateful function g2(x : Cat) : Cat = Chain.create()
|
||||
stateful function f2(x : Animal) : Animal = g2(x) // fail
|
||||
|
||||
stateful function g3(x : Cat) : Animal = f1(x)
|
||||
stateful function f3(x : Cat) : Cat = g3(x) // fail
|
||||
|
||||
stateful function g4(x : (Cat => Animal)) : Cat = Chain.create()
|
||||
stateful function f4(x : (Animal => Cat)) : Animal = g4(x)
|
||||
|
||||
stateful function g44(x : list(list(Cat) => list(Animal))) : Cat = Chain.create()
|
||||
stateful function f44(x : list(list(Animal) => list(Cat))) : Animal = g44(x)
|
||||
|
||||
stateful function g5(x : (Animal => Animal)) : Cat = Chain.create()
|
||||
stateful function f5(x : (Cat => Cat)) : Animal = g5(x) // fail
|
||||
|
||||
stateful function g6() : option(Cat) = Some(Chain.create())
|
||||
stateful function f6() : option(Animal) = g6()
|
||||
stateful function h6() : option(Cat) = f6() // fail
|
||||
|
||||
type cat_type = Cat
|
||||
type animal_type = Animal
|
||||
type cat_cat_map = map(cat_type, cat_type)
|
||||
type animal_animal_map = map(animal_type, animal_type)
|
||||
|
||||
stateful function g71(x : animal_type) : cat_type = Chain.create()
|
||||
stateful function f71(x : cat_type) : animal_type = g1(x)
|
||||
|
||||
stateful function g72() : cat_cat_map = { [Chain.create()] = Chain.create() }
|
||||
stateful function f72() : animal_animal_map = g13()
|
||||
|
||||
stateful function g73() =
|
||||
let some_cat : Cat = Chain.create()
|
||||
let some_animal : Animal = some_cat
|
||||
|
||||
let some_cat_cat_map : map(Cat, Cat) = g13()
|
||||
let some_animal_animal_map : map(Animal, Animal) = some_cat_cat_map
|
||||
|
||||
let x : Animal = some_animal_animal_map[some_cat] // success
|
||||
let y : Cat = some_cat_cat_map[some_animal] // fail
|
||||
|
||||
()
|
||||
@@ -0,0 +1,135 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
datatype dt_contra('a) = DT_CONTRA('a => unit)
|
||||
datatype dt_co('a) = DT_CO(unit => 'a)
|
||||
datatype dt_inv('a) = DT_INV('a => 'a)
|
||||
datatype dt_biv('a) = DT_BIV(unit => unit)
|
||||
datatype dt_inv_sep('a) = DT_INV_SEP_A('a => unit) | DT_INV_SEP_B(unit => 'a)
|
||||
datatype dt_co_nest_a('a) = DT_CO_NEST_A(dt_contra('a) => unit)
|
||||
datatype dt_contra_nest_a('a) = DT_CONTRA_NEST_A(dt_co('a) => unit)
|
||||
datatype dt_contra_nest_b('a) = DT_CONTRA_NEST_B(unit => dt_contra('a))
|
||||
datatype dt_co_nest_b('a) = DT_CO_NEST_B(unit => dt_co('a))
|
||||
datatype dt_co_twice('a) = DT_CO_TWICE(('a => unit) => 'a)
|
||||
datatype dt_contra_twice('a) = DT_CONTRA_TWICE('a => 'a => unit)
|
||||
datatype dt_a_contra_b_contra('a, 'b) = DT_A_CONTRA_B_CONTRA('a => 'b => unit)
|
||||
|
||||
function f_a_to_a_to_u(_ : Animal) : (Animal => unit) = f_a_to_u
|
||||
function f_a_to_c_to_u(_ : Animal) : (Cat => unit) = f_c_to_u
|
||||
function f_c_to_a_to_u(_ : Cat) : (Animal => unit) = f_a_to_u
|
||||
function f_c_to_c_to_u(_ : Cat) : (Cat => unit) = f_c_to_u
|
||||
|
||||
function f_u_to_u(_ : unit) : unit = ()
|
||||
function f_a_to_u(_ : Animal) : unit = ()
|
||||
function f_c_to_u(_ : Cat) : unit = ()
|
||||
|
||||
function f_dt_contra_a_to_u(_ : dt_contra(Animal)) : unit = ()
|
||||
function f_dt_contra_c_to_u(_ : dt_contra(Cat)) : unit = ()
|
||||
function f_dt_co_a_to_u(_ : dt_co(Animal)) : unit = ()
|
||||
function f_dt_co_c_to_u(_ : dt_co(Cat)) : unit = ()
|
||||
function f_u_to_dt_contra_a(_ : unit) : dt_contra(Animal) = DT_CONTRA(f_a_to_u)
|
||||
function f_u_to_dt_contra_c(_ : unit) : dt_contra(Cat) = DT_CONTRA(f_c_to_u)
|
||||
|
||||
stateful function f_c() : Cat = Chain.create()
|
||||
stateful function f_a() : Animal = f_c()
|
||||
|
||||
stateful function f_u_to_a(_ : unit) : Animal = f_a()
|
||||
stateful function f_u_to_c(_ : unit) : Cat = f_c()
|
||||
stateful function f_a_to_a(_ : Animal) : Animal = f_a()
|
||||
stateful function f_a_to_c(_ : Animal) : Cat = f_c()
|
||||
stateful function f_c_to_a(_ : Cat) : Animal = f_a()
|
||||
stateful function f_c_to_c(_ : Cat) : Cat = f_c()
|
||||
|
||||
stateful function f_a_to_u_to_c(_ : (Animal => unit)) : Cat = f_c()
|
||||
stateful function f_c_to_u_to_a(_ : (Cat => unit)) : Animal = f_a()
|
||||
stateful function f_c_to_u_to_c(_ : (Cat => unit)) : Cat = f_c()
|
||||
|
||||
stateful function f_u_to_dt_co_a(_ : unit) : dt_co(Animal) = DT_CO(f_u_to_a)
|
||||
stateful function f_u_to_dt_co_c(_ : unit) : dt_co(Cat) = DT_CO(f_u_to_c)
|
||||
|
||||
stateful entrypoint init() =
|
||||
let va1 : dt_contra(Animal) = DT_CONTRA(f_a_to_u) // success
|
||||
let va2 : dt_contra(Animal) = DT_CONTRA(f_c_to_u) // fail
|
||||
let va3 : dt_contra(Cat) = DT_CONTRA(f_a_to_u) // success
|
||||
let va4 : dt_contra(Cat) = DT_CONTRA(f_c_to_u) // success
|
||||
|
||||
let vb1 : dt_co(Animal) = DT_CO(f_u_to_a) // success
|
||||
let vb2 : dt_co(Animal) = DT_CO(f_u_to_c) // success
|
||||
let vb3 : dt_co(Cat) = DT_CO(f_u_to_a) // fail
|
||||
let vb4 : dt_co(Cat) = DT_CO(f_u_to_c) // success
|
||||
|
||||
let vc1 : dt_inv(Animal) = DT_INV(f_a_to_a) // success
|
||||
let vc2 : dt_inv(Animal) = DT_INV(f_a_to_c) // success
|
||||
let vc3 : dt_inv(Animal) = DT_INV(f_c_to_a) // fail
|
||||
let vc4 : dt_inv(Animal) = DT_INV(f_c_to_c) // fail
|
||||
let vc5 : dt_inv(Cat) = DT_INV(f_a_to_a) // fail
|
||||
let vc6 : dt_inv(Cat) = DT_INV(f_a_to_c) // fail
|
||||
let vc7 : dt_inv(Cat) = DT_INV(f_c_to_a) // fail
|
||||
let vc8 : dt_inv(Cat) = DT_INV(f_c_to_c) // success
|
||||
|
||||
let vd1 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
|
||||
let vd2 : dt_biv(Animal) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
|
||||
let vd3 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Animal) // success
|
||||
let vd4 : dt_biv(Cat) = DT_BIV(f_u_to_u) : dt_biv(Cat) // success
|
||||
|
||||
let ve1 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_a_to_u) // success
|
||||
let ve2 : dt_inv_sep(Animal) = DT_INV_SEP_A(f_c_to_u) // fail
|
||||
let ve3 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_a) // success
|
||||
let ve4 : dt_inv_sep(Animal) = DT_INV_SEP_B(f_u_to_c) // fail
|
||||
let ve5 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_a_to_u) // fail
|
||||
let ve6 : dt_inv_sep(Cat) = DT_INV_SEP_A(f_c_to_u) // success
|
||||
let ve7 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_a) // fail
|
||||
let ve8 : dt_inv_sep(Cat) = DT_INV_SEP_B(f_u_to_c) // success
|
||||
|
||||
let vf1 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_a_to_u) // success
|
||||
let vf2 : dt_co_nest_a(Animal) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
|
||||
let vf3 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_a_to_u) // fail
|
||||
let vf4 : dt_co_nest_a(Cat) = DT_CO_NEST_A(f_dt_contra_c_to_u) // success
|
||||
|
||||
let vg1 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
|
||||
let vg2 : dt_contra_nest_a(Animal) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // fail
|
||||
let vg3 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_a_to_u) // success
|
||||
let vg4 : dt_contra_nest_a(Cat) = DT_CONTRA_NEST_A(f_dt_co_c_to_u) // success
|
||||
|
||||
let vh1 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
|
||||
let vh2 : dt_contra_nest_b(Animal) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // fail
|
||||
let vh3 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_a) // success
|
||||
let vh4 : dt_contra_nest_b(Cat) = DT_CONTRA_NEST_B(f_u_to_dt_contra_c) // success
|
||||
|
||||
let vi1 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_a) // success
|
||||
let vi2 : dt_co_nest_b(Animal) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
|
||||
let vi3 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_a) // fail
|
||||
let vi4 : dt_co_nest_b(Cat) = DT_CO_NEST_B(f_u_to_dt_co_c) // success
|
||||
|
||||
let vj1 : dt_co_twice(Animal) = DT_CO_TWICE(f_a_to_u_to_c : (Animal => unit) => Animal) : dt_co_twice(Animal) // success
|
||||
let vj2 : dt_co_twice(Animal) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
|
||||
let vj3 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_a : (Animal => unit) => Animal) : dt_co_twice(Animal) // fail
|
||||
let vj4 : dt_co_twice(Cat) = DT_CO_TWICE(f_c_to_u_to_c : (Cat => unit) => Cat ) : dt_co_twice(Cat) // success
|
||||
|
||||
let vk01 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk02 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
|
||||
let vk03 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
|
||||
let vk04 : dt_a_contra_b_contra(Animal, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk05 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk06 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
|
||||
let vk07 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // fail
|
||||
let vk08 : dt_a_contra_b_contra(Animal, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk09 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk10 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // fail
|
||||
let vk11 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
|
||||
let vk12 : dt_a_contra_b_contra(Cat, Animal) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // fail
|
||||
let vk13 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_a_to_u) // success
|
||||
let vk14 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_a_to_c_to_u) // success
|
||||
let vk15 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_a_to_u) // success
|
||||
let vk16 : dt_a_contra_b_contra(Cat, Cat) = DT_A_CONTRA_B_CONTRA(f_c_to_c_to_u) // success
|
||||
|
||||
let vl1 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
|
||||
let vl2 : dt_contra_twice(Animal) = DT_CONTRA_TWICE(f_a_to_c_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // fail
|
||||
let vl3 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_a_to_a_to_u : Animal => Animal => unit) : dt_contra_twice(Animal) // success
|
||||
let vl4 : dt_contra_twice(Cat) = DT_CONTRA_TWICE(f_c_to_a_to_u : Cat => Cat => unit) : dt_contra_twice(Cat) // success
|
||||
|
||||
()
|
||||
@@ -0,0 +1,47 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
entrypoint oracle() = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
|
||||
entrypoint query() = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
|
||||
entrypoint init() =
|
||||
let o01 : oracle(Animal, Animal) = oracle() : oracle(Animal, Animal) // success
|
||||
let o02 : oracle(Animal, Animal) = oracle() : oracle(Animal, Cat) // success
|
||||
let o03 : oracle(Animal, Animal) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o04 : oracle(Animal, Animal) = oracle() : oracle(Cat, Cat) // fail
|
||||
let o05 : oracle(Animal, Cat) = oracle() : oracle(Animal, Animal) // fail
|
||||
let o06 : oracle(Animal, Cat) = oracle() : oracle(Animal, Cat) // success
|
||||
let o07 : oracle(Animal, Cat) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o08 : oracle(Animal, Cat) = oracle() : oracle(Cat, Cat) // fail
|
||||
let o09 : oracle(Cat, Animal) = oracle() : oracle(Animal, Animal) // success
|
||||
let o10 : oracle(Cat, Animal) = oracle() : oracle(Animal, Cat) // success
|
||||
let o11 : oracle(Cat, Animal) = oracle() : oracle(Cat, Animal) // success
|
||||
let o12 : oracle(Cat, Animal) = oracle() : oracle(Cat, Cat) // success
|
||||
let o13 : oracle(Cat, Cat) = oracle() : oracle(Animal, Animal) // fail
|
||||
let o14 : oracle(Cat, Cat) = oracle() : oracle(Animal, Cat) // success
|
||||
let o15 : oracle(Cat, Cat) = oracle() : oracle(Cat, Animal) // fail
|
||||
let o16 : oracle(Cat, Cat) = oracle() : oracle(Cat, Cat) // success
|
||||
|
||||
let q01 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Animal) // success
|
||||
let q02 : oracle_query(Animal, Animal) = query() : oracle_query(Animal, Cat) // success
|
||||
let q03 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Animal) // success
|
||||
let q04 : oracle_query(Animal, Animal) = query() : oracle_query(Cat, Cat) // success
|
||||
let q05 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q06 : oracle_query(Animal, Cat) = query() : oracle_query(Animal, Cat) // success
|
||||
let q07 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Animal) // fail
|
||||
let q08 : oracle_query(Animal, Cat) = query() : oracle_query(Cat, Cat) // success
|
||||
let q09 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q10 : oracle_query(Cat, Animal) = query() : oracle_query(Animal, Cat) // fail
|
||||
let q11 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Animal) // success
|
||||
let q12 : oracle_query(Cat, Animal) = query() : oracle_query(Cat, Cat) // success
|
||||
let q13 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Animal) // fail
|
||||
let q14 : oracle_query(Cat, Cat) = query() : oracle_query(Animal, Cat) // fail
|
||||
let q15 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Animal) // fail
|
||||
let q16 : oracle_query(Cat, Cat) = query() : oracle_query(Cat, Cat) // success
|
||||
|
||||
()
|
||||
@@ -0,0 +1,51 @@
|
||||
contract interface Animal =
|
||||
entrypoint sound : () => string
|
||||
|
||||
contract Cat : Animal =
|
||||
entrypoint sound() = "meow"
|
||||
|
||||
main contract Main =
|
||||
record rec_co('a) = { x : 'a ,
|
||||
y : () => 'a }
|
||||
record rec_contra('a) = { x : 'a => unit }
|
||||
record rec_inv('a) = { x : 'a => unit,
|
||||
y : () => 'a }
|
||||
record rec_biv('a) = { x : int }
|
||||
|
||||
stateful entrypoint new_cat() : Cat = Chain.create()
|
||||
stateful entrypoint new_animal() : Animal = new_cat()
|
||||
stateful entrypoint animal_to_unit(_ : Animal) : unit = ()
|
||||
stateful entrypoint cat_to_unit(_ : Cat) : unit = ()
|
||||
stateful entrypoint unit_to_animal() : Animal = new_animal()
|
||||
stateful entrypoint unit_to_cat() : Cat = new_cat()
|
||||
|
||||
stateful entrypoint init() =
|
||||
let ra : rec_co(Animal) = { x = new_animal(), y = unit_to_animal }
|
||||
let rc : rec_co(Cat) = { x = new_cat(), y = unit_to_cat }
|
||||
let r01 : rec_co(Animal) = ra // success
|
||||
let r02 : rec_co(Animal) = rc // success
|
||||
let r03 : rec_co(Cat) = ra // fail
|
||||
let r04 : rec_co(Cat) = rc // sucess
|
||||
|
||||
let ratu : rec_contra(Animal) = { x = animal_to_unit }
|
||||
let rctu : rec_contra(Cat) = { x = cat_to_unit }
|
||||
let r05 : rec_contra(Animal) = ratu // success
|
||||
let r06 : rec_contra(Animal) = rctu // fail
|
||||
let r07 : rec_contra(Cat) = ratu // success
|
||||
let r08 : rec_contra(Cat) = rctu // success
|
||||
|
||||
let rxaya : rec_inv(Animal) = { x = animal_to_unit, y = unit_to_animal }
|
||||
let rxcyc : rec_inv(Cat) = { x = cat_to_unit, y = unit_to_cat }
|
||||
let r09 : rec_inv(Animal) = rxaya // success
|
||||
let r10 : rec_inv(Animal) = rxcyc // fail
|
||||
let r11 : rec_inv(Cat) = rxaya // fail
|
||||
let r12 : rec_inv(Cat) = rxcyc // success
|
||||
|
||||
let rba : rec_biv(Animal) = { x = 1 }
|
||||
let rbc : rec_biv(Cat) = { x = 1 }
|
||||
let r13 : rec_biv(Animal) = rba // success
|
||||
let r14 : rec_biv(Animal) = rbc // success
|
||||
let r15 : rec_biv(Cat) = rba // success
|
||||
let r16 : rec_biv(Cat) = rbc // success
|
||||
|
||||
()
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
contract MapServer =
|
||||
|
||||
function insert : (string, string, map(string, string)) => map(string, string)
|
||||
function delete : (string, map(string, string)) => map(string, string)
|
||||
|
||||
contract PrimitiveMaps =
|
||||
|
||||
record state = { remote : MapServer,
|
||||
map : map(string, string),
|
||||
map2 : map(string, string) }
|
||||
|
||||
function init(r) =
|
||||
let m = {}
|
||||
{ remote = r, map = m, map2 = m }
|
||||
|
||||
function set_remote(r) = put(state{ remote = r })
|
||||
|
||||
function insert(k, v, m) : map(string, string) = m{ [k] = v }
|
||||
function delete(k, m) : map(string, string) = Map.delete(k, m)
|
||||
|
||||
function remote_insert(k, v, m) =
|
||||
state.remote.insert(k, v, m)
|
||||
|
||||
function remote_delete(k, m) =
|
||||
state.remote.delete(k, m)
|
||||
|
||||
function get_state_map() = state.map
|
||||
function set_state_map(m) = put(state{ map = m })
|
||||
|
||||
function clone_state() = put(state{ map2 = state.map })
|
||||
|
||||
function insert_state(k, v) = put(state{ map @ m = m { [k] = v } })
|
||||
function delete_state(k) = put(state{ map @ m = Map.delete(k, m) })
|
||||
function lookup_state(k) = Map.lookup(k, state.map)
|
||||
|
||||
function double_insert_state(k, v1, v2) =
|
||||
put(state{ map @ m = m { [k] = v1 },
|
||||
map2 @ m = m { [k] = v2 } })
|
||||
|
||||
function test() =
|
||||
let m = {} : map(string, string)
|
||||
let m1 = m { ["foo"] = "value_of_foo",
|
||||
["bla"] = "value_of_bla" }
|
||||
let m2 = Map.delete("foo", m1)
|
||||
let m3 = m2 { ["bla"] = "new_value_of_bla" }
|
||||
[Map.lookup("foo", m), Map.lookup("bla", m),
|
||||
Map.lookup("foo", m1), Map.lookup("bla", m1),
|
||||
Map.lookup("foo", m2), Map.lookup("bla", m2),
|
||||
Map.lookup("foo", m3), Map.lookup("bla", m3)]
|
||||
|
||||
function return_map() =
|
||||
Map.delete("goo", {["foo"] = "bar", ["goo"] = "gaa"})
|
||||
|
||||
function argument_map(m : map(string, string)) =
|
||||
m["foo"]
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
contract interface Coin =
|
||||
entrypoint mint : () => int
|
||||
|
||||
contract interface OtherCoin =
|
||||
entrypoint mint : () => int
|
||||
|
||||
main contract Main =
|
||||
function mkCoin() : Coin = ct_11111111111111111111111111111115rHyByZ
|
||||
entrypoint foo() : int =
|
||||
let r = mkCoin()
|
||||
r.mint()
|
||||
@@ -1,20 +0,0 @@
|
||||
contract Remote1 =
|
||||
function set : (int) => int
|
||||
|
||||
contract RemoteCall =
|
||||
record state = { i : int }
|
||||
|
||||
function init(x) = { i = x }
|
||||
|
||||
function set( x : int) : int =
|
||||
let old = state.i
|
||||
put(state{ i = x })
|
||||
old
|
||||
|
||||
function call(r : Remote1, x : int, g : int) : int =
|
||||
r.set(gas = g, value = 10, x)
|
||||
|
||||
function get() = state.i
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
contract RemoteState =
|
||||
record rstate = { i : int, s : string, m : map(int, int) }
|
||||
|
||||
function look_at(s : rstate) = ()
|
||||
function return_s(big : bool) =
|
||||
let x = "short"
|
||||
let y = "______longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long___________longer_string_at_least_32_bytes_long_____"
|
||||
if(big) y else x
|
||||
function return_m(big : bool) =
|
||||
let x = { [1] = 2 }
|
||||
let y = { [1] = 2, [3] = 4, [5] = 6 }
|
||||
if(big) y else x
|
||||
|
||||
function get(s : rstate) = s
|
||||
function get_i(s : rstate) = s.i
|
||||
function get_s(s : rstate) = s.s
|
||||
function get_m(s : rstate) = s.m
|
||||
|
||||
function fun_update_i(s : rstate, ni) = s{ i = ni }
|
||||
function fun_update_s(s : rstate, ns) = s{ s = ns }
|
||||
function fun_update_m(s : rstate, nm) = s{ m = nm }
|
||||
function fun_update_mk(s : rstate, k, v) = s{ m = s.m{[k] = v} }
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
contract Remote =
|
||||
function id : ('a) => 'a
|
||||
function missing : ('a) => 'a
|
||||
function wrong_type : (string) => string
|
||||
|
||||
contract Main =
|
||||
|
||||
function id(x : int) =
|
||||
x
|
||||
|
||||
function wrong_type(x : int) =
|
||||
x
|
||||
|
||||
function remote_id(r : Remote, x) =
|
||||
r.id(x)
|
||||
|
||||
function remote_missing(r : Remote, x) =
|
||||
r.missing(x)
|
||||
|
||||
function remote_wrong_type(r : Remote, x) =
|
||||
r.wrong_type(x)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
contract ValueOnErr =
|
||||
function err : () => int
|
||||
function ok : () => int
|
||||
|
||||
contract RemoteValueOnErr =
|
||||
|
||||
public function callErr(
|
||||
r : ValueOnErr,
|
||||
value : int) : int =
|
||||
r.err(value = value)
|
||||
|
||||
public function callErrLimitGas(
|
||||
r : ValueOnErr,
|
||||
value : int,
|
||||
gas : int) : int =
|
||||
r.err(value = value, gas = gas)
|
||||
|
||||
public function callOk(
|
||||
r : ValueOnErr,
|
||||
value : int) : int =
|
||||
r.ok(value = value)
|
||||
@@ -9,4 +9,4 @@ contract IntegerHolder =
|
||||
entrypoint get() = state
|
||||
|
||||
main contract Test =
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Builtins without named arguments can appear unapplied in both AEVM and FATE.
|
||||
// Builtins without named arguments can appear unapplied.
|
||||
// Named argument builtins are:
|
||||
// Oracle.register
|
||||
// Oracle.respond
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
contract UpfrontCharges =
|
||||
record state = { b : int } // For enabling retrieval of sender balance observed inside init.
|
||||
public function init() : state = { b = b() }
|
||||
public function initialSenderBalance() : int = state.b
|
||||
public function senderBalance() : int = b()
|
||||
private function b() = Chain.balance(Call.origin)
|
||||
@@ -1,7 +0,0 @@
|
||||
contract ValueOnErr =
|
||||
|
||||
public function err() : int =
|
||||
switch(0) 1 => 5
|
||||
|
||||
public function ok() : int =
|
||||
11
|
||||
@@ -0,0 +1,60 @@
|
||||
// Used include
|
||||
include "Pair.aes"
|
||||
// Unused include
|
||||
include "Triple.aes"
|
||||
|
||||
namespace UnusedNamespace =
|
||||
function f() = 1 + g()
|
||||
|
||||
// Used in f
|
||||
private function g() = 2
|
||||
|
||||
// Unused
|
||||
private function h() = 3
|
||||
|
||||
contract Warnings =
|
||||
|
||||
type state = int
|
||||
|
||||
type unused_type = bool
|
||||
|
||||
entrypoint init(p) = Pair.fst(p) + Pair.snd(p)
|
||||
|
||||
stateful entrypoint negative_spend(to : address) = Chain.spend(to, -1)
|
||||
|
||||
entrypoint shadowing() =
|
||||
let x = 1
|
||||
let x = 2
|
||||
x
|
||||
|
||||
entrypoint division_by_zero(x) = x / 0
|
||||
|
||||
stateful entrypoint unused_stateful() = 1
|
||||
stateful entrypoint used_stateful(x : int) = put(x)
|
||||
|
||||
entrypoint unused_variables(unused_arg : int) =
|
||||
let unused_var = 10
|
||||
let z = 20
|
||||
z
|
||||
|
||||
// Unused functions
|
||||
function unused_function() = ()
|
||||
function recursive_unused_function() = recursive_unused_function()
|
||||
function called_unused_function1() = called_unused_function2()
|
||||
function called_unused_function2() = called_unused_function1()
|
||||
|
||||
function rv() = 1
|
||||
entrypoint unused_return_value() =
|
||||
rv()
|
||||
2
|
||||
|
||||
namespace FunctionsAsArgs =
|
||||
function f() = g()
|
||||
|
||||
private function g() = h(inc)
|
||||
private function h(fn : (int => int)) = fn(1)
|
||||
|
||||
// Passed as arg to h in g
|
||||
private function inc(n : int) : int = n + 1
|
||||
// Never used
|
||||
private function dec(n : int) : int = n - 1
|
||||
Reference in New Issue
Block a user