Compare commits

..

1 Commits

Author SHA1 Message Date
radrow a5cbf2fd79 Updated CHANGELOG 2020-03-23 12:48:47 +01:00
39 changed files with 253 additions and 651 deletions
+10 -25
View File
@@ -6,28 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Added documentation (actually, moved from `protocol`)
- Added standard library docs. Moved builtin docs from `sophia.md` to
`sophia_stdlib.md`
- Added library for rational numbers
### Changed
- Optimized `List.aes` library
### Removed
## [4.3.0]
### Added
- Added documentation (moved from `protocol`)
- `Frac.aes` library for rational numbers
- Added some more meaningful error messages
- Exported several parsing functionalities
- With option `keep_included` it is possible to see which files were included during the parse
- There is a function `run_parser` that be used to evaluate any parsing rule
- Exported parsers: `body`, `type` and `decl`
### Changed
- Performance improvements in the standard library
- Fixed ACI encoder to handle `-` unary operator
- Fixed including by absolute path
- Fixed variant type printing in the ACI error messages
- Fixed pretty printing of combined function clauses
### Removed
- `let` definitions are no longer supported in the toplevel of the contract
- type declarations are no longer supported
## [4.2.0] - 2020-01-15
### Added
- Allow separate entrypoint/function type signature and definition, and pattern
@@ -72,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(de-)construct byte arrays.
- `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
- [Standard libraries](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md)
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
- Checks that `init` is not called from other functions.
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
@@ -211,11 +197,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD
[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0
[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0
[4.0.0]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.2.0...HEAD
[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.1.0
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.0.0
[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
+8 -5
View File
@@ -1,11 +1,12 @@
# aesophia
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code.
It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system.
The compiler is currently being used three places
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
## Documentation
@@ -13,6 +14,7 @@ The compiler is currently being used three places
* [Sophia Documentation](docs/sophia.md).
* [Sophia Standard Library](docs/sophia_stdlib.md).
## Versioning
`aesophia` has a version that is only loosely connected to the version of the
@@ -21,6 +23,7 @@ minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc.
## Interface Modules
The basic modules for interfacing the compiler:
+14 -21
View File
@@ -2,6 +2,7 @@
**Table of Contents**
- [-](#-)
- [Language Features](#language-features)
- [Contracts](#contracts)
- [Calling other contracts](#calling-other-contracts)
@@ -52,7 +53,8 @@
- [Operators types](#operators-types)
- [Operator precendences](#operator-precendences)
- [Examples](#examples)
- [The lifetime of a contract](#the-lifetime-of-a-contract)
- [Killing a contract](#killing-a-contract)
## The Sophia Language
An Æternity BlockChain Language
@@ -444,13 +446,6 @@ Example syntax:
// yields [12,13,14,20,21,22,30,31,32]
```
Lists can be constructed using the range syntax using special `..` operator:
```
[1..4] == [1,2,3,4]
```
The ranges are always ascending and have step equal to 1.
Please refer to the [standard library](sophia_stdlib.md#List) for the predefined functionalities.
### Maps and records
@@ -587,7 +582,7 @@ Example for an oracle answering questions of type `string` with answers of type
contract Oracles =
stateful entrypoint registerOracle(acct : address,
sign : signature, // Signed network id + oracle address + contract address
sign : signature, // Signed oracle address + contract address
qfee : int,
ttl : Chain.ttl) : oracle(string, int) =
Oracle.register(acct, signature = sign, qfee, ttl)
@@ -608,13 +603,13 @@ contract Oracles =
Oracle.extend(o, ttl)
stateful entrypoint signExtendOracle(o : oracle(string, int),
sign : signature, // Signed network id + oracle address + contract address
ttl : Chain.ttl) : unit =
sign : signature, // Signed oracle address + contract address
ttl : Chain.ttl) : unit =
Oracle.extend(o, signature = sign, ttl)
stateful entrypoint respond(o : oracle(string, int),
q : oracle_query(string, int),
sign : signature, // Signed network id + oracle query id + contract address
sign : signature, // Signed oracle query id + contract address
r : int) =
Oracle.respond(o, q, signature = sign, r)
@@ -830,17 +825,15 @@ In describing the syntax below, we use the following conventions:
A Sophia file consists of a sequence of *declarations* in a layout block.
```c
File ::= Block(TopDecl)
TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
File ::= Block(Decl)
Decl ::= ['payable'] 'contract' Con '=' Block(Decl)
| 'namespace' Con '=' Block(Decl)
| '@compiler' PragmaOp Version
| 'include' String
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
| 'type' Id ['(' TVar* ')'] ['=' TypeAlias]
| 'record' Id ['(' TVar* ')'] '=' RecordType
| 'datatype' Id ['(' TVar* ')'] '=' DataType
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
| EModifier* ('entrypoint' | 'function') Block(FunDecl)
FunDecl ::= Id ':' Type // Type signature
| Id Args [':' Type] '=' Block(Stmt) // Definition
@@ -952,7 +945,6 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
| '[' Sep(Expr, ',') ']' // List [1, 2, 3]
| '[' Expr '|' Sep(Generator, ',') ']'
// List comprehension [k | x <- [1], if (f(x)), let k = x+1]
| '[' Expr '..' Expr ']' // List range [1..n]
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
| '(' Expr ')' // Parens (1 + 2) * 3
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
@@ -1020,8 +1012,8 @@ contract FundMe =
deadline : int,
goal : int }
stateful function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount)
function spend(args : spend_args) =
raw_spend(args.recipient, args.amount)
entrypoint init(beneficiary, deadline, goal) : state =
{ contributions = {},
@@ -1060,6 +1052,7 @@ contract FundMe =
require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary,
amount = Contract.balance })
put(state{ beneficiary = #0 })
stateful function withdraw_contributor() =
if(state.total >= state.goal)
+10 -33
View File
@@ -369,7 +369,7 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`.
* The `acct` is the address of the oracle to register (can be the same as the contract).
* `signature` is a signature proving that the contract is allowed to register the account -
the `network id` + `account address` + `contract address` (concatenated as byte arrays) is
the account address + the contract address (concatenated as byte arrays) is
signed with the
private key of the account, proving you have the private key of the oracle to be. If the
address is the same as the contract `sign` is ignored and can be left out entirely.
@@ -403,7 +403,7 @@ Responds to the question `q` on `o`.
Unless the contract address is the same as the oracle address the `signature`
(which is an optional, named argument)
needs to be provided. Proving that we have the private key of the oracle by
signing the `network id` + `oracle query id` + `contract address`
signing the oracle query id + contract address
### extend
@@ -468,8 +468,7 @@ Naming System (AENS).
If `owner` is equal to `Contract.address` the signature `signature` is
ignored, and can be left out since it is a named argument. Otherwise we need
a signature to prove that we are allowed to do AENS operations on behalf of
`owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature),
i.e. the signature material should be prefixed by the network id.
`owner`
### resolve
```
@@ -487,7 +486,7 @@ type checked against this type at run time.
AENS.preclaim(owner : address, commitment_hash : hash, <signature : signature>) : unit
```
The signature should be over `network id` + `owner address` + `Contract.address`
The signature should be over `owner address` + `Contract.address`
(concatenated as byte arrays).
@@ -496,29 +495,29 @@ The signature should be over `network id` + `owner address` + `Contract.address`
AENS.claim(owner : address, name : string, salt : int, name_fee : int, <signature : signature>) : unit
```
The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
The signature should be over `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing.
### transfer
```
AENS.transfer(owner : address, new_owner : address, name : string, <signature : signature>) : unit
AENS.transfer(owner : address, new_owner : address, name_hash : hash, <signature : signature>) : unit
```
Transfers name to the new owner.
The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
The signature should be over `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing.
### revoke
```
AENS.revoke(owner : address, name : string, <signature : signature>) : unit
AENS.revoke(owner : address, name_hash : hash, <signature : signature>) : unit
```
Revokes the name to extend the ownership time.
The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address`
The signature should be over `owner address` + `name_hash` + `Contract.address`
using the private key of the `owner` account for signing.
@@ -700,13 +699,6 @@ List.last(l : list('a)) : option('a)
Returns `Some` of the last element of a list or `None` if the list is empty.
### contains
```
List.contains(e : 'a, l : list('a)) : bool
```
Checks if list `l` contains element `e`. Equivalent to `List.find(x => x == e, l) != None`.
### find
```
List.find(p : 'a => bool, l : list('a)) : option('a)
@@ -975,20 +967,12 @@ List.unzip(l : list('a * 'b)) : list('a) * list('b)
Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices.
### merge
```
List.merge(lesser_cmp : ('a, 'a) => bool, l1 : list('a), l2 : list('a)) : list('a)
```
Merges two sorted lists into a single sorted list. O(length(l1) + length(l2))
### sort
```
List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a)
```
Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. O(length(l) * log_2(length(l))).
Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2).
### intersperse
@@ -1055,13 +1039,6 @@ Option.force(o : option('a)) : 'a
Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`.
### contains
```
Option.contains(e : 'a, o : option('a)) : bool
```
Returns `true` if and only if `o` contains element equal to `e`. Equivalent to `Option.match(false, x => x == e, o)`.
### on_elem
```
Option.on_elem(o : option('a), f : 'a => unit) : unit
+7 -14
View File
@@ -56,14 +56,11 @@ namespace Frac =
/** Integer to rational division
*/
function make_frac(n : int, d : int) : frac =
if (d == 0) abort("Zero denominator")
if (d == 0) abort("Division by zero")
elif (n == 0) Zero
elif ((n < 0) == (d < 0)) simplify(Pos(abs_int(n), abs_int(d)))
else simplify(Neg(abs_int(n), abs_int(d)))
function one() : frac = Pos(1, 1)
function zero() : frac = Zero
function eq(a : frac, b : frac) : bool =
let (na, da) = to_pair(a)
let (nb, db) = to_pair(b)
@@ -149,10 +146,7 @@ namespace Frac =
function mul(a : frac, b : frac) : frac = make_frac(num(a) * num(b), den(a) * den(b))
function div(a : frac, b : frac) : frac = switch(b)
Neg(n, d) => mul(a, Neg(d, n))
Zero => abort("Division by zero")
Pos(n, d) => mul(a, Pos(d, n))
function div(a : frac, b : frac) : frac = mul(a, inv(b))
/** `b` to the power of `e`
*/
@@ -174,10 +168,9 @@ namespace Frac =
function optimize(f : frac, loss : frac) : frac =
require(geq(loss, Zero), "negative loss optimize")
let s = sign(f)
mul(from_int(s), run_optimize(abs(f), abs(f), loss))
private function run_optimize(orig : frac, f : frac, loss : frac) : frac =
let (n, d) = to_pair(f)
let t = make_frac((n+1)/2, (d+1)/2)
if(gt(abs(sub(t, orig)), loss)) f
mul(from_int(s), run_optimize(abs(f), loss))
private function run_optimize(f : frac, loss : frac) : frac =
let t = make_frac((num(f) + 1) / 2, (den(f) + 1)/2)
if(gt(abs(sub(t, f)), loss)) f
elif (eq(t, f)) f
else run_optimize(orig, t, loss)
else run_optimize(t, loss)
+7 -67
View File
@@ -15,14 +15,10 @@ namespace List =
_::t => Some(t)
function last(l : list('a)) : option('a) = switch(l)
[] => None
[x] => Some(x)
[] => None
[x] => Some(x)
_::t => last(t)
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists.
*/
@@ -227,67 +223,11 @@ namespace List =
(left, right)::t => unzip_(t, left::acc_l, right::acc_r)
/** Merges two sorted lists using `lt` comparator
*/
function
merge : (('a, 'a) => bool, list('a), list('a)) => list('a)
merge(lt, x::xs, y::ys) =
if(lt(x, y)) x::merge(lt, xs, y::ys)
else y::merge(lt, x::xs, ys)
merge(_, [], ys) = ys
merge(_, xs, []) = xs
/** Mergesort inspired by
* https://hackage.haskell.org/package/base-4.14.1.0/docs/src/Data.OldList.html#sort
*/
function
sort : (('a, 'a) => bool, list('a)) => list('a)
sort(_, []) = []
sort(lt, l) =
merge_all(lt, monotonic_subs(lt, l))
/** Splits list into compound increasing sublists
*/
private function
monotonic_subs : (('a, 'a) => bool, list('a)) => list(list('a))
monotonic_subs(lt, x::y::rest) =
if(lt(y, x)) desc(lt, y, [x], rest)
else asc(lt, y, [x], rest)
monotonic_subs(_, l) = [l]
/** Extracts the longest descending prefix and proceeds with monotonic split
*/
private function
desc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
desc(lt, x, acc, h::t) =
if(lt(x, h)) (x::acc) :: monotonic_subs(lt, h::t)
else desc(lt, h, x::acc, t)
desc(_, x, acc, []) = [x::acc]
/** Extracts the longest ascending prefix and proceeds with monotonic split
*/
private function
asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
asc(lt, x, acc, h::t) =
if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t)
else asc(lt, h, x::acc, t)
asc(_, x, acc, []) = [List.reverse(x::acc)]
/** Merges list of sorted lists
*/
private function
merge_all : (('a, 'a) => bool, list(list('a))) => list('a)
merge_all(_, [part]) = part
merge_all(lt, parts) = merge_all(lt, merge_pairs(lt, parts))
/** Single round of `merge_all` pairs of lists in a list of list
*/
private function
merge_pairs : (('a, 'a) => bool, list(list('a))) => list(list('a))
merge_pairs(lt, x::y::rest) = merge(lt, x, y) :: merge_pairs(lt, rest)
merge_pairs(_, l) = l
// TODO: Improve?
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => switch (partition((x) => lesser_cmp(x, h), t))
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
/** Puts `delim` between every two members of the list
*/
+1 -5
View File
@@ -22,11 +22,7 @@ namespace Option =
/** Assume it is `Some`
*/
function force(o : option('a)) : 'a = switch(o)
None => abort("Forced None value")
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function force(o : option('a)) : 'a = default(abort("Forced None value"), o)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
+1 -1
View File
@@ -15,7 +15,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "4.3.0"},
{relx, [{release, {aesophia, "4.2.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+7 -15
View File
@@ -14,8 +14,6 @@
, contract_interface/2
, contract_interface/3
, from_typed_ast/2
, render_aci_json/1
, json_encode_expr/1
@@ -25,8 +23,6 @@
-type json() :: jsx:json_term().
-type json_text() :: binary().
-export_type([aci_type/0]).
%% External API
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
file(Type, File) ->
@@ -69,20 +65,18 @@ do_contract_interface(Type, ContractString, Options) ->
try
Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
TypedAst = aeso_ast_infer_types:infer(Ast, [dont_unfold]),
%% io:format("~p\n", [TypedAst]),
from_typed_ast(Type, TypedAst)
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end
catch
throw:{error, Errors} -> {error, Errors}
end.
from_typed_ast(Type, TypedAst) ->
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)},
@@ -200,8 +194,6 @@ encode_expr({bytes, _, B}) ->
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, {'-', _}, [{int, _, N}]}) ->
encode_expr({int, [], -N});
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
+33 -95
View File
@@ -12,11 +12,7 @@
-module(aeso_ast_infer_types).
-export([ infer/1
, infer/2
, unfold_types_in_type/3
, pp_type/2
]).
-export([infer/1, infer/2, unfold_types_in_type/3]).
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()}
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
@@ -553,21 +549,17 @@ global_env() ->
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
-spec infer(aeso_syntax:ast()) -> {aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
-spec infer(aeso_syntax:ast()) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
infer(Contracts) ->
infer(Contracts, []).
-type option() :: return_env | dont_unfold | no_code | debug_mode | term().
-type option() :: return_env | dont_unfold | no_code | term().
-spec init_env(list(option())) -> env().
init_env(_Options) -> global_env().
-spec infer(aeso_syntax:ast(), list(option())) ->
{aeso_syntax:ast(), aeso_syntax:ast()} | {env(), aeso_syntax:ast(), aeso_syntax:ast()}.
infer([], Options) ->
create_type_errors(),
type_error({no_decls, proplists:get_value(src_file, Options, no_file)}),
destroy_and_report_type_errors(init_env(Options));
aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
infer(Contracts, Options) ->
ets_init(), %% Init the ETS table state
try
@@ -576,15 +568,15 @@ infer(Contracts, Options) ->
ets_new(type_vars, [set]),
check_modifiers(Env, Contracts),
{Env1, Decls} = infer1(Env, Contracts, [], Options),
{Env2, DeclsFolded, DeclsUnfolded} =
{Env2, Decls2} =
case proplists:get_value(dont_unfold, Options, false) of
true -> {Env1, Decls, Decls};
true -> {Env1, Decls};
false -> E = on_scopes(Env1, fun(Scope) -> unfold_record_types(Env1, Scope) end),
{E, Decls, unfold_record_types(E, Decls)}
{E, unfold_record_types(E, Decls)}
end,
case proplists:get_value(return_env, Options, false) of
false -> {DeclsFolded, DeclsUnfolded};
true -> {Env2, DeclsFolded, DeclsUnfolded}
false -> Decls2;
true -> {Env2, Decls2}
end
after
clean_up_ets()
@@ -622,23 +614,15 @@ check_scope_name_clash(Env, Kind, Name) ->
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
{env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, Options) ->
create_type_errors(),
infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0),
destroy_and_report_type_errors(Env),
infer_contract(Env, Kind, Defs, Options).
infer_contract(Env, Kind, Defs).
%% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions.
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0, Options) ->
create_type_errors(),
Defs01 = process_blocks(Defs0),
Defs = case lists:member(debug_mode, Options) of
true -> expose_internals(Defs01, What);
false -> Defs01
end,
destroy_and_report_type_errors(Env0),
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
infer_contract(Env0, What, Defs0) ->
Defs = process_blocks(Defs0),
Env = Env0#env{ what = What },
Kind = fun({type_def, _, _, _, _}) -> type;
({letfun, _, _, _, _, _}) -> function;
@@ -685,39 +669,20 @@ process_blocks(Decls) ->
-spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()].
process_block(_, []) -> [];
process_block(_, [Decl]) -> [Decl];
process_block(_Ann, [Decl | Decls]) ->
process_block(Ann, [Decl | Decls]) ->
IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1;
(_) -> false end end,
case Decl of
{fun_decl, Ann1, Id = {id, _, Name}, Type} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), Decls),
[type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
[{fun_clauses, Ann1, Id, Type, Clauses}];
[{fun_clauses, Ann1, Id, Type, Clauses} |
process_block(Ann, Rest)];
{letfun, Ann1, Id = {id, _, Name}, _, _, _} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]),
[type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
[{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]
[{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses} |
process_block(Ann, Rest)]
end.
%% Turns private stuff into public stuff
expose_internals(Defs, What) ->
[ begin
Ann = element(2, Def),
NewAnn = case What of
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
main_contract -> [{entrypoint, true}|Ann]; % minor duplication
contract -> Ann
end,
Def1 = setelement(2, Def, NewAnn),
case Def1 of % fix inner clauses
{fun_clauses, Ans, Id, T, Clauses} ->
{fun_clauses, Ans, Id, T, expose_internals(Clauses, What)};
_ -> Def1
end
end
|| Def <- Defs
].
-spec check_typedefs(env(), [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
check_typedefs(Env = #env{ namespace = Ns }, Defs) ->
create_type_errors(),
@@ -2297,30 +2262,6 @@ mk_t_err(Pos, Msg) ->
mk_t_err(Pos, Msg, Ctxt) ->
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
mk_error({no_decls, File}) ->
Pos = aeso_errors:pos(File, 0, 0),
mk_t_err(Pos, "Empty contract\n");
mk_error({mismatched_decl_in_funblock, Name, Decl}) ->
Msg = io_lib:format("Mismatch in the function block. Expected implementation/type declaration of ~s function\n", [Name]),
mk_t_err(pos(Decl), Msg);
mk_error({higher_kinded_typevar, T}) ->
Msg = io_lib:format("Type ~s is a higher kinded type variable\n"
"(takes another type as an argument)\n", [pp(instantiate(T))]
),
mk_t_err(pos(T), Msg);
mk_error({wrong_type_arguments, X, ArityGiven, ArityReal}) ->
Msg = io_lib:format("Arity for ~s doesn't match. Expected ~p, got ~p\n"
, [pp(instantiate(X)), ArityReal, ArityGiven]
),
mk_t_err(pos(X), Msg);
mk_error({unnamed_map_update_with_default, Upd}) ->
Msg = "Invalid map update with default\n",
mk_t_err(pos(Upd), Msg);
mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) ->
Msg = io_lib:format("~s at ~s was declared with an invalid type ~s.\n"
"Entrypoints and functions must have functional types"
, [pp(Id), pp_loc(Id), pp(instantiate(Type))]),
mk_t_err(pos(Id), Msg);
mk_error({cannot_unify, A, B, When}) ->
Msg = io_lib:format("Cannot unify ~s\n and ~s\n",
[pp(instantiate(A)), pp(instantiate(B))]),
@@ -2403,6 +2344,14 @@ mk_error({indexed_type_must_be_word, Type, Type1}) ->
Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n",
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
mk_t_err(pos(Type), Msg);
mk_error({payload_type_must_be_string, Type, Type}) ->
Msg = io_lib:format("The payload type ~s (at ~s) should be string\n",
[pp_type("", Type), pp_loc(Type)]),
mk_t_err(pos(Type), Msg);
mk_error({payload_type_must_be_string, Type, Type1}) ->
Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n",
[pp_type("", Type), pp_loc(Type), pp_type("", Type1)]),
mk_t_err(pos(Type), Msg);
mk_error({event_0_to_3_indexed_values, Constr}) ->
Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n",
[name(Constr), pp_loc(Constr)]),
@@ -2446,21 +2395,13 @@ mk_error({include, _, {string, Pos, Name}}) ->
[binary_to_list(Name), pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg);
mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg);
mk_error({contract, _Pos, {con, Pos, Name}, _Def}) ->
Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg);
mk_error({type_decl, _, {id, Pos, Name}, _}) ->
Msg = io_lib:format("Empty type declarations are not supported\nType ~s at ~s lacks a definition\n",
[Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg);
mk_error({letval, _Pos, {id, Pos, Name}, _Def}) ->
Msg = io_lib:format("Toplevel \"let\" definitions are not supported\nValue ~s at ~s could be replaced by 0-argument function\n",
Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
[Name, pp_loc(Pos)]),
mk_t_err(pos(Pos), Msg);
mk_error({repeated_arg, Fun, Arg}) ->
Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
[Arg, pp(Fun), pp_loc(Fun)]),
mk_t_err(pos(Fun), Msg);
mk_error({stateful_not_allowed, Id, Fun}) ->
Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
[pp(Id), pp_loc(Id), pp(Fun)]),
@@ -2588,9 +2529,6 @@ mk_error({mixed_record_and_map, Expr}) ->
Msg = io_lib:format("Mixed record fields and map keys in\n~s",
[pp_expr(" ", Expr)]),
mk_t_err(pos(Expr), Msg);
mk_error({conflicting_updates_for_field, Upd, Key}) ->
Msg = io_lib:format("Conflicting updates for field '~s'\n", [Key]),
mk_t_err(pos(Upd), Msg);
mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
mk_t_err(pos(0, 0), Msg).
@@ -2813,7 +2751,7 @@ desugar_updates([Upd | Updates]) ->
{More, Updates1} = updates_key(Key, Updates),
%% Check conflicts
case length([ [] || [] <- [Rest | More] ]) of
N when N > 1 -> type_error({conflicting_updates_for_field, Upd, Key});
N when N > 1 -> error({conflicting_updates_for_field, Upd, Key});
_ -> ok
end,
[MakeField(lists:append([Rest | More])) | desugar_updates(Updates1)].
+1
View File
@@ -291,6 +291,7 @@ decls_to_fcode(Env, Decls) ->
end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of
false -> fcode_error({missing_definition, Id});
+41 -62
View File
@@ -38,13 +38,10 @@
| pp_assembler
| pp_bytecode
| no_code
| keep_included
| debug_mode
| {backend, aevm | fate}
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}
| {aci, aeso_aci:aci_type()}.
| {src_file, string()}.
-type options() :: [option()].
-export_type([ option/0
@@ -118,8 +115,7 @@ from_string(Backend, ContractString, Options) ->
end.
from_string1(aevm, ContractString, Options) ->
#{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
#{icode := Icode} = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options),
@@ -127,63 +123,47 @@ from_string1(aevm, ContractString, 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)};
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode)
}};
from_string1(fate, ContractString, Options) ->
#{ fcode := FCode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
#{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
undefined ->
Result;
Type ->
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
maps:put(aci, Aci, Result)
end.
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
}}.
-spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(UnfoldedTypedAst, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(UnfoldedTypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
#{ icode => Icode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
#{ icode => Icode,
typed_ast => TypedAst,
type_env => TypeEnv};
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
#{ fcode => Fcode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
#{ fcode => Fcode,
typed_ast => TypedAst,
type_env => TypeEnv}
end.
-define(CALL_NAME, "__call").
@@ -218,9 +198,9 @@ check_call1(ContractString0, FunName, Args, Options) ->
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,
#{} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options),
#{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 ],
@@ -240,14 +220,13 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{ fcode := OrgFcode
, ast := Ast } = string_to_code(ContractString0, Options),
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
@@ -273,8 +252,9 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
end.
%% Add the __call function to a contract.
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
insert_call_function(Ast, Code, Call, FunName, Args) ->
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string().
insert_call_function(Code, Call, FunName, Args, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
@@ -330,7 +310,7 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
try
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
@@ -356,12 +336,12 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
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)),
Type1 = prettypr:format(aeso_pretty:type(Type)),
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)),
Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
@@ -406,7 +386,7 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst),
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
@@ -670,9 +650,8 @@ 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),
{ok, Ast} = aeso_parser:type(String),
try aeso_ast_to_icode:ast_typerep(Ast) of
Type -> {ok, Type}
catch _:_ -> {error, bad_type}
+20 -40
View File
@@ -3,33 +3,20 @@
%%% Description :
%%% Created : 1 Mar 2018 by Ulf Norell
-module(aeso_parser).
-compile({no_auto_import,[map_get/2]}).
-export([string/1,
string/2,
string/3,
auto_imports/1,
hash_include/2,
decl/0,
type/0,
body/0,
maybe_block/1,
run_parser/2,
run_parser/3]).
type/1]).
-include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
-type parse_result() :: aeso_syntax:ast() | none().
-type include_hash() :: {string(), binary()}.
escape_errors({ok, Ok}) ->
Ok;
escape_errors({error, Err}) ->
parse_error(Err).
-spec string(string()) -> parse_result().
string(String) ->
string(String, sets:new(), []).
@@ -43,17 +30,21 @@ string(String, Opts) ->
-spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result().
string(String, Included, Opts) ->
AST = run_parser(file(), String, Opts),
case expand_includes(AST, Included, Opts) of
{ok, AST1} -> AST1;
{error, Err} -> parse_error(Err)
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
case expand_includes(AST, Included, Opts) of
{ok, AST1} -> AST1;
{error, Err} -> parse_error(Err)
end;
{error, Err} ->
parse_error(Err)
end.
run_parser(P, Inp) ->
escape_errors(parse_and_scan(P, Inp, [])).
run_parser(P, Inp, Opts) ->
escape_errors(parse_and_scan(P, Inp, Opts)).
type(String) ->
case parse_and_scan(type(), String, []) of
{ok, AST} -> {ok, AST};
{error, Err} -> {error, [mk_error(Err)]}
end.
parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
@@ -111,7 +102,7 @@ decl() ->
%% Function declarations
, ?RULE(modifiers(), fun_or_entry(), maybe_block(fundef_or_decl()), fun_block(_1, _2, _3))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
, ?RULE(keyword('let'), valdef(),set_pos(get_pos(_1), _2))
])).
fun_block(Mods, Kind, [Decl]) ->
@@ -605,13 +596,8 @@ expand_includes(AST, Included, Opts) ->
|| File <- lists:usort(auto_imports(AST)) ] ++ AST,
expand_includes(AST1, Included, [], Opts).
expand_includes([], Included, Acc, Opts) ->
case lists:member(keep_included, Opts) of
false ->
{ok, lists:reverse(Acc)};
true ->
{ok, {lists:reverse(Acc), Included}}
end;
expand_includes([], _Included, Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
case get_include_code(File, Ann, Opts) of
{ok, Code} ->
@@ -670,14 +656,8 @@ stdlib_options() ->
get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, Bin}, {ok, _}} ->
case filename:basename(File) == File of
true -> { error
, fail( ann_pos(Ann)
, "Illegal redefinition of standard library " ++ binary_to_list(File))};
%% If a path is provided then the stdlib takes lower priority
false -> {ok, binary_to_list(Bin)}
end;
{{ok, _}, {ok,_ }} ->
fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
{_, {ok, Bin}} ->
{ok, binary_to_list(Bin)};
{{ok, Bin}, _} ->
+8 -13
View File
@@ -145,12 +145,8 @@ decl(D, Options) ->
with_options(Options, fun() -> decl(D) end).
-spec decl(aeso_syntax:decl()) -> doc().
decl({contract, Attrs, C, Ds}) ->
Mod = fun({Mod, true}) when Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")])
, hsep(name(C), text("="))), decls(Ds));
decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({pragma, _, Pragma}) -> pragma(Pragma);
@@ -159,16 +155,13 @@ decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def),
equals(typedecl(Kind, T, Vars), typedef(Def));
decl({fun_decl, Ann, F, T}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of
true -> text("entrypoint");
false -> text("function")
end,
hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]);
hsep(Fun, typed(name(F), T));
decl(D = {letfun, Attrs, _, _, _, _}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful ->
text(atom_to_list(Mod));
(_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of
@@ -371,8 +364,7 @@ expr_p(_, {Type, _, Bin})
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\"");
expr_p(_, {string, _, S}) ->
text(io_lib:format("\"~s\"", [binary_to_list(S)]));
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
case C of
$' -> text("'\\''");
@@ -494,3 +486,6 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) ->
end;
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
fmt(Fmt, Args) -> text(lists:flatten(io_lib:format(Fmt, Args))).
term(X) -> fmt("~p", [X]).
+6 -12
View File
@@ -37,26 +37,20 @@
-type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()}
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
| {fun_decl, ann(), id(), type()}
| {fun_clauses, ann(), id(), type(), [letbind()]}
| {block, ann(), [decl()]}
| fundecl()
| letfun()
| letval(). % Only for error msgs
| letbind().
-type compiler_version() :: [non_neg_integer()].
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind()
:: letfun()
| letval().
:: {letval, ann(), pat(), expr()}
| {letfun, ann(), id(), [pat()], type(), expr()}.
-type arg() :: {arg, ann(), id(), type()}.
+1
View File
@@ -45,6 +45,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_decl, _, I, _} -> BindType(I);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
-2
View File
@@ -95,8 +95,6 @@ from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, [{field_t, _, FName, FType}]}, Val) ->
{record, [], [{field, [], [{proj, [], FName}], from_fate(FType, Val)}]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Contract Language for aeternity"},
{vsn, "4.3.0"},
{vsn, "4.2.0"},
{registered, []},
{applications,
[kernel,
+6 -13
View File
@@ -11,10 +11,7 @@ test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)),
%% Check if the compiler provides correct aci
{ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]),
?assertEqual(JSON, JSON2).
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
test_cases(1) ->
Contract = <<"payable contract C =\n"
@@ -83,11 +80,11 @@ test_cases(3) ->
DecACI = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>,
" datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}.
%% Roundtrip
%% Rounttrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
@@ -97,13 +94,8 @@ all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = case lists:member(Name, aeso_compiler_tests:debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
@@ -130,3 +122,4 @@ check_stub(Stub, Options) ->
_ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ],
error({parse_errors, Errs})
end.
-1
View File
@@ -113,7 +113,6 @@ compilable_contracts() ->
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
+12 -88
View File
@@ -39,7 +39,7 @@ simple_compile_test_() ->
error(ErrBin)
end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_compilable_on(Backend))] ++
not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Test file not found error",
fun() ->
{error, Errors} = aeso_compiler:file("does_not_exist.aes"),
@@ -110,15 +110,7 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name),
Options1 =
case lists:member(Name, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[ {src_file, Name ++ ".aes"}, {backend, Backend}
, {include, {file_system, [aeso_test_utils:contract_path()]}}
] ++ Options,
case aeso_compiler:from_string(String, Options1) of
case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {backend, Backend} | Options]) of
{ok, Map} -> Map;
{error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors
@@ -173,19 +165,11 @@ compilable_contracts() ->
"underscore_number_literals",
"qualified_constructor",
"let_patterns",
"lhs_matching",
"hermetization_turnoff"
"lhs_matching"
].
not_compilable_on(fate) -> [];
not_compilable_on(aevm) ->
["stdlib_include",
"manual_stdlib_include",
"hermetization_turnoff"
].
debug_mode_contracts() ->
["hermetization_turnoff"].
not_yet_compilable(fate) -> [];
not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors
@@ -391,15 +375,11 @@ failing_contracts() ->
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>])
, ?TYPE_ERROR(not_toplevel_include,
[<<?Pos(2, 11)
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>])
, ?TYPE_ERROR(not_toplevel_namespace,
[<<?Pos(2, 13)
"Nested namespaces are not allowed\nNamespace 'Foo' at line 2, column 13 not defined at top level.">>])
, ?TYPE_ERROR(not_toplevel_contract,
[<<?Pos(2, 12)
"Nested contracts are not allowed\nContract 'Con' at line 2, column 12 not defined at top level.">>])
, ?TYPE_ERROR(bad_include_and_ns,
[<<?Pos(2, 11)
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<?Pos(3, 13)
"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>])
, ?TYPE_ERROR(bad_address_literals,
[<<?Pos(11, 5)
"Cannot unify address\n"
@@ -632,44 +612,6 @@ failing_contracts() ->
[<<?Pos(5, 28)
"Invalid call to contract entrypoint 'Foo.foo'.\n"
"It must be called as 'c.foo' for some c : Foo.">>])
, ?TYPE_ERROR(toplevel_let,
[<<?Pos(2, 7)
"Toplevel \"let\" definitions are not supported\n"
"Value this_is_illegal at line 2, column 7 could be replaced by 0-argument function">>])
, ?TYPE_ERROR(empty_typedecl,
[<<?Pos(2, 8)
"Empty type declarations are not supported\n"
"Type t at line 2, column 8 lacks a definition">>])
, ?TYPE_ERROR(higher_kinded_type,
[<<?Pos(2, 35)
"Type 'm is a higher kinded type variable\n"
"(takes another type as an argument)">>])
, ?TYPE_ERROR(bad_arity,
[<<?Pos(3, 20)
"Arity for id doesn't match. Expected 1, got 0">>,
<<?Pos(3, 25)
"Cannot unify int\n"
" and id\n"
"when checking the type of the expression at line 3, column 25\n"
" 123 : int\n"
"against the expected type\n"
" id">>,
<<?Pos(4, 20)
"Arity for id doesn't match. Expected 1, got 2">>,
<<?Pos(4, 35)
"Cannot unify int\n"
" and id(int, int)\n"
"when checking the type of the expression at line 4, column 35\n"
" 123 : int\n"
"against the expected type\n"
" id(int, int)">>])
, ?TYPE_ERROR(bad_unnamed_map_update_default,
[<<?Pos(4, 17)
"Invalid map update with default">>])
, ?TYPE_ERROR(non_functional_entrypoint,
[<<?Pos(2, 14)
"f at line 2, column 14 was declared with an invalid type int.\n"
"Entrypoints and functions must have functional types">>])
, ?TYPE_ERROR(bad_records,
[<<?Pos(3, 16)
"Mixed record fields and map keys in\n"
@@ -681,16 +623,6 @@ failing_contracts() ->
"Empty record/map update\n"
" r {}">>
])
, ?TYPE_ERROR(bad_function_block,
[<<?Pos(4, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>,
<<?Pos(5, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>
])
, ?TYPE_ERROR(just_an_empty_file,
[<<?Pos(0, 0)
"Empty contract">>
])
, ?TYPE_ERROR(bad_number_of_args,
[<<?Pos(3, 39)
"Cannot unify () => unit\n"
@@ -713,9 +645,6 @@ failing_contracts() ->
" g : (int, string) => 'c\nto arguments\n"
" \"Litwo, ojczyzno moja\" : string">>
])
, ?TYPE_ERROR(bad_state,
[<<?Pos(4, 16)
"Conflicting updates for field 'foo'">>])
].
-define(Path(File), "code_errors/" ??File).
@@ -869,11 +798,6 @@ validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code(
ByteCode#{ byte_code := FCode1 }, Source,
case lists:member(Contract2, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source,
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
+2 -1
View File
@@ -58,7 +58,8 @@ contract Greeter =
let state = { greeting = "Hello" }
function setGreeting(greeting: string) =
let setGreeting =
(greeting: string) =>
state{ greeting = greeting }
+29 -63
View File
@@ -1,78 +1,44 @@
// Try to cover all syntactic constructs.
@compiler > 0
@compiler =< 10.1.1.1.1.1.2.3.4
contract AllSyntaxType =
type typeDecl /* bla */
type paramTypeDecl('a, 'b)
namespace Ns =
datatype d('a) = D | S(int) | M('a, list('a), int)
private function fff() = 123
stateful entrypoint
f (1, x) = (_) => x
payable contract AllSyntaxType =
/** Multi-
* line
* comment
*/
stateful function foo : _
entrypoint bar : int => (int * 'a)
function foo : _
contract AllSyntax =
datatype mickiewicz = Adam | Mickiewicz
record goethe('a, 'b) = {
johann : int,
wolfgang : 'a,
von : 'a * 'b * int,
goethe : unit
}
type dante = Ns.d(int)
type shakespeare('a) = goethe('a, 'a)
type typeDecl = int
type paramTypeDecl('a, 'b) = (('a, 'b) => 'b) => list('a) => 'b => 'b
type state = shakespeare(int)
record nestedRecord = { x : int }
record recordType = { z : nestedRecord, y : int }
datatype variantType('a) = None | Some('a)
entrypoint init() = {
johann = 1000,
wolfgang = -10,
von = (2 + 2, 0, List.sum([x | k <- [1,2,3]
, let l = k + 1
, if(l < 10)
, let f(x) = x + 100
, Adam <- [Adam, Mickiewicz]
, let x = f(l)
])),
goethe = () }
let valWithType : map(int, int) => option(int) = (m) => Map.get(m, 42)
let valNoType =
if(valWithType(Map.empty) == None)
print(42 mod 10 * 5 / 3)
function f() =
let kp = "nietzsche"
let p = "Пушкин"
let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210)
function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ [])
function funNoType() =
let foo = (x, y : bool) =>
if (! (y && x =< 0x0b || true)) [x]
else [11..20]
let setY(r : recordType) : unit = r{ y = 5 }
let setX(r : recordType, x : int) : recordType = r { z.x = x } // nested record update
let getY(r) = switch(r) {y = y} => y
switch (funWithType(1, -2))
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => ()
let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
if(Bits.test(Bits.all, 10))
abort("ohno")
if(true && false)
require(true, "ohyes")
elif(false || 2 == 2)
()
else
()
if(true) f(1,2)((1,2))
else switch(1::[1,2,3])
[] => 1
a::b => 123
1::2::3 => 123123
[2,3,4] => 1
_ => 13
1::[2] => 2138
put(state{johann = 1})
let hash : address = #01ab0fff11
let b = false
let qcon = Mod.Con
let str = "blabla\nfoo"
let chr = '"'
let m = {["foo"] = 19, /*hey wanna talk about inlined comments?*/ ["bar"] = 42}
let n = {}
m{ ["x" = 0] @ z = z + state.johann }
let sh : shakespeare(shakespeare(int)) =
{wolfgang = state}
sh{wolfgang.wolfgang = sh.wolfgang} // comment
-4
View File
@@ -1,4 +0,0 @@
contract C =
type id('a) = 'a
entrypoint f() : id = 123
entrypoint g() : id(int, int) = 123
-5
View File
@@ -1,5 +0,0 @@
contract C =
function
g(1) = 2
f(2) = 3
h(1) = 123
@@ -1,4 +1,5 @@
contract BadCon =
contract Bad =
include "included.aes"
namespace Foo =
function foo() = 42
-5
View File
@@ -1,5 +0,0 @@
contract C =
record state = { foo : int }
entrypoint init(i : int) =
state{ foo = i,
foo = 42 }
@@ -1,5 +0,0 @@
contract C =
entrypoint f() =
let z = 123
{}{ [1 = 0] = z + 1 }
2
-3
View File
@@ -1,3 +0,0 @@
contract C =
type t
entrypoint f() = 123
-5
View File
@@ -45,8 +45,3 @@ contract FunctionArguments =
entrypoint due(t : Chain.ttl) =
true
record singleton_r = { x : int }
entrypoint singleton_rec(r : singleton_r) =
r.x
-11
View File
@@ -1,11 +0,0 @@
namespace M =
function mf() = mg()
function mg() = mf()
namespace N =
function nf() = ng() + M.mf() + M.mg()
private function ng() = nf() + M.mf() + M.mg()
contract C =
entrypoint f() = N.ng() + N.nf() + g()
function g() = N.ng() + N.nf() + f()
-3
View File
@@ -1,3 +0,0 @@
contract IWantToBelieve =
type stateT('s, 'm, 'a) = 's => 'm('a * 's)
entrypoint s() = 123
+2 -2
View File
@@ -15,7 +15,7 @@ contract MultiSig =
| OwnerRemoved (address) // of { .removedOwner : Address }
| ReqChanged (int) // of { .newReq : int }
function maxOwners() : int = 250
let maxOwners : int = 250
record state = { nRequired : int
, nOwners : int
@@ -68,7 +68,7 @@ contract MultiSig =
switch(check_pending(callhash()))
CheckFail(state') => { state = state' }
CheckOk(state') =>
if(state.nOwners >= maxOwners()) () /* TODO */
if(state.nOwners >= maxOwners) () /* TODO */
else
let nOwners' = state'.nOwners + 1
{ state = state' { owners = Map.insert(nOwners', newOwner, state'.owners)
@@ -1,5 +0,0 @@
contract C1 =
entrypoint f : int
contract C =
entrypoint f() = 123
-6
View File
@@ -1,6 +0,0 @@
namespace BadNs =
contract Con =
entrypoint e : () => int
contract Con =
entrypoint foo() = 43
-5
View File
@@ -1,5 +0,0 @@
namespace BadNs =
include "included.aes"
contract Con =
entrypoint foo() = 43
-3
View File
@@ -1,3 +0,0 @@
contract C =
let this_is_illegal = 2/0
entrypoint this_is_legal() = 2/0
+13
View File
@@ -1,3 +1,16 @@
/* Contract type */
contract VotingType =
type state
function init : list(string) => state
function giveRightToVote : address => unit
function delegate : address => unit
function vote : int => unit
function winnerName : unit => string
function currentTally : unit => list(string * int)
/* Contract implementation */
contract Voting =
// Types