Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ad5270e38 | |||
| a982f25262 | |||
| 20cab3ae57 | |||
| 1ffb20178c | |||
| 6d79d2d558 | |||
| 24c579a5d3 | |||
| 1be24c94c5 | |||
| ebb1f9ecf9 | |||
| 9cb3158dfd | |||
| becafe4001 | |||
| e8a171dc45 | |||
| a7b7aafced | |||
| 262452fb70 | |||
| 3029bf31cb | |||
| 4896ad3b36 | |||
| b20b9c5df5 | |||
| d793660545 | |||
| 4957d01e9e | |||
| 9d76e6186a | |||
| ae3edac53e | |||
| acec32e744 | |||
| 5784f074a6 | |||
| d07b321b25 | |||
| 2e6c01cb75 | |||
| b22eeffc3d | |||
| b366bed24b | |||
| 1975ccf804 | |||
| 4f68729631 | |||
| 10c845d3cf | |||
| 393d7710c1 | |||
| 37e5a92b2e | |||
| cb9c9df103 | |||
| c09313a92c | |||
| 75b2d6981f | |||
| 78d94786b6 | |||
| 216f7f8a25 | |||
| 254172e3a3 | |||
| eadb4e8c83 | |||
| e2af89287d | |||
| 3996b6a711 | |||
| e8b32a6875 | |||
| cca7bdff49 | |||
| 1d9f59fec3 | |||
| d82b42518e | |||
| 00a3a51d0d |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,7 @@
|
||||
import glob
|
||||
import shutil
|
||||
|
||||
def pre_build(**kwargs):
|
||||
for file in glob.glob('../docs/*.md'):
|
||||
shutil.copy(file, 'docs')
|
||||
shutil.copy('../CHANGELOG.md', 'docs')
|
||||
@@ -0,0 +1,55 @@
|
||||
site_name: æternity Sophia Language
|
||||
plugins:
|
||||
- search
|
||||
- mkdocs-simple-hooks:
|
||||
hooks:
|
||||
on_pre_build: 'hook:pre_build'
|
||||
repo_url: 'https://github.com/aeternity/aesophia'
|
||||
edit_uri: ''
|
||||
|
||||
extra:
|
||||
version:
|
||||
provider: mike
|
||||
|
||||
theme:
|
||||
favicon: favicon.png
|
||||
name: material
|
||||
custom_dir: overrides
|
||||
language: en
|
||||
palette:
|
||||
- scheme: default
|
||||
primary: pink
|
||||
accent: pink
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
primary: pink
|
||||
accent: pink
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
features:
|
||||
- content.tabs.link
|
||||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
|
||||
# Don't include MkDocs' JavaScript
|
||||
include_search_page: false
|
||||
search_index_only: true
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight
|
||||
- pymdownx.superfences
|
||||
- toc:
|
||||
toc_depth: 3
|
||||
|
||||
nav:
|
||||
- Introduction: index.md
|
||||
- Syntax: sophia_syntax.md
|
||||
- Features: sophia_features.md
|
||||
- Standard library: sophia_stdlib.md
|
||||
- Contract examples: sophia_examples.md
|
||||
- Changelog: CHANGELOG.md
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block outdated %}
|
||||
You're not viewing the latest version.
|
||||
<a href="{{ '../' ~ base_url }}">
|
||||
<strong>Click here to go to latest.</strong>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Publish development docs
|
||||
on:
|
||||
push:
|
||||
branches: ['master']
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push master
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Publish release docs
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- 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
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push --update-aliases $RELEASE_VERSION latest
|
||||
@@ -0,0 +1,4 @@
|
||||
mkdocs==1.2.3
|
||||
mkdocs-simple-hooks==0.1.3
|
||||
mkdocs-material==7.1.9
|
||||
mike==1.0.1
|
||||
@@ -21,3 +21,6 @@ rebar3.crashdump
|
||||
aesophia
|
||||
.qcci
|
||||
current_counterexample.eqc
|
||||
test/contracts/test.aes
|
||||
__pycache__
|
||||
.docssite/docs/*.md
|
||||
|
||||
+54
-1
@@ -9,6 +9,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [6.1.0] - 2021-10-20
|
||||
### Added
|
||||
- `Bitwise` stdlib
|
||||
- `Set` stdlib
|
||||
- `Option.force_msg`
|
||||
- Loading namespaces into the current scope (e.g. `using Pair`)
|
||||
- Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`)
|
||||
- Add builtin types (`AENS.name, AENS.pointee, Chain.ttl, Chain.base_tx, Chain.ga_meta_tx, Chain.paying_for_tx`) to
|
||||
the calldata and result decoder
|
||||
- Patterns guards
|
||||
```
|
||||
switch(x)
|
||||
a::[] | a > 10 => 1
|
||||
_ => 2
|
||||
```
|
||||
```
|
||||
function
|
||||
f(a::[]) | a > 10 = 1
|
||||
f(_) = 2
|
||||
```
|
||||
### Changed
|
||||
- Fixed the ACI renderer, it shouldn't drop the `stateful` modifier
|
||||
|
||||
## [6.0.2] 2021-07-05
|
||||
### Changed
|
||||
- `List.from_to_step` now forbids non-positive step (this change does
|
||||
*not* alter the behavior of the previously deployed contracts)
|
||||
- Fixed leaking state between contracts
|
||||
|
||||
## [6.0.1] 2021-06-24
|
||||
### Changed
|
||||
- Fixed a bug in calldata encoding for contracts containing multiple contracts
|
||||
- Fixed a missing `include` in the `Frac` standard library
|
||||
|
||||
## [6.0.0] 2021-05-26
|
||||
### Added
|
||||
- Child contracts
|
||||
- `Chain.clone`
|
||||
- `Chain.create`
|
||||
- `Chain.bytecode_hash`
|
||||
- Minor support for variadic functions
|
||||
- `void` type that represents an empty type
|
||||
- `Call.fee` builtin
|
||||
### Changed
|
||||
- Contract interfaces must be now invocated by `contract interface` keywords
|
||||
- `main` keyword to indicate the main contract in case there are child contracts around
|
||||
- `List.sum` and `List.product` no longer use `List.foldl`
|
||||
### Removed
|
||||
|
||||
## [5.0.0] 2021-04-30
|
||||
### Added
|
||||
- A new and improved [`String` standard library](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#string)
|
||||
@@ -283,7 +332,11 @@ 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/v5.0.0...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v6.1.0...HEAD
|
||||
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
|
||||
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
|
||||
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
|
||||
[6.0.0]: https://github.com/aeternity/aesophia/compare/v5.0.0...v6.0.0
|
||||
[5.0.0]: https://github.com/aeternity/aesophia/compare/v4.3.0...v5.0.0
|
||||
[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2017, aeternity developers
|
||||
Copyright (c) 2017, æternity developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -5,25 +5,30 @@ This is the __sophia__ compiler for the æternity system which compiles contract
|
||||
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
|
||||
- In [æternity node](https://github.com/aeternity/aeternity) tests
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md).
|
||||
* [Sophia Documentation](docs/sophia.md).
|
||||
* [Sophia Standard Library](docs/sophia_stdlib.md).
|
||||
* [Introduction](docs/index.md)
|
||||
* [Syntax](docs/sophia_syntax.md)
|
||||
* [Features](docs/sophia_features.md)
|
||||
* [Standard library](docs/sophia_stdlib.md)
|
||||
* [Contract examples](docs/sophia_examples.md)
|
||||
|
||||
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
|
||||
|
||||
## Versioning
|
||||
|
||||
`aesophia` has a version that is only loosely connected to the version of the
|
||||
Aeternity node - in principle they will share the major version but not
|
||||
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.
|
||||
Versioning should follow the [semantic versioning](https://semver.org/spec/v2.0.0) guidelines. Id est, given a version number MAJOR.MINOR.PATCH, increment the:
|
||||
|
||||
- MAJOR version when you make incompatible API changes
|
||||
- MINOR version when you add functionality in a backwards compatible manner
|
||||
- PATCH version when you make backwards compatible bug fixes
|
||||
|
||||
|
||||
## Interface Modules
|
||||
|
||||
The basic modules for interfacing the compiler:
|
||||
|
||||
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
|
||||
* [aeso_aci: the ACI interface](./docs/aeso_aci.md)
|
||||
* [aeso_compiler: the Sophia compiler](docs/aeso_compiler.md)
|
||||
* [aeso_aci: the ACI interface](docs/aeso_aci.md)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Introduction
|
||||
Sophia is a functional language designed for smart contract development. It is strongly typed and has
|
||||
restricted mutable state.
|
||||
|
||||
Sophia is customized for smart contracts, which can be published
|
||||
to a blockchain. Thus some features of conventional
|
||||
languages, such as floating point arithmetic, are not present in Sophia, and
|
||||
some [æternity blockchain](https://aeternity.com) specific primitives, constructions and types have been added.
|
||||
|
||||
!!! Note
|
||||
- For rapid prototyping of smart contracts check out [AEstudio](https://studio.aepps.com/)!
|
||||
- For playing around and diving deeper into the language itself check out the [REPL](https://repl.aeternity.io/)!
|
||||
+1
-1163
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# Contract examples
|
||||
|
||||
## Crowdfunding
|
||||
```sophia
|
||||
/*
|
||||
* A simple crowd-funding example
|
||||
*/
|
||||
contract FundMe =
|
||||
|
||||
record spend_args = { recipient : address,
|
||||
amount : int }
|
||||
|
||||
record state = { contributions : map(address, int),
|
||||
total : int,
|
||||
beneficiary : address,
|
||||
deadline : int,
|
||||
goal : int }
|
||||
|
||||
stateful function spend(args : spend_args) =
|
||||
Chain.spend(args.recipient, args.amount)
|
||||
|
||||
entrypoint init(beneficiary, deadline, goal) : state =
|
||||
{ contributions = {},
|
||||
beneficiary = beneficiary,
|
||||
deadline = deadline,
|
||||
total = 0,
|
||||
goal = goal }
|
||||
|
||||
function is_contributor(addr) =
|
||||
Map.member(addr, state.contributions)
|
||||
|
||||
stateful entrypoint contribute() =
|
||||
if(Chain.block_height >= state.deadline)
|
||||
spend({ recipient = Call.caller, amount = Call.value }) // Refund money
|
||||
false
|
||||
else
|
||||
let amount =
|
||||
switch(Map.lookup(Call.caller, state.contributions))
|
||||
None => Call.value
|
||||
Some(n) => n + Call.value
|
||||
put(state{ contributions[Call.caller] = amount,
|
||||
total @ tot = tot + Call.value })
|
||||
true
|
||||
|
||||
stateful entrypoint withdraw() =
|
||||
if(Chain.block_height < state.deadline)
|
||||
abort("Cannot withdraw before deadline")
|
||||
if(Call.caller == state.beneficiary)
|
||||
withdraw_beneficiary()
|
||||
elif(is_contributor(Call.caller))
|
||||
withdraw_contributor()
|
||||
else
|
||||
abort("Not a contributor or beneficiary")
|
||||
|
||||
stateful function withdraw_beneficiary() =
|
||||
require(state.total >= state.goal, "Project was not funded")
|
||||
spend({recipient = state.beneficiary,
|
||||
amount = Contract.balance })
|
||||
|
||||
stateful function withdraw_contributor() =
|
||||
if(state.total >= state.goal)
|
||||
abort("Project was funded")
|
||||
let to = Call.caller
|
||||
spend({recipient = to,
|
||||
amount = state.contributions[to]})
|
||||
put(state{ contributions @ c = Map.delete(to, c) })
|
||||
```
|
||||
|
||||
## Repositories
|
||||
This is a list with repositories that include smart contracts written in Sophia:
|
||||
|
||||
- [aepp-sophia-examples](https://github.com/aeternity/aepp-sophia-examples)
|
||||
- A repository that contains lots of different examples. The functionality of these examples is - to some extent - also covered by tests written in JavaScript.
|
||||
@@ -0,0 +1,877 @@
|
||||
# Features
|
||||
## Contracts
|
||||
|
||||
The main unit of code in Sophia is the *contract*.
|
||||
|
||||
- A contract implementation, or simply a contract, is the code for a
|
||||
smart contract and consists of a list of types, entrypoints and local
|
||||
functions. Only the entrypoints can be called from outside the contract.
|
||||
- A contract instance is an entity living on the block chain (or in a state
|
||||
channel). Each instance has an address that can be used to call its
|
||||
entrypoints, either from another contract or in a call transaction.
|
||||
- A contract may define a type `state` encapsulating its local
|
||||
state. When creating a new contract the `init` entrypoint is executed and the
|
||||
state is initialized to its return value.
|
||||
|
||||
The language offers some primitive functions to interact with the blockchain and contracts.
|
||||
Please refer to the [Chain](sophia_stdlib.md#chain), [Contract](sophia_stdlib.md#contract)
|
||||
and the [Call](sophia_stdlib.md#call) namespaces in the documentation.
|
||||
|
||||
### Calling other contracts
|
||||
|
||||
To call a function in another contract you need the address to an instance of
|
||||
the contract. The type of the address must be a contract type, which consists
|
||||
of a number of type definitions and entrypoint declarations. For instance,
|
||||
|
||||
```sophia
|
||||
// A contract type
|
||||
contract interface VotingType =
|
||||
entrypoint vote : string => unit
|
||||
```
|
||||
|
||||
Now given contract address of type `VotingType` you can call the `vote`
|
||||
entrypoint of that contract:
|
||||
|
||||
```sophia
|
||||
contract VoteTwice =
|
||||
entrypoint voteTwice(v : VotingType, alt : string) =
|
||||
v.vote(alt)
|
||||
v.vote(alt)
|
||||
```
|
||||
|
||||
Contract calls take two optional named arguments `gas : int` and `value : int`
|
||||
that lets you set a gas limit and provide tokens to a contract call. If omitted
|
||||
the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
|
||||
|
||||
```sophia
|
||||
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
|
||||
v.vote(value = fee, alt)
|
||||
v.vote(value = fee, alt)
|
||||
```
|
||||
|
||||
Named arguments can be given in any order.
|
||||
|
||||
Note that reentrant calls are not permitted. In other words, when calling
|
||||
another contract it cannot call you back (directly or indirectly).
|
||||
|
||||
To construct a value of a contract type you can give a contract address literal
|
||||
(for instance `ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay`), or
|
||||
convert an account address to a contract address using `Address.to_contract`.
|
||||
Note that if the contract does not exist, or it doesn't have the entrypoint, or
|
||||
the type of the entrypoint does not match the stated contract type, the call
|
||||
fails.
|
||||
|
||||
To recover the underlying `address` of a contract instance there is a field
|
||||
`address : address`. For instance, to send tokens to the voting contract (given that it is payable)
|
||||
without calling it you can write
|
||||
|
||||
```sophia
|
||||
entrypoint pay(v : VotingType, amount : int) =
|
||||
Chain.spend(v.address, amount)
|
||||
```
|
||||
|
||||
### Protected contract calls
|
||||
|
||||
If a contract call fails for any reason (for instance, the remote contract
|
||||
crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong
|
||||
type) the parent call also fails. To make it possible to recover from failures,
|
||||
contract calls takes a named argument `protected : bool` (default `false`).
|
||||
|
||||
The protected argument must be a literal boolean, and when set to `true`
|
||||
changes the type of the contract call, wrapping the result in an `option` type.
|
||||
If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is
|
||||
the return value of the call.
|
||||
|
||||
```sophia
|
||||
contract interface VotingType =
|
||||
entrypoint : vote : string => unit
|
||||
|
||||
contract Voter =
|
||||
entrypoint tryVote(v : VotingType, alt : string) =
|
||||
switch(v.vote(alt, protected = true) : option(unit))
|
||||
None => "Voting failed"
|
||||
Some(_) => "Voting successful"
|
||||
```
|
||||
|
||||
Any gas that was consumed by the contract call before the failure stays
|
||||
consumed, which means that in order to protect against the remote contract
|
||||
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.
|
||||
|
||||
|
||||
### Contract factories and child contracts
|
||||
|
||||
Since the version 6.0.0 Sophia supports deploying contracts by other
|
||||
contracts. This can be done in two ways:
|
||||
|
||||
- Contract cloning via [`Chain.clone`](sophia_stdlib.md#clone)
|
||||
- Direct deploy via [`Chain.create`](sophia_stdlib.md#create)
|
||||
|
||||
These functions take variable number of arguments that must match the created
|
||||
contract's `init` function. Beside that they take some additional named
|
||||
arguments – please refer to their documentation for the details.
|
||||
|
||||
While `Chain.clone` requires only a `contract interface` and a living instance
|
||||
of a given contract on the chain, `Chain.create` needs a full definition of a
|
||||
to-create contract defined by the standard `contract` syntax, for example
|
||||
|
||||
```sophia
|
||||
contract IntHolder =
|
||||
type state = int
|
||||
entrypoint init(x) = x
|
||||
entrypoint get() = state
|
||||
|
||||
main contract IntHolderFactory =
|
||||
stateful entrypoint new(x : int) : IntHolder =
|
||||
let ih = Chain.create(x) : IntHolder
|
||||
ih
|
||||
```
|
||||
|
||||
In case of a presence of child contracts (`IntHolder` in this case), the main
|
||||
contract must be pointed out with the `main` keyword as shown in the example.
|
||||
|
||||
|
||||
## Mutable state
|
||||
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of state
|
||||
associated with each contract instance.
|
||||
|
||||
- Each contract defines a type `state` encapsulating its mutable state.
|
||||
The type `state` defaults to the `unit`.
|
||||
- The initial state of a contract is computed by the contract's `init`
|
||||
function. The `init` function is *pure* and returns the initial state as its
|
||||
return value.
|
||||
If the type `state` is `unit`, the `init` function defaults to returning the value `()`.
|
||||
At contract creation time, the `init` function is executed and
|
||||
its result is stored as the contract state.
|
||||
- The value of the state is accessible from inside the contract
|
||||
through an implicitly bound variable `state`.
|
||||
- State updates are performed by calling a function `put : state => unit`.
|
||||
- Aside from the `put` function (and similar functions for transactions
|
||||
and events), the language is purely functional.
|
||||
- Functions modifying the state need to be annotated with the `stateful` keyword (see below).
|
||||
|
||||
To make it convenient to update parts of a deeply nested state Sophia
|
||||
provides special syntax for map/record updates.
|
||||
|
||||
### Stateful functions
|
||||
|
||||
Top-level functions and entrypoints must be annotated with the
|
||||
`stateful` keyword to be allowed to affect the state of the running contract.
|
||||
For instance,
|
||||
|
||||
```sophia
|
||||
stateful entrypoint set_state(s : state) =
|
||||
put(s)
|
||||
```
|
||||
|
||||
Without the `stateful` annotation the compiler does not allow the call to
|
||||
`put`. A `stateful` annotation is required to
|
||||
|
||||
* Use a stateful primitive function. These are
|
||||
- `put`
|
||||
- `Chain.spend`
|
||||
- `Oracle.register`
|
||||
- `Oracle.query`
|
||||
- `Oracle.respond`
|
||||
- `Oracle.extend`
|
||||
- `AENS.preclaim`
|
||||
- `AENS.claim`
|
||||
- `AENS.transfer`
|
||||
- `AENS.revoke`
|
||||
- `AENS.update`
|
||||
* Call a `stateful` function in the current contract
|
||||
* Call another contract with a non-zero `value` argument.
|
||||
|
||||
A `stateful` annotation *is not* required to
|
||||
|
||||
* Read the contract state.
|
||||
* Issue an event using the `event` function.
|
||||
* Call another contract with `value = 0`, even if the called function is stateful.
|
||||
|
||||
## Payable
|
||||
|
||||
### Payable contracts
|
||||
|
||||
A concrete contract is by default *not* payable. Any attempt at spending to such
|
||||
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
|
||||
contract shall be able to receive funds in this way it has to be declared `payable`:
|
||||
|
||||
```sophia
|
||||
// A payable contract
|
||||
payable contract ExampleContract =
|
||||
stateful entrypoint do_stuff() = ...
|
||||
```
|
||||
|
||||
If in doubt, it is possible to check if an address is payable using
|
||||
`Address.is_payable(addr)`.
|
||||
|
||||
### Payable entrypoints
|
||||
|
||||
A contract entrypoint is by default *not* payable. Any call to such a function
|
||||
(either a [Remote call](#calling-other-contracts) or a contract call transaction)
|
||||
that has a non-zero `value` will fail. Contract entrypoints that should be called
|
||||
with a non-zero value should be declared `payable`.
|
||||
|
||||
```sophia
|
||||
payable stateful entrypoint buy(to : address) =
|
||||
if(Call.value > 42)
|
||||
transfer_item(to)
|
||||
else
|
||||
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
|
||||
can appear at the top-level and can contain type and function definitions, but
|
||||
not entrypoints. Outside the namespace you can refer to the (non-private) names
|
||||
by qualifying them with the namespace (`Namespace.name`).
|
||||
For example,
|
||||
|
||||
```sophia
|
||||
namespace Library =
|
||||
type number = int
|
||||
function inc(x : number) : number = x + 1
|
||||
|
||||
contract MyContract =
|
||||
entrypoint plus2(x) : Library.number =
|
||||
Library.inc(Library.inc(x))
|
||||
```
|
||||
|
||||
Functions in namespaces have access to the same environment (including the
|
||||
`Chain`, `Call`, and `Contract`, builtin namespaces) as function in a contract,
|
||||
with the exception of `state`, `put` and `Chain.event` since these are
|
||||
dependent on the specific state and event types of the contract.
|
||||
|
||||
To avoid mentioning the namespace every time it is used, Sophia allows
|
||||
including the namespace in the current scope with the `using` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
using Pair
|
||||
contract C =
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
It is also possible to make an alias for the namespace with the `as` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
contract C =
|
||||
using Pair as P
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
P.fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
Having the same alias for multiple namespaces is possible and it allows
|
||||
referening functions that are defined in different namespaces and have
|
||||
different names with the same alias:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function g() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
entrypoint init() = A.f() + A.g()
|
||||
```
|
||||
|
||||
Note that using functions with the same name would result in an ambiguous name
|
||||
error:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function f() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
|
||||
// the next line has an error because f is defined in both Xa and Xb
|
||||
entrypoint init() = A.f()
|
||||
```
|
||||
|
||||
Importing specific parts of a namespace or hiding these parts can also be
|
||||
done like this:
|
||||
```
|
||||
using Pair for [fst, snd] // this will only import fst and snd
|
||||
using Triple hiding [fst, snd] // this will import everything except for fst and snd
|
||||
```
|
||||
|
||||
Note that it is possible to use a namespace in the top level of the file, in the
|
||||
contract level, namespace level, or in the function level.
|
||||
|
||||
## Splitting code over multiple files
|
||||
|
||||
Code from another file can be included in a contract using an `include`
|
||||
statement. These must appear at the top-level (outside the main contract). The
|
||||
included file can contain one or more namespaces and abstract contracts. For
|
||||
example, if the file `library.aes` contains
|
||||
|
||||
```sophia
|
||||
namespace Library =
|
||||
function inc(x) = x + 1
|
||||
```
|
||||
|
||||
you can use it from another file using an `include`:
|
||||
|
||||
```sophia
|
||||
include "library.aes"
|
||||
contract MyContract =
|
||||
entrypoint plus2(x) = Library.inc(Library.inc(x))
|
||||
```
|
||||
|
||||
This behaves as if the contents of `library.aes` was textually inserted into
|
||||
the file, except that error messages will refer to the original source
|
||||
locations. The language will try to include each file at most one time automatically,
|
||||
so even cyclic includes should be working without any special tinkering.
|
||||
|
||||
## Standard library
|
||||
|
||||
Sophia offers [standard library](sophia_stdlib.md) which exposes some
|
||||
primitive operations and some higher level utilities. The builtin
|
||||
namespaces like `Chain`, `Contract`, `Map`
|
||||
are included by default and are supported internally by the compiler.
|
||||
Others like `List`, `Frac`, `Option` need to be manually included using the
|
||||
`include` directive. For example
|
||||
```sophia
|
||||
include "List.aes"
|
||||
include "Pair.aes"
|
||||
-- Map is already there!
|
||||
|
||||
namespace C =
|
||||
entrypoint keys(m : map('a, 'b)) : list('a) =
|
||||
List.map(Pair.fst, (Map.to_list(m)))
|
||||
```
|
||||
|
||||
## Types
|
||||
Sophia has the following types:
|
||||
|
||||
| Type | Description | Example |
|
||||
|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| int | A 2-complement integer | ```-1``` |
|
||||
| address | æternity address, 32 bytes | ```Call.origin``` |
|
||||
| bool | A Boolean | ```true``` |
|
||||
| bits | A bit field | ```Bits.none``` |
|
||||
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
|
||||
| string | An array of bytes | ```"Foo"``` |
|
||||
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
|
||||
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
|
||||
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
|
||||
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
|
||||
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
|
||||
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
|
||||
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
|
||||
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
|
||||
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
|
||||
| signature | A signature - equivalent to `bytes(64)` | |
|
||||
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
|
||||
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
|
||||
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
|
||||
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
|
||||
|
||||
## Literals
|
||||
| Type | Constant/Literal example(s) |
|
||||
| ---------- | ------------------------------- |
|
||||
| int | `-1`, `2425`, `4598275923475723498573485768` |
|
||||
| address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` |
|
||||
| bool | `true`, `false` |
|
||||
| bits | `Bits.none`, `Bits.all` |
|
||||
| bytes(8) | `#fedcba9876543210` |
|
||||
| string | `"This is a string"` |
|
||||
| list | `[1, 2, 3]`, `[(true, 24), (false, 19), (false, -42)]` |
|
||||
| tuple | `(42, "Foo", true)` |
|
||||
| record | `{ owner = Call.origin, value = 100000000 }` |
|
||||
| map | `{["foo"] = 19, ["bar"] = 42}`, `{}` |
|
||||
| option(int) | `Some(42)`, `None` |
|
||||
| state | `state{ owner = Call.origin, magic_key = #a298105f }` |
|
||||
| event | `EventX(0, "Hello")` |
|
||||
| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
|
||||
| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
|
||||
| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` |
|
||||
| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` |
|
||||
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
|
||||
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
|
||||
|
||||
## Arithmetic
|
||||
|
||||
Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following
|
||||
arithmetic operations:
|
||||
- addition (`x + y`)
|
||||
- subtraction (`x - y`)
|
||||
- multiplication (`x * y`)
|
||||
- division (`x / y`), truncated towards zero
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
corresponding integer, so setting very high bits can be expensive).
|
||||
|
||||
## Type aliases
|
||||
|
||||
Type aliases can be introduced with the `type` keyword and can be
|
||||
parameterized. For instance
|
||||
|
||||
```sophia
|
||||
type number = int
|
||||
type string_map('a) = map(string, 'a)
|
||||
```
|
||||
|
||||
A type alias and its definition can be used interchangeably. Sophia does not support
|
||||
higher-kinded types, meaning that following type alias is invalid: `type wrap('f, 'a) = 'f('a)`
|
||||
|
||||
## Algebraic data types
|
||||
|
||||
Sophia supports algebraic data types (variant types) and pattern matching. Data
|
||||
types are declared by giving a list of constructors with
|
||||
their respective arguments. For instance,
|
||||
|
||||
```sophia
|
||||
datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
|
||||
```
|
||||
|
||||
Elements of data types can be pattern matched against, using the `switch` construct:
|
||||
|
||||
```sophia
|
||||
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||
switch(x)
|
||||
Left(x) => Some(x)
|
||||
Right(_) => None
|
||||
Both(x, _) => Some(x)
|
||||
```
|
||||
|
||||
or directly in the left-hand side:
|
||||
```sophia
|
||||
function
|
||||
get_left : one_or_both('a, 'b) => option('a)
|
||||
get_left(Left(x)) = Some(x)
|
||||
get_left(Right(_)) = None
|
||||
get_left(Both(x, _)) = Some(x)
|
||||
```
|
||||
|
||||
*NOTE: Data types cannot currently be recursive.*
|
||||
|
||||
Sophia also supports the assignment of patterns to variables:
|
||||
```sophia
|
||||
function f(x) = switch(x)
|
||||
h1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`
|
||||
_ => x
|
||||
|
||||
function g(p : int * option(int)) : int =
|
||||
let (a, (o = Some(b))) = p // o is equal to Pair.snd(p)
|
||||
b
|
||||
```
|
||||
|
||||
Guards are boolean expressions that can be used on patterns in both switch
|
||||
statements and functions definitions. If a guard expression evaluates to
|
||||
`true`, then the corresponding body will be used. Otherwise, the next pattern
|
||||
will be checked:
|
||||
|
||||
```sophia
|
||||
function get_left_if_positive(x : one_or_both(int, 'b)) : option(int) =
|
||||
switch(x)
|
||||
Left(x) | x > 0 => Some(x)
|
||||
Both(x, _) | x > 0 => Some(x)
|
||||
_ => None
|
||||
```
|
||||
|
||||
```sophia
|
||||
function
|
||||
get_left_if_positive : one_or_both(int, 'b) => option(int)
|
||||
get_left_if_positive(Left(x)) | x > 0 = Some(x)
|
||||
get_left_if_positive(Both(x, _)) | x > 0 = Some(x)
|
||||
get_left_if_positive(_) = None
|
||||
```
|
||||
|
||||
Guards cannot be stateful even when used inside a stateful function.
|
||||
|
||||
## Lists
|
||||
|
||||
A Sophia list is a dynamically sized, homogenous, immutable, singly
|
||||
linked list. A list is constructed with the syntax `[1, 2, 3]`. The
|
||||
elements of a list can be any of datatype but they must have the same
|
||||
type. The type of lists with elements of type `'e` is written
|
||||
`list('e)`. For example we can have the following lists:
|
||||
|
||||
```sophia
|
||||
[1, 33, 2, 666] : list(int)
|
||||
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
|
||||
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
|
||||
```
|
||||
|
||||
New elements can be prepended to the front of a list with the `::`
|
||||
operator. So `42 :: [1, 2, 3]` returns the list `[42, 1, 2, 3]`. The
|
||||
concatenation operator `++` appends its second argument to its first
|
||||
and returns the resulting list. So concatenating two lists
|
||||
`[1, 22, 33] ++ [10, 18, 55]` returns the list `[1, 22, 33, 10, 18, 55]`.
|
||||
|
||||
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
|
||||
Example syntax:
|
||||
```sophia
|
||||
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
||||
// yields [12,13,14,20,21,22,30,31,32]
|
||||
```
|
||||
|
||||
Lists can be constructed using the range syntax using special `..` operator:
|
||||
```sophia
|
||||
[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
|
||||
|
||||
A Sophia record type is given by a fixed set of fields with associated,
|
||||
possibly different, types. For instance
|
||||
```sophia
|
||||
record account = { name : string,
|
||||
balance : int,
|
||||
history : list(transaction) }
|
||||
```
|
||||
|
||||
Maps, on the other hand, can contain an arbitrary number of key-value bindings,
|
||||
but of a fixed type. The type of maps with keys of type `'k` and values of type
|
||||
`'v` is written `map('k, 'v)`. The key type can be any type that does not
|
||||
contain a map or a function type.
|
||||
|
||||
Please refer to the [standard library](sophia_stdlib.md#map) for the predefined functionalities.
|
||||
|
||||
### Constructing maps and records
|
||||
|
||||
A value of record type is constructed by giving a value for each of the fields.
|
||||
For the example above,
|
||||
```sophia
|
||||
function new_account(name) =
|
||||
{name = name, balance = 0, history = []}
|
||||
```
|
||||
Maps are constructed similarly, with keys enclosed in square brackets
|
||||
```sophia
|
||||
function example_map() : map(string, int) =
|
||||
{["key1"] = 1, ["key2"] = 2}
|
||||
```
|
||||
The empty map is written `{}`.
|
||||
|
||||
### Accessing values
|
||||
|
||||
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
||||
```sophia
|
||||
function get_balance(a : address, accounts : map(address, account)) =
|
||||
accounts[a].balance
|
||||
```
|
||||
Looking up a non-existing key in a map results in contract execution failing. A
|
||||
default value to return for non-existing keys can be provided using the syntax
|
||||
`m[k = default]`. See also `Map.member` and `Map.lookup` below.
|
||||
|
||||
### Updating a value
|
||||
|
||||
Record field updates are written `r{f = v}`. This creates a new record value
|
||||
which is the same as `r`, but with the value of the field `f` replaced by `v`.
|
||||
Similarly, `m{[k] = v}` constructs a map with the same values as `m` except
|
||||
that `k` maps to `v`. It makes no difference if `m` has a mapping for `k` or
|
||||
not.
|
||||
|
||||
It is possible to give a name to the old value of a field or mapping in an
|
||||
update: instead of `acc{ balance = acc.balance + 100 }` it is possible to write
|
||||
`acc{ balance @ b = b + 100 }`, binding `b` to `acc.balance`. When giving a
|
||||
name to a map value (`m{ [k] @ x = v }`), the corresponding key must be present
|
||||
in the map or execution fails, but a default value can be provided:
|
||||
`m{ [k = default] @ x = v }`. In this case `x` is bound to `default` if
|
||||
`k` is not in the map.
|
||||
|
||||
Updates can be nested:
|
||||
```sophia
|
||||
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
||||
accounts{ [a].history = [] }
|
||||
```
|
||||
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
|
||||
requires `a` to be present in the accounts map. To have `clear_history` create
|
||||
an account if `a` is not in the map you can write (given a function `empty_account`):
|
||||
```sophia
|
||||
accounts{ [a = empty_account()].history = [] }
|
||||
```
|
||||
|
||||
### Map implementation
|
||||
|
||||
Internally in the VM maps are implemented as hash maps and support fast lookup
|
||||
and update. Large maps can be stored in the contract state and the size of the
|
||||
map does not contribute to the gas costs of a contract call reading or updating
|
||||
it.
|
||||
|
||||
## Strings
|
||||
|
||||
There is a builtin type `string`, which can be seen as an array of bytes.
|
||||
Strings can be compared for equality (`==`, `!=`), used as keys in maps and
|
||||
records, and used in builtin functions `String.length`, `String.concat` and
|
||||
the hash functions described below.
|
||||
|
||||
Please refer to the `String` [library documentation](sophia_stdlib.md#string).
|
||||
|
||||
## Chars
|
||||
|
||||
There is a builtin type `char` (the underlying representation being an integer),
|
||||
mainly used to manipulate strings via `String.to_list`/`String.from_list`.
|
||||
|
||||
Characters can also be introduced as character literals (`'x', '+', ...).
|
||||
|
||||
Please refer to the `Char` [library documentation](sophia_stdlib.md#char).
|
||||
|
||||
## Byte arrays
|
||||
|
||||
Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system,
|
||||
for example the literal `#cafe` creates a two-element array of bytes `ca` (202) and `fe` (254)
|
||||
and thus is a value of type `bytes(2)`.
|
||||
|
||||
Please refer to the `Bytes` [library documentation](sophia_stdlib.md#bytes).
|
||||
|
||||
## Cryptographic builtins
|
||||
|
||||
Libraries [Crypto](sophia_stdlib.md#crypto) and [String](sophia_stdlib.md#string) provide functions to
|
||||
hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`.
|
||||
|
||||
## Authorization interface
|
||||
|
||||
When a Generalized account is authorized, the authorization function needs
|
||||
access to the transaction and the transaction hash for the wrapped transaction. (A `GAMetaTx`
|
||||
wrapping a transaction.) The transaction and the transaction hash is available in the primitive
|
||||
`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a
|
||||
normal contract call they return `None`.
|
||||
|
||||
## Oracle interface
|
||||
You can attach an oracle to the current contract and you can interact with oracles
|
||||
through the Oracle interface.
|
||||
|
||||
For a full description of how Oracle works see
|
||||
[Oracles](https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#oracles).
|
||||
For a functionality documentation refer to the [standard library](sophia_stdlib.md#oracle).
|
||||
|
||||
### Example
|
||||
|
||||
Example for an oracle answering questions of type `string` with answers of type `int`:
|
||||
```sophia
|
||||
contract Oracles =
|
||||
|
||||
stateful entrypoint registerOracle(acct : address,
|
||||
sign : signature, // Signed network id + oracle address + contract address
|
||||
qfee : int,
|
||||
ttl : Chain.ttl) : oracle(string, int) =
|
||||
Oracle.register(acct, signature = sign, qfee, ttl)
|
||||
|
||||
entrypoint queryFee(o : oracle(string, int)) : int =
|
||||
Oracle.query_fee(o)
|
||||
|
||||
payable stateful entrypoint createQuery(o : oracle_query(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : int) : oracle_query(string, int) =
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
Oracle.query(o, q, qfee, qttl, RelativeTTL(rttl))
|
||||
|
||||
stateful entrypoint extendOracle(o : oracle(string, int),
|
||||
ttl : Chain.ttl) : unit =
|
||||
Oracle.extend(o, ttl)
|
||||
|
||||
stateful entrypoint signExtendOracle(o : oracle(string, int),
|
||||
sign : signature, // Signed network id + 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
|
||||
r : int) =
|
||||
Oracle.respond(o, q, signature = sign, r)
|
||||
|
||||
entrypoint getQuestion(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) : string =
|
||||
Oracle.get_question(o, q)
|
||||
|
||||
entrypoint hasAnswer(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) =
|
||||
switch(Oracle.get_answer(o, q))
|
||||
None => false
|
||||
Some(_) => true
|
||||
|
||||
entrypoint getAnswer(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) : option(int) =
|
||||
Oracle.get_answer(o, q)
|
||||
```
|
||||
|
||||
### Sanity checks
|
||||
|
||||
When an Oracle literal is passed to a contract, no deep checks are performed.
|
||||
For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query)
|
||||
functions are provided.
|
||||
|
||||
## AENS interface
|
||||
|
||||
Contracts can interact with the
|
||||
[æternity naming system](https://github.com/aeternity/protocol/blob/master/AENS.md).
|
||||
For this purpose the [AENS](sophia_stdlib.md#aens) library was exposed.
|
||||
|
||||
### Example
|
||||
|
||||
In this example we assume that the name `name` already exists, and is owned by
|
||||
an account with address `addr`. In order to allow a contract `ct` to handle
|
||||
`name` the account holder needs to create a
|
||||
[signature](#delegation-signature) `sig` of `addr | name.hash | ct.address`.
|
||||
|
||||
Armed with this information we can for example write a function that extends
|
||||
the name if it expires within 1000 blocks:
|
||||
```sophia
|
||||
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, FixedTTL(expiry), _)) =>
|
||||
if(Chain.block_height + 1000 > expiry)
|
||||
AENS.update(addr, name, Some(RelativeTTL(50000)), None, None, signature = sig)
|
||||
```
|
||||
|
||||
And we can write functions that adds and removes keys from the pointers of the
|
||||
name:
|
||||
```sophia
|
||||
stateful entrypoint add_key(addr : address, name : string, key : string,
|
||||
pt : AENS.pointee, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, _, ptrs)) =>
|
||||
AENS.update(addr, name, None, None, Some(ptrs{[key] = pt}), signature = sig)
|
||||
|
||||
stateful entrypoint delete_key(addr : address, name : string,
|
||||
key : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, _, ptrs)) =>
|
||||
let ptrs = Map.delete(key, ptrs)
|
||||
AENS.update(addr, name, None, None, Some(ptrs), signature = sig)
|
||||
```
|
||||
|
||||
*Note:* From the Iris hardfork more strict rules apply for AENS pointers, when
|
||||
a Sophia contract lookup or update (bad) legacy pointers, the bad keys are
|
||||
automatically removed so they will not appear in the pointers map.
|
||||
|
||||
## Events
|
||||
|
||||
Sophia contracts log structured messages to an event log in the resulting
|
||||
blockchain transaction. The event log is quite similar to [Events in
|
||||
Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events).
|
||||
Events are further discussed in the [protocol](https://github.com/aeternity/protocol/blob/master/contracts/events.md).
|
||||
|
||||
|
||||
To use events a contract must declare a datatype `event`, and events are then
|
||||
logged using the `Chain.event` function:
|
||||
|
||||
```sophia
|
||||
datatype event
|
||||
= Event1(int, int, string)
|
||||
| Event2(string, address)
|
||||
|
||||
Chain.event(e : event) : unit
|
||||
```
|
||||
|
||||
The event can have 0-3 *indexed* fields, and an optional *payload* field. A
|
||||
field is indexed if it fits in a 32-byte word, i.e.
|
||||
- `bool`
|
||||
- `int`
|
||||
- `bits`
|
||||
- `address`
|
||||
- `oracle(_, _)`
|
||||
- `oracle_query(_, _)`
|
||||
- contract types
|
||||
- `bytes(n)` for `n` ≤ 32, in particular `hash`
|
||||
|
||||
The payload field must be either a string or a byte array of more than 32 bytes.
|
||||
The fields can appear in any order.
|
||||
|
||||
*NOTE:* Indexing is not part of the core æternity node.
|
||||
|
||||
Events are emitted by using the `Chain.event` function. The following function
|
||||
will emit one Event of each kind in the example.
|
||||
|
||||
```sophia
|
||||
entrypoint emit_events() : () =
|
||||
Chain.event(Event1(42, 34, "foo"))
|
||||
Chain.event(Event2("This is not indexed", Contract.address))
|
||||
```
|
||||
|
||||
### Argument order
|
||||
|
||||
It is only possible to have one (1) `string` parameter in the event, but it can
|
||||
be placed in any position (and its value will end up in the `data` field), i.e.
|
||||
```sophia
|
||||
AnotherEvent(string, indexed address)
|
||||
|
||||
...
|
||||
|
||||
Chain.event(AnotherEvent("This is not indexed", Contract.address))
|
||||
```
|
||||
would yield exactly the same result in the example above!
|
||||
|
||||
## Compiler pragmas
|
||||
|
||||
To enforce that a contract is only compiled with specific versions of the
|
||||
Sophia compiler, you can give one or more `@compiler` pragmas at the
|
||||
top-level (typically at the beginning) of a file. For instance, to enforce that
|
||||
a contract is compiled with version 4.3 of the compiler you write
|
||||
|
||||
```sophia
|
||||
@compiler >= 4.3
|
||||
@compiler < 4.4
|
||||
```
|
||||
|
||||
Valid operators in compiler pragmas are `<`, `=<`, `==`, `>=`, and `>`. Version
|
||||
numbers are given as a sequence of non-negative integers separated by dots.
|
||||
Trailing zeros are ignored, so `4.0.0 == 4`. If a constraint is violated an
|
||||
error is reported and compilation fails.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Contracts can fail with an (uncatchable) exception using the built-in function
|
||||
|
||||
```sophia
|
||||
abort(reason : string) : 'a
|
||||
```
|
||||
|
||||
Calling abort causes the top-level call transaction to return an error result
|
||||
containing the `reason` string. Only the gas used up to and including the abort
|
||||
call is charged. This is different from termination due to a crash which
|
||||
consumes all available gas.
|
||||
|
||||
For convenience the following function is also built-in:
|
||||
|
||||
```sophia
|
||||
function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
```
|
||||
|
||||
## Delegation signature
|
||||
|
||||
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
|
||||
optional delegation signature. This is typically used when a user/accounts
|
||||
would like to allow a contract to act on it's behalf. The exact data to be
|
||||
signed varies for the different operations, but in all cases you should prepend
|
||||
the signature data with the `network_id` (`ae_mainnet` for the æternity mainnet, etc.).
|
||||
+530
-152
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
# Syntax
|
||||
|
||||
## Lexical syntax
|
||||
|
||||
### Comments
|
||||
|
||||
Single line comments start with `//` and block comments are enclosed in `/*`
|
||||
and `*/` and can be nested.
|
||||
|
||||
### Keywords
|
||||
|
||||
```
|
||||
contract elif else entrypoint false function if import include let mod namespace
|
||||
private payable stateful switch true type record datatype main interface
|
||||
```
|
||||
|
||||
### Tokens
|
||||
|
||||
- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter.
|
||||
- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter.
|
||||
- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`)
|
||||
- `QCon = (Con\.)+Con` qualified constructor
|
||||
- `TVar = 'Id` type variable (e.g `'a`, `'b`)
|
||||
- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators
|
||||
- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators
|
||||
- `String` string literal enclosed in `"` with escape character `\`
|
||||
- `Char` character literal enclosed in `'` with escape character `\`
|
||||
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
|
||||
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
|
||||
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
|
||||
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix
|
||||
|
||||
Valid string escape codes are
|
||||
|
||||
| Escape | ASCII | |
|
||||
|---------------|-------------|---|
|
||||
| `\b` | 8 | |
|
||||
| `\t` | 9 | |
|
||||
| `\n` | 10 | |
|
||||
| `\v` | 11 | |
|
||||
| `\f` | 12 | |
|
||||
| `\r` | 13 | |
|
||||
| `\e` | 27 | |
|
||||
| `\xHexDigits` | *HexDigits* | |
|
||||
|
||||
|
||||
See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the
|
||||
details on the base58 literals.
|
||||
|
||||
## Layout blocks
|
||||
|
||||
Sophia uses Python-style layout rules to group declarations and statements. A
|
||||
layout block with more than one element must start on a separate line and be
|
||||
indented more than the currently enclosing layout block. Blocks with a single
|
||||
element can be written on the same line as the previous token.
|
||||
|
||||
Each element of the block must share the same indentation and no part of an
|
||||
element may be indented less than the indentation of the block. For instance
|
||||
|
||||
```sophia
|
||||
contract Layout =
|
||||
function foo() = 0 // no layout
|
||||
function bar() = // layout block starts on next line
|
||||
let x = foo() // indented more than 2 spaces
|
||||
x
|
||||
+ 1 // the '+' is indented more than the 'x'
|
||||
```
|
||||
|
||||
## Notation
|
||||
|
||||
In describing the syntax below, we use the following conventions:
|
||||
|
||||
- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with
|
||||
some associated value (like `Id`).
|
||||
- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`.
|
||||
- Choices are separated by vertical bars: `|`.
|
||||
- Optional elements are enclosed in `[` square brackets `]`.
|
||||
- `(` Parentheses `)` are used for grouping.
|
||||
- Zero or more repetitions are denoted by a postfix `*`, and one or more
|
||||
repetitions by a `+`.
|
||||
- `Block(X)` denotes a layout block of `X`s.
|
||||
- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s
|
||||
separated by `S`s.
|
||||
- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty.
|
||||
|
||||
|
||||
## Declarations
|
||||
|
||||
A Sophia file consists of a sequence of *declarations* in a layout block.
|
||||
|
||||
```c
|
||||
File ::= Block(TopDecl)
|
||||
|
||||
TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
|
||||
| 'namespace' Con '=' Block(Decl)
|
||||
| '@compiler' PragmaOp Version
|
||||
| 'include' String
|
||||
|
||||
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||
|
||||
FunDecl ::= Id ':' Type // Type signature
|
||||
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
||||
|
||||
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
|
||||
Version ::= Sep1(Int, '.')
|
||||
|
||||
EModifier ::= 'payable' | 'stateful'
|
||||
FModifier ::= 'stateful' | 'private'
|
||||
|
||||
Args ::= '(' Sep(Pattern, ',') ')'
|
||||
```
|
||||
|
||||
Contract declarations must appear at the top-level.
|
||||
|
||||
For example,
|
||||
```sophia
|
||||
contract Test =
|
||||
type t = int
|
||||
entrypoint add (x : t, y : t) = x + y
|
||||
```
|
||||
|
||||
There are three forms of type declarations: type aliases (declared with the
|
||||
`type` keyword), record type definitions (`record`) and data type definitions
|
||||
(`datatype`):
|
||||
|
||||
```c
|
||||
TypeAlias ::= Type
|
||||
RecordType ::= '{' Sep(FieldType, ',') '}'
|
||||
DataType ::= Sep1(ConDecl, '|')
|
||||
|
||||
FieldType ::= Id ':' Type
|
||||
ConDecl ::= Con ['(' Sep1(Type, ',') ')']
|
||||
```
|
||||
|
||||
For example,
|
||||
```sophia
|
||||
record point('a) = {x : 'a, y : 'a}
|
||||
datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))
|
||||
type int_shape = shape(int)
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```c
|
||||
Type ::= Domain '=>' Type // Function type
|
||||
| Type '(' Sep(Type, ',') ')' // Type application
|
||||
| '(' Type ')' // Parens
|
||||
| 'unit' | Sep(Type, '*') // Tuples
|
||||
| Id | QId | TVar
|
||||
|
||||
Domain ::= Type // Single argument
|
||||
| '(' Sep(Type, ',') ')' // Multiple arguments
|
||||
```
|
||||
|
||||
The function type arrow associates to the right.
|
||||
|
||||
Example,
|
||||
```sophia
|
||||
'a => list('a) => (int * list('a))
|
||||
```
|
||||
|
||||
## Statements
|
||||
|
||||
Function bodies are blocks of *statements*, where a statement is one of the following
|
||||
|
||||
```c
|
||||
Stmt ::= 'switch' '(' Expr ')' Block(Case)
|
||||
| 'if' '(' Expr ')' Block(Stmt)
|
||||
| 'elif' '(' Expr ')' Block(Stmt)
|
||||
| 'else' Block(Stmt)
|
||||
| 'let' LetDef
|
||||
| Expr
|
||||
|
||||
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
||||
| Pattern '=' Block(Stmt) // Value definition
|
||||
|
||||
Case ::= Pattern '=>' Block(Stmt)
|
||||
Pattern ::= Expr
|
||||
```
|
||||
|
||||
`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,
|
||||
|
||||
```sophia
|
||||
let x : int = 4
|
||||
switch(f(x))
|
||||
None => 0
|
||||
Some(y) =>
|
||||
if(y > 10)
|
||||
"too big"
|
||||
elif(y < 3)
|
||||
"too small"
|
||||
else
|
||||
"just right"
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
```c
|
||||
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
|
||||
| '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
|
||||
| UnOp Expr // Unary operator ! b
|
||||
| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)
|
||||
| Expr '.' Id // Projection state.x
|
||||
| Expr '[' Expr ']' // Map lookup map[key]
|
||||
| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }
|
||||
| '[' 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
|
||||
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
||||
| AccountAddress | ContractAddress // Chain identifiers
|
||||
| OracleAddress | OracleQueryId // Chain identifiers
|
||||
|
||||
Generator ::= Pattern '<-' Expr // Generator
|
||||
| 'if' '(' Expr ')' // Guard
|
||||
| LetDef // Definition
|
||||
|
||||
LamArgs ::= '(' Sep(LamArg, ',') ')'
|
||||
LamArg ::= Id [':' Type]
|
||||
|
||||
FieldUpdate ::= Path '=' Expr
|
||||
Path ::= Id // Record field
|
||||
| '[' Expr ']' // Map key
|
||||
| Path '.' Id // Nested record field
|
||||
| Path '[' Expr ']' // Nested map key
|
||||
|
||||
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
|
||||
UnOp ::= '-' | '!'
|
||||
```
|
||||
|
||||
## Operators types
|
||||
|
||||
| Operators | Type
|
||||
| --- | ---
|
||||
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
||||
| `!` `&&` `\|\|` | logical operators
|
||||
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||
| `::` `++` | list operators
|
||||
|
||||
## Operator precendences
|
||||
|
||||
In order of highest to lowest precedence.
|
||||
|
||||
| Operators | Associativity
|
||||
| --- | ---
|
||||
| `!` | right
|
||||
| `^` | left
|
||||
| `*` `/` `mod` | left
|
||||
| `-` (unary) | right
|
||||
| `+` `-` | left
|
||||
| `::` `++` | right
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `&&` | right
|
||||
| `\|\|` | right
|
||||
@@ -0,0 +1,136 @@
|
||||
@compiler >= 4.3
|
||||
|
||||
namespace Bitwise =
|
||||
|
||||
// bit shift 'x' right 'n' postions
|
||||
function bsr(n : int, x : int) : int =
|
||||
let step = 2^n
|
||||
let res = x / step
|
||||
if (x >= 0 || x mod step == 0)
|
||||
res
|
||||
else
|
||||
res - 1
|
||||
|
||||
// bit shift 'x' left 'n' positions
|
||||
function bsl(n : int, x : int) : int =
|
||||
x * 2^n
|
||||
|
||||
// bit shift 'x' left 'n' positions, limit at 'lim' bits
|
||||
function bsli(n : int, x : int, lim : int) : int =
|
||||
(x * 2^n) mod (2^lim)
|
||||
|
||||
// bitwise 'and' for arbitrary precision integers
|
||||
function band(a : int, b : int) : int =
|
||||
if (a >= 0 && b >= 0)
|
||||
uband_(a, b)
|
||||
elif (b >= 0)
|
||||
ubnand_(b, -1 - a)
|
||||
elif (a >= 0)
|
||||
ubnand_(a, -1 - b)
|
||||
else
|
||||
-1 - ubor_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'or' for arbitrary precision integers
|
||||
function
|
||||
bor : (int, int) => int
|
||||
bor(0, b) = b
|
||||
bor(a, 0) = a
|
||||
bor(a : int, b : int) : int =
|
||||
if (a >= 0 && b >= 0)
|
||||
ubor_(a, b)
|
||||
elif (b >= 0)
|
||||
-1 - ubnand_(-1 - a, b)
|
||||
elif (a >= 0)
|
||||
-1 - ubnand_(-1 - b, a)
|
||||
else
|
||||
-1 - uband_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'xor' for arbitrary precision integers
|
||||
function
|
||||
bxor : (int, int) => int
|
||||
bxor(0, b) = b
|
||||
bxor(a, 0) = a
|
||||
bxor(a, b) =
|
||||
if (a >= 0 && b >= 0)
|
||||
ubxor_(a, b)
|
||||
elif (b >= 0)
|
||||
-1 - ubxor_(-1 - a, b)
|
||||
elif (a >= 0)
|
||||
-1 - ubxor_(a, -1 - b)
|
||||
else
|
||||
ubxor_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'not' for arbitrary precision integers
|
||||
function bnot(a : int) = bxor(a, -1)
|
||||
|
||||
// Bitwise 'and' for non-negative integers
|
||||
function uband(a : int, b : int) : int =
|
||||
require(a >= 0 && b >= 0, "uband is only defined for non-negative integers")
|
||||
switch((a, b))
|
||||
(0, _) => 0
|
||||
(_, 0) => 0
|
||||
_ => uband__(a, b, 1, 0)
|
||||
|
||||
private function uband_(a, b) = uband__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
uband__(0, b, val, acc) = acc
|
||||
uband__(a, 0, val, acc) = acc
|
||||
uband__(a, b, val, acc) =
|
||||
switch (a mod 2 + b mod 2)
|
||||
2 => uband__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => uband__(a / 2, b / 2, val * 2, acc)
|
||||
|
||||
// Bitwise 'or' for non-negative integers
|
||||
function ubor(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers")
|
||||
switch((a, b))
|
||||
(0, _) => b
|
||||
(_, 0) => a
|
||||
_ => ubor__(a, b, 1, 0)
|
||||
|
||||
private function ubor_(a, b) = ubor__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
ubor__(0, 0, val, acc) = acc
|
||||
ubor__(a, b, val, acc) =
|
||||
switch (a mod 2 + b mod 2)
|
||||
0 => ubor__(a / 2, b / 2, val * 2, acc)
|
||||
_ => ubor__(a / 2, b / 2, val * 2, acc + val)
|
||||
|
||||
//Bitwise 'xor' for non-negative integers
|
||||
function
|
||||
ubxor : (int, int) => int
|
||||
ubxor(0, b) = b
|
||||
ubxor(a, 0) = a
|
||||
ubxor(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
|
||||
ubxor__(a, b, 1, 0)
|
||||
|
||||
private function ubxor_(a, b) = ubxor__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
ubxor__(0, 0, val, acc) = acc
|
||||
ubxor__(a, b, val, acc) =
|
||||
switch(a mod 2 + b mod 2)
|
||||
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
|
||||
ubnand__(0, b, val, acc) = acc
|
||||
ubnand__(a, b, val, acc) =
|
||||
switch((a mod 2, b mod 2))
|
||||
(1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => ubnand__(a / 2, b / 2, val * 2, acc)
|
||||
@@ -1,3 +1,5 @@
|
||||
include "String.aes"
|
||||
|
||||
namespace Frac =
|
||||
|
||||
private function gcd(a : int, b : int) =
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace List =
|
||||
* `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0.
|
||||
*/
|
||||
function from_to_step(a : int, b : int, s : int) : list(int) =
|
||||
require(s > 0, "List.from_to_step: non-positive step")
|
||||
from_to_step_(a, b - (b-a) mod s, s, [])
|
||||
private function from_to_step_(a : int, b : int, s : int, acc : list(int)) : list(int) =
|
||||
if(b < a) acc
|
||||
@@ -208,10 +209,13 @@ namespace List =
|
||||
[] => false
|
||||
h::t => if(p(h)) true else any(p, t)
|
||||
|
||||
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l)
|
||||
|
||||
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l)
|
||||
function sum(l : list(int)) : int = switch(l)
|
||||
[] => 0
|
||||
h::t => h + sum(t)
|
||||
|
||||
function product(l : list(int)) : int = switch(l)
|
||||
[] => 1
|
||||
h::t => h * sum(t)
|
||||
|
||||
/** Zips two list by applying bimapping function on respective elements.
|
||||
* Drops the tail of the longer list.
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace Option =
|
||||
None => abort("Forced None value")
|
||||
Some(x) => x
|
||||
|
||||
/** Assume it is `Some` with custom error message
|
||||
*/
|
||||
function force_msg(o : option('a), err : string) : 'a = switch(o)
|
||||
None => abort(err)
|
||||
Some(x) => x
|
||||
|
||||
function contains(e : 'a, o : option('a)) = o == Some(e)
|
||||
|
||||
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
include "List.aes"
|
||||
include "Option.aes"
|
||||
include "Pair.aes"
|
||||
|
||||
namespace Set =
|
||||
record set('a) = { to_map : map('a, unit) }
|
||||
|
||||
function new() : set('a) =
|
||||
{ to_map = {} }
|
||||
|
||||
function member(e : 'a, s : set('a)) : bool =
|
||||
Map.member(e, s.to_map)
|
||||
|
||||
function insert(e : 'a, s : set('a)) : set('a) =
|
||||
{ to_map = s.to_map{[e] = ()} }
|
||||
|
||||
function delete(e : 'a, s : set('a)) : set('a) =
|
||||
{ to_map = Map.delete(e, s.to_map) }
|
||||
|
||||
function size(s : set('a)) : int =
|
||||
Map.size(s.to_map)
|
||||
|
||||
function to_list(s : set('a)) : list('a) =
|
||||
List.map(Pair.fst, Map.to_list(s.to_map))
|
||||
|
||||
function from_list(l : list('a)) : set('a) =
|
||||
{ to_map = Map.from_list(List.map((x) => (x, ()), l)) }
|
||||
|
||||
function filter(p : 'a => bool, s : set('a)) : set('a) =
|
||||
from_list(List.filter(p, to_list(s)))
|
||||
|
||||
function fold(f : ('a, 'b) => 'b, acc : 'b, s : set('a)) : 'b =
|
||||
List.foldr(f, acc, to_list(s))
|
||||
|
||||
function subtract(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
filter((x) => !member(x, s2), s1)
|
||||
|
||||
function intersection(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
filter((x) => member(x, s2), s1)
|
||||
|
||||
function intersection_list(sets : list(set('a))) : set('a) =
|
||||
List.foldr(
|
||||
intersection,
|
||||
Option.default(new(), List.first(sets)),
|
||||
Option.default([], List.tail(sets)))
|
||||
|
||||
function union(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
from_list(to_list(s1) ++ to_list(s2))
|
||||
|
||||
function union_list(sets : list(set('a))) : set('a) =
|
||||
List.foldr(union, new(), sets)
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
@@ -15,7 +15,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "5.0.0"},
|
||||
{relx, [{release, {aesophia, "6.1.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}},
|
||||
{ref,"05dfd7ffc7fb1e07ecc0b1e516da571f56d7dc8f"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+18
-9
@@ -21,6 +21,8 @@
|
||||
, json_encode_expr/1
|
||||
, json_encode_type/1]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-type aci_type() :: json | string.
|
||||
-type json() :: jsx:json_term().
|
||||
-type json_text() :: binary().
|
||||
@@ -68,9 +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),
|
||||
%% io:format("~p\n", [Ast]),
|
||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
%% io:format("~p\n", [TypedAst]),
|
||||
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 = {contract, _, {con, _, Name}, _}) ->
|
||||
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)) ],
|
||||
@@ -107,7 +107,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
||||
|| F <- sort_decls(contract_funcs(Contract)),
|
||||
is_entrypoint(F) ],
|
||||
|
||||
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}};
|
||||
#{contract => C3#{kind => Head, functions => Fdefs, payable => is_payable(Contract)}};
|
||||
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
|
||||
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
||||
#{namespace => #{name => encode_name(Name),
|
||||
@@ -232,13 +232,19 @@ do_render_aci_json(Json) ->
|
||||
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
|
||||
|
||||
decode_contract(#{contract := #{name := Name,
|
||||
kind := Kind,
|
||||
payable := Payable,
|
||||
type_defs := Ts0,
|
||||
functions := Fs} = C}) ->
|
||||
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
||||
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
||||
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
|
||||
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
|
||||
[payable(Payable), case Kind of
|
||||
contract_main -> "main contract ";
|
||||
contract_child -> "contract ";
|
||||
contract_interface -> "contract interface "
|
||||
end,
|
||||
io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts), decode_funcs(Fs)];
|
||||
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||
@@ -248,8 +254,8 @@ decode_contract(_) -> [].
|
||||
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
||||
|
||||
%% decode_func(#{name := init}) -> [];
|
||||
decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) ->
|
||||
[" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ",
|
||||
decode_func(#{name := Name, stateful:= Stateful, payable := Payable, arguments := As, returns := T}) ->
|
||||
[" ", payable(Payable), stateful(Stateful), "entrypoint ", io_lib:format("~s", [Name]), " : ",
|
||||
decode_args(As), " => ", decode_type(T), $\n].
|
||||
|
||||
decode_args(As) ->
|
||||
@@ -330,12 +336,15 @@ decode_tvar(#{name := N}) -> io_lib:format("~s", [N]).
|
||||
payable(true) -> "payable ";
|
||||
payable(false) -> "".
|
||||
|
||||
stateful(true) -> "stateful ";
|
||||
stateful(false) -> "".
|
||||
|
||||
%% #contract{Ann, Con, [Declarations]}.
|
||||
|
||||
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
|
||||
contract_funcs({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
[ D || D <- Decls, is_fun(D)].
|
||||
|
||||
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
|
||||
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
[ D || D <- Decls, is_type(D) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
|
||||
+451
-95
@@ -18,7 +18,9 @@
|
||||
, pp_type/2
|
||||
]).
|
||||
|
||||
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()}
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()] | var_args, utype()}
|
||||
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
|
||||
| {tuple_t, aeso_syntax:ann(), [utype()]}
|
||||
| aeso_syntax:id() | aeso_syntax:qid()
|
||||
@@ -39,6 +41,7 @@
|
||||
element(1, T) =:= qcon).
|
||||
|
||||
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
|
||||
| {var_args, aeso_syntax:ann(), aeso_syntax:expr()}
|
||||
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
|
||||
|
||||
-type pos() :: aeso_errors:pos().
|
||||
@@ -73,7 +76,10 @@
|
||||
-record(is_contract_constraint,
|
||||
{ contract_t :: utype(),
|
||||
context :: {contract_literal, aeso_syntax:expr()} |
|
||||
{address_to_contract, aeso_syntax:ann()}
|
||||
{address_to_contract, aeso_syntax:ann()} |
|
||||
{bytecode_hash, aeso_syntax:ann()} |
|
||||
{var_args, aeso_syntax:ann(), aeso_syntax:expr()},
|
||||
force_def = false :: boolean()
|
||||
}).
|
||||
|
||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||
@@ -96,7 +102,11 @@
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract.
|
||||
-type namespace_alias() :: none | name().
|
||||
-type namespace_parts() :: none | {for, [name()]} | {hiding, [name()]}.
|
||||
-type used_namespaces() :: [{qname(), namespace_alias(), namespace_parts()}].
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||
@@ -115,21 +125,24 @@
|
||||
-type scope() :: #scope{}.
|
||||
|
||||
-record(env,
|
||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||
, vars = [] :: [{name(), var_info()}]
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, namespace = [] :: qname()
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | main_contract
|
||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||
, vars = [] :: [{name(), var_info()}]
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, namespace = [] :: qname()
|
||||
, used_namespaces = [] :: used_namespaces()
|
||||
, in_pattern = false :: boolean()
|
||||
, in_guard = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | contract_interface
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
|
||||
-define(PRINT_TYPES(Fmt, Args),
|
||||
when_option(pp_types, fun () -> io:format(Fmt, Args) end)).
|
||||
-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#").
|
||||
|
||||
%% -- Environment manipulation -----------------------------------------------
|
||||
|
||||
@@ -191,9 +204,9 @@ bind_fun(X, Type, Env) ->
|
||||
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||
Ann = aeso_syntax:get_ann(Type),
|
||||
NoCode = get_option(no_code, false),
|
||||
Entry = if X == "init", What == main_contract, not NoCode ->
|
||||
Entry = if X == "init", What == contract, not NoCode ->
|
||||
{reserved_init, Ann, Type};
|
||||
What == contract -> {contract_fun, Ann, Type};
|
||||
What == contract_interface -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
||||
@@ -261,13 +274,40 @@ contract_call_type({fun_t, Ann, [], Args, Ret}) ->
|
||||
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
|
||||
|
||||
-spec bind_contract(aeso_syntax:decl(), env()) -> env().
|
||||
bind_contract({contract, Ann, Id, Contents}, Env) ->
|
||||
bind_contract({Contract, Ann, Id, Contents}, Env)
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
Key = name(Id),
|
||||
Sys = [{origin, system}],
|
||||
Fields = [ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||
%% Predefined fields
|
||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ],
|
||||
Fields =
|
||||
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|
||||
|| {fun_decl, AnnF, Entrypoint, Type} <- Contents ] ++
|
||||
[ {field_t, AnnF, Entrypoint,
|
||||
contract_call_type(
|
||||
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
|
||||
}
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents,
|
||||
Name =/= "init"
|
||||
] ++
|
||||
%% Predefined fields
|
||||
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
|
||||
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
|
||||
contract_call_type(
|
||||
case [ [ArgT || {typed, _, _, ArgT} <- Args]
|
||||
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
++ [ Args
|
||||
|| {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
++ [ Args
|
||||
|| {fun_decl, AnnF, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents,
|
||||
aeso_syntax:get_ann(entrypoint, AnnF, false)]
|
||||
of
|
||||
[] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}};
|
||||
[Args] -> {fun_t, [stateful,payable|Sys], [], Args, {id, Sys, "void"}}
|
||||
end
|
||||
)
|
||||
}
|
||||
],
|
||||
FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn,
|
||||
kind = contract,
|
||||
field_t = Type,
|
||||
@@ -278,9 +318,38 @@ bind_contract({contract, Ann, Id, Contents}, Env) ->
|
||||
|
||||
%% What scopes could a given name come from?
|
||||
-spec possible_scopes(env(), qname()) -> [qname()].
|
||||
possible_scopes(#env{ namespace = Current}, Name) ->
|
||||
possible_scopes(#env{ namespace = Current, used_namespaces = UsedNamespaces }, Name) ->
|
||||
Qual = lists:droplast(Name),
|
||||
[ lists:sublist(Current, I) ++ Qual || I <- lists:seq(0, length(Current)) ].
|
||||
NewQuals = case lists:filter(fun(X) -> element(2, X) == Qual end, UsedNamespaces) of
|
||||
[] ->
|
||||
[Qual];
|
||||
Namespaces ->
|
||||
lists:map(fun(X) -> element(1, X) end, Namespaces)
|
||||
end,
|
||||
Ret1 = [ lists:sublist(Current, I) ++ Q || I <- lists:seq(0, length(Current)), Q <- NewQuals ],
|
||||
Ret2 = [ Namespace ++ Q || {Namespace, none, _} <- UsedNamespaces, Q <- NewQuals ],
|
||||
lists:usort(Ret1 ++ Ret2).
|
||||
|
||||
-spec visible_in_used_namespaces(used_namespaces(), qname()) -> boolean().
|
||||
visible_in_used_namespaces(UsedNamespaces, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
case lists:filter(fun({Ns, _, _}) -> Qual == Ns end, UsedNamespaces) of
|
||||
[] ->
|
||||
true;
|
||||
Namespaces ->
|
||||
IsVisible = fun(Namespace) ->
|
||||
case Namespace of
|
||||
{_, _, {for, Names}} ->
|
||||
lists:member(Name, Names);
|
||||
{_, _, {hiding, Names}} ->
|
||||
not lists:member(Name, Names);
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end,
|
||||
lists:any(IsVisible, Namespaces)
|
||||
end.
|
||||
|
||||
-spec lookup_type(env(), type_id()) -> false | {qname(), type_info()}.
|
||||
lookup_type(Env, Id) ->
|
||||
@@ -307,7 +376,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
||||
end.
|
||||
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
|
||||
lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
AllowPrivate = lists:prefix(Qual, Current),
|
||||
@@ -331,7 +400,11 @@ lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
{Ann1, _} = E ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
case not is_private(Ann1) orelse AllowPrivate of
|
||||
true -> {QName, E};
|
||||
true ->
|
||||
case visible_in_used_namespaces(UsedNamespaces, QName) of
|
||||
true -> {QName, E};
|
||||
false -> false
|
||||
end;
|
||||
false -> false
|
||||
end
|
||||
end
|
||||
@@ -396,8 +469,11 @@ global_env() ->
|
||||
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
|
||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
|
||||
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
|
||||
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
|
||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||
FunCN = fun(C, Named, Normal, Ret) -> {type_sig, Ann, C, Named, Normal, Ret} end,
|
||||
FunN = fun(Named, Normal, Ret) -> FunCN(none, Named, Normal, Ret) end,
|
||||
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
|
||||
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
|
||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
|
||||
@@ -424,6 +500,7 @@ global_env() ->
|
||||
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
|
||||
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
|
||||
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
|
||||
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
|
||||
|
||||
Fee = Int,
|
||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||
@@ -443,6 +520,7 @@ global_env() ->
|
||||
{"require", Fun([Bool, String], Unit)}])
|
||||
, types = MkDefs(
|
||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
||||
{"void", 0},
|
||||
{"unit", {[], {alias_t, Unit}}},
|
||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||
@@ -463,6 +541,23 @@ global_env() ->
|
||||
{"block_height", Int},
|
||||
{"difficulty", Int},
|
||||
{"gas_limit", Int},
|
||||
{"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))},
|
||||
{"create", Stateful(
|
||||
FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||
], var_args, A))},
|
||||
{"clone", Stateful(
|
||||
FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int,
|
||||
{typed, Ann,
|
||||
{app, Ann,
|
||||
{typed, Ann, {qid, Ann, ["Call","gas_left"]},
|
||||
typesig_to_fun_t(Fun([], Int))
|
||||
},
|
||||
[]}, Int
|
||||
}}
|
||||
, {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
|
||||
, {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {typed, Ann, {bool, Ann, false}, Bool}}
|
||||
, {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined}
|
||||
], var_args, A))},
|
||||
%% Tx constructors
|
||||
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
|
||||
{"PayingForTx", Fun([Address, Int], PayForTx)},
|
||||
@@ -505,6 +600,7 @@ global_env() ->
|
||||
{"caller", Address},
|
||||
{"value", Int},
|
||||
{"gas_price", Int},
|
||||
{"fee", Int},
|
||||
{"gas_left", Fun([], Int)}])
|
||||
},
|
||||
|
||||
@@ -699,9 +795,13 @@ infer(Contracts, Options) ->
|
||||
try
|
||||
Env = init_env(Options),
|
||||
create_options(Options),
|
||||
ets_new(defined_contracts, [bag]),
|
||||
ets_new(type_vars, [set]),
|
||||
check_modifiers(Env, Contracts),
|
||||
{Env1, Decls} = infer1(Env, Contracts, [], Options),
|
||||
create_type_errors(),
|
||||
Contracts1 = identify_main_contract(Contracts, Options),
|
||||
destroy_and_report_type_errors(Env),
|
||||
{Env1, Decls} = infer1(Env, Contracts1, [], Options),
|
||||
{Env2, DeclsFolded, DeclsUnfolded} =
|
||||
case proplists:get_value(dont_unfold, Options, false) of
|
||||
true -> {Env1, Decls, Decls};
|
||||
@@ -719,12 +819,21 @@ infer(Contracts, Options) ->
|
||||
-spec infer1(env(), [aeso_syntax:decl()], [aeso_syntax:decl()], list(option())) ->
|
||||
{env(), [aeso_syntax:decl()]}.
|
||||
infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
|
||||
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
|
||||
infer1(Env, [{Contract, Ann, ConName, Code} | Rest], Acc, Options)
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
%% do type inference on each contract independently.
|
||||
check_scope_name_clash(Env, contract, ConName),
|
||||
What = if Rest == [] -> main_contract; true -> contract end,
|
||||
What = case Contract of
|
||||
contract_main -> contract;
|
||||
contract_child -> contract;
|
||||
contract_interface -> contract_interface
|
||||
end,
|
||||
case What of
|
||||
contract -> ets_insert(defined_contracts, {qname(ConName)});
|
||||
contract_interface -> ok
|
||||
end,
|
||||
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
|
||||
Contract1 = {contract, Ann, ConName, Code1},
|
||||
Contract1 = {Contract, Ann, ConName, Code1},
|
||||
Env2 = pop_scope(Env1),
|
||||
Env3 = bind_contract(Contract1, Env2),
|
||||
infer1(Env3, Rest, [Contract1 | Acc], Options);
|
||||
@@ -733,10 +842,31 @@ infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
|
||||
Namespace1 = {namespace, Ann, Name, Code1},
|
||||
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options);
|
||||
infer1(Env, [Using = {using, _, _, _, _} | Rest], Acc, Options) ->
|
||||
infer1(check_usings(Env, Using), Rest, Acc, Options);
|
||||
infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
||||
%% Pragmas are checked in check_modifiers
|
||||
infer1(Env, Rest, Acc, Options).
|
||||
|
||||
%% Asserts that the main contract is somehow defined.
|
||||
identify_main_contract(Contracts, Options) ->
|
||||
Children = [C || C = {contract_child, _, _, _} <- Contracts],
|
||||
Mains = [C || C = {contract_main, _, _, _} <- Contracts],
|
||||
case Mains of
|
||||
[] -> case Children of
|
||||
[] -> type_error(
|
||||
{main_contract_undefined,
|
||||
[{file, File} || {src_file, File} <- Options]});
|
||||
[{contract_child, Ann, Con, Body}] ->
|
||||
(Contracts -- Children) ++ [{contract_main, Ann, Con, Body}];
|
||||
[H|_] -> type_error({ambiguous_main_contract,
|
||||
aeso_syntax:get_ann(H)})
|
||||
end;
|
||||
[_] -> (Contracts -- Mains) ++ Mains; %% Move to the end
|
||||
[H|_] -> type_error({multiple_main_contracts,
|
||||
aeso_syntax:get_ann(H)})
|
||||
end.
|
||||
|
||||
check_scope_name_clash(Env, Kind, Name) ->
|
||||
case get_scope(Env, qname(Name)) of
|
||||
false -> ok;
|
||||
@@ -746,7 +876,7 @@ check_scope_name_clash(Env, Kind, Name) ->
|
||||
destroy_and_report_type_errors(Env)
|
||||
end.
|
||||
|
||||
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
||||
-spec infer_contract_top(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
||||
{env(), [aeso_syntax:decl()]}.
|
||||
infer_contract_top(Env, Kind, Defs0, Options) ->
|
||||
create_type_errors(),
|
||||
@@ -756,7 +886,7 @@ infer_contract_top(Env, Kind, Defs0, Options) ->
|
||||
|
||||
%% 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()]}.
|
||||
-spec infer_contract(env(), contract_interface | contract | namespace, [aeso_syntax:decl()], list(option())) -> {env(), [aeso_syntax:decl()]}.
|
||||
infer_contract(Env0, What, Defs0, Options) ->
|
||||
create_type_errors(),
|
||||
Defs01 = process_blocks(Defs0),
|
||||
@@ -770,21 +900,24 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
({letfun, _, _, _, _, _}) -> function;
|
||||
({fun_clauses, _, _, _, _}) -> function;
|
||||
({fun_decl, _, _, _}) -> prototype;
|
||||
({using, _, _, _, _}) -> using;
|
||||
(_) -> unexpected
|
||||
end,
|
||||
Get = fun(K) -> [ Def || Def <- Defs, Kind(Def) == K ] end,
|
||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type)),
|
||||
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
|
||||
OldUsedNamespaces = Env#env.used_namespaces,
|
||||
Env01 = check_usings(Env, Get(using, Defs)),
|
||||
{Env1, TypeDefs} = check_typedefs(Env01, Get(type, Defs)),
|
||||
create_type_errors(),
|
||||
check_unexpected(Get(unexpected)),
|
||||
check_unexpected(Get(unexpected, Defs)),
|
||||
Env2 =
|
||||
case What of
|
||||
namespace -> Env1;
|
||||
contract -> Env1;
|
||||
main_contract -> bind_state(Env1) %% bind state and put
|
||||
namespace -> Env1;
|
||||
contract_interface -> Env1;
|
||||
contract -> bind_state(Env1) %% bind state and put
|
||||
end,
|
||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
|
||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype, Defs) ]),
|
||||
Env3 = bind_funs(ProtoSigs, Env2),
|
||||
Functions = Get(function),
|
||||
Functions = Get(function, Defs),
|
||||
%% Check for duplicates in Functions (we turn it into a map below)
|
||||
FunBind = fun({letfun, Ann, {id, _, Fun}, _, _, _}) -> {Fun, {tuple_t, Ann, []}};
|
||||
({fun_clauses, Ann, {id, _, Fun}, _, _}) -> {Fun, {tuple_t, Ann, []}} end,
|
||||
@@ -794,12 +927,14 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
check_reserved_entrypoints(FunMap),
|
||||
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
||||
SCCs = aeso_utils:scc(DepGraph),
|
||||
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||
%% Remove namespaces used in the current namespace
|
||||
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env4, Defs1),
|
||||
destroy_and_report_type_errors(Env4),
|
||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||
%% Add inferred types of definitions
|
||||
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
||||
|
||||
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
||||
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
|
||||
@@ -830,9 +965,9 @@ 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
|
||||
namespace -> [A ||A <- Ann, A /= {private, true}, A /= private];
|
||||
contract -> [{entrypoint, true}|Ann]; % minor duplication
|
||||
contract_interface -> Ann
|
||||
end,
|
||||
Def1 = setelement(2, Def, NewAnn),
|
||||
case Def1 of % fix inner clauses
|
||||
@@ -899,6 +1034,43 @@ check_typedef(Env, {variant_t, Cons}) ->
|
||||
{variant_t, [ {constr_t, Ann, Con, [ check_type(Env, Arg) || Arg <- Args ]}
|
||||
|| {constr_t, Ann, Con, Args} <- Cons ]}.
|
||||
|
||||
check_usings(Env, []) ->
|
||||
Env;
|
||||
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
|
||||
AliasName = case Alias of
|
||||
none ->
|
||||
none;
|
||||
_ ->
|
||||
qname(Alias)
|
||||
end,
|
||||
case get_scope(Env, qname(Con)) of
|
||||
false ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace, Ann, qname(Con)}),
|
||||
destroy_and_report_type_errors(Env);
|
||||
Scope ->
|
||||
Nsp = case Parts of
|
||||
none ->
|
||||
{qname(Con), AliasName, none};
|
||||
{ForOrHiding, Ids} ->
|
||||
IsUndefined = fun(Id) ->
|
||||
proplists:lookup(name(Id), Scope#scope.funs) == none
|
||||
end,
|
||||
UndefinedIds = lists:filter(IsUndefined, Ids),
|
||||
case UndefinedIds of
|
||||
[] ->
|
||||
{qname(Con), AliasName, {ForOrHiding, lists:map(fun name/1, Ids)}};
|
||||
_ ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace_parts, Ann, qname(Con), lists:map(fun qname/1, UndefinedIds)}),
|
||||
destroy_and_report_type_errors(Env)
|
||||
end
|
||||
end,
|
||||
check_usings(Env#env{ used_namespaces = UsedNamespaces ++ [Nsp] }, Rest)
|
||||
end;
|
||||
check_usings(Env, Using = {using, _, _, _, _}) ->
|
||||
check_usings(Env, [Using]).
|
||||
|
||||
check_unexpected(Xs) ->
|
||||
[ type_error(X) || X <- Xs ].
|
||||
|
||||
@@ -907,15 +1079,16 @@ check_modifiers(Env, Contracts) ->
|
||||
check_modifiers_(Env, Contracts),
|
||||
destroy_and_report_type_errors(Env).
|
||||
|
||||
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
|
||||
IsMain = Rest == [],
|
||||
check_modifiers_(Env, [{Contract, _, Con, Decls} | Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
IsInterface = Contract =:= contract_interface,
|
||||
check_modifiers1(contract, Decls),
|
||||
case {lists:keymember(letfun, 1, Decls),
|
||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
||||
_ when not IsMain ->
|
||||
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
|
||||
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
|
||||
_ when IsInterface ->
|
||||
case [ {AnnF, Id} || {letfun, AnnF, Id, _, _, _} <- Decls ] of
|
||||
[{AnnF, Id} | _] -> type_error({definition_in_contract_interface, AnnF, Id});
|
||||
[] -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
@@ -927,6 +1100,8 @@ check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
|
||||
check_modifiers_(Env, [{pragma, Ann, Pragma} | Rest]) ->
|
||||
check_pragma(Env, Ann, Pragma),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{using, _, _, _, _} | Rest]) ->
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [Decl | Rest]) ->
|
||||
type_error({bad_top_level_decl, Decl}),
|
||||
check_modifiers_(Env, Rest);
|
||||
@@ -1182,23 +1357,29 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
||||
infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) ->
|
||||
{{Name, Sig}, Clause} = infer_letfun1(Env, LetFun),
|
||||
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}.
|
||||
|
||||
infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
|
||||
infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, GuardedBodies}) ->
|
||||
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||
current_function = Fun },
|
||||
{NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}),
|
||||
ExpectedType = check_type(Env, arg_type(NameAttrib, What)),
|
||||
NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType),
|
||||
InferGuardedBodies = fun({guarded, Ann, Guards, Body}) ->
|
||||
NewGuards = lists:map(fun(Guard) ->
|
||||
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"})
|
||||
end, Guards),
|
||||
NewBody = check_expr(NewEnv, Body, ExpectedType),
|
||||
{guarded, Ann, NewGuards, NewBody}
|
||||
end,
|
||||
NewGuardedBodies = [{guarded, _, _, {typed, _, _, ResultType}} | _] = lists:map(InferGuardedBodies, GuardedBodies),
|
||||
NamedArgs = [],
|
||||
TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType},
|
||||
{{Name, TypeSig},
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewBody}}.
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}.
|
||||
|
||||
desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
||||
NeedDesugar =
|
||||
case Clauses of
|
||||
[{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As);
|
||||
_ -> true
|
||||
[{letfun, _, _, As, _, [{guarded, _, [], _}]}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As);
|
||||
_ -> true
|
||||
end,
|
||||
case NeedDesugar of
|
||||
false -> [Clause] = Clauses, Clause;
|
||||
@@ -1209,11 +1390,10 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
||||
Tuple = fun([X]) -> X;
|
||||
(As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}}
|
||||
end,
|
||||
{letfun, Ann, Fun, Args, RetType,
|
||||
{typed, NoAnn,
|
||||
{switch, NoAnn, Tuple(Args),
|
||||
[ {'case', AnnC, Tuple(ArgsC), Body}
|
||||
|| {letfun, AnnC, _, ArgsC, _, Body} <- Clauses ]}, RetType}}
|
||||
{letfun, Ann, Fun, Args, RetType, [{guarded, NoAnn, [], {typed, NoAnn,
|
||||
{switch, NoAnn, Tuple(Args),
|
||||
[ {'case', AnnC, Tuple(ArgsC), GuardedBodies}
|
||||
|| {letfun, AnnC, _, ArgsC, _, GuardedBodies} <- Clauses ]}, RetType}}]}
|
||||
end.
|
||||
|
||||
print_typesig({Name, TypeSig}) ->
|
||||
@@ -1251,6 +1431,12 @@ lookup_name(Env, As, Id, Options) ->
|
||||
{set_qname(QId, Id), Ty1}
|
||||
end.
|
||||
|
||||
check_stateful(#env{ in_guard = true }, Id, Type = {type_sig, _, _, _, _, _}) ->
|
||||
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||
false -> ok;
|
||||
true ->
|
||||
type_error({stateful_not_allowed_in_guards, Id})
|
||||
end;
|
||||
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _, _}) ->
|
||||
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||
false -> ok;
|
||||
@@ -1275,7 +1461,7 @@ check_state_dependencies(Env, Defs) ->
|
||||
SetState = Top ++ ["put"],
|
||||
Init = Top ++ ["init"],
|
||||
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
|
||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
|
||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
|
||||
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||
case maps:get(Init, Deps, false) of
|
||||
false -> ok; %% No init, so nothing to check
|
||||
@@ -1379,12 +1565,12 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re
|
||||
infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, E),
|
||||
BlockType = fresh_uvar(AsLV),
|
||||
{'case', _, NewPattern, NewRest} =
|
||||
{'case', _, NewPattern, [{guarded, _, [], NewRest}]} =
|
||||
infer_case( Env
|
||||
, AsLC
|
||||
, Pattern
|
||||
, PatType
|
||||
, {list_comp, AsLC, Yield, Rest}
|
||||
, [{guarded, AsLC, [], {list_comp, AsLC, Yield, Rest}}]
|
||||
, BlockType),
|
||||
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = NewRest,
|
||||
{ typed
|
||||
@@ -1408,8 +1594,7 @@ infer_expr(Env, {typed, As, Body, Type}) ->
|
||||
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
|
||||
{typed, As, NewBody, NewType};
|
||||
infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
||||
Args = Args0 -- NamedArgs,
|
||||
{NamedArgs, Args} = split_args(Args0),
|
||||
case aeso_syntax:get_ann(format, Ann) of
|
||||
infix ->
|
||||
infer_op(Env, Ann, Fun, Args, fun infer_infix/1);
|
||||
@@ -1418,13 +1603,13 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
_ ->
|
||||
NamedArgsVar = fresh_uvar(Ann),
|
||||
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
|
||||
%% TODO: named args constraints
|
||||
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
|
||||
NewFun0 = infer_expr(Env, Fun),
|
||||
NewArgs = [infer_expr(Env, A) || A <- Args],
|
||||
ArgTypes = [T || {typed, _, _, T} <- NewArgs],
|
||||
NewFun1 = {typed, _, _, FunType} = infer_var_args_fun(Env, NewFun0, NamedArgs1, ArgTypes),
|
||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||
GeneralResultType = fresh_uvar(Ann),
|
||||
ResultType = fresh_uvar(Ann),
|
||||
When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes},
|
||||
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When),
|
||||
add_named_argument_constraint(
|
||||
#dependent_type_constraint{ named_args_t = NamedArgsVar,
|
||||
@@ -1432,7 +1617,7 @@ infer_expr(Env, {app, Ann, Fun, Args0} = App) ->
|
||||
general_type = GeneralResultType,
|
||||
specialized_type = ResultType,
|
||||
context = {check_return, App} }),
|
||||
{typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||
{typed, Ann, {app, Ann, NewFun1, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
|
||||
end;
|
||||
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
|
||||
@@ -1443,8 +1628,8 @@ infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||
infer_expr(Env, {switch, Attrs, Expr, Cases}) ->
|
||||
NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr),
|
||||
SwitchType = fresh_uvar(Attrs),
|
||||
NewCases = [infer_case(Env, As, Pattern, ExprType, Branch, SwitchType)
|
||||
|| {'case', As, Pattern, Branch} <- Cases],
|
||||
NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType)
|
||||
|| {'case', As, Pattern, GuardedBranches} <- Cases],
|
||||
{typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType};
|
||||
infer_expr(Env, {record, Attrs, Fields}) ->
|
||||
RecordType = fresh_uvar(Attrs),
|
||||
@@ -1526,10 +1711,13 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
|
||||
ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args],
|
||||
ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args],
|
||||
ResultType = fresh_uvar(Attrs),
|
||||
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} =
|
||||
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
|
||||
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, [{guarded, _, [], NewBody}]} =
|
||||
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, [{guarded, Attrs, [], Body}], ResultType),
|
||||
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
|
||||
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}};
|
||||
infer_expr(Env, {letpat, Attrs, Id, Pattern}) ->
|
||||
NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern),
|
||||
{typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType};
|
||||
infer_expr(Env, Let = {letval, Attrs, _, _}) ->
|
||||
type_error({missing_body_for_let, Attrs}),
|
||||
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
|
||||
@@ -1537,6 +1725,62 @@ infer_expr(Env, Let = {letfun, Attrs, _, _, _, _}) ->
|
||||
type_error({missing_body_for_let, Attrs}),
|
||||
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]}).
|
||||
|
||||
infer_var_args_fun(Env, {typed, Ann, Fun, FunType0}, NamedArgs, ArgTypes) ->
|
||||
FunType =
|
||||
case Fun of
|
||||
{qid, _, ["Chain", "create"]} ->
|
||||
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
||||
GasCapMock = {named_arg_t, Ann, {id, Ann, "gas"}, {id, Ann, "int"}, {int, Ann, 0}},
|
||||
ProtectedMock = {named_arg_t, Ann, {id, Ann, "protected"}, {id, Ann, "bool"}, {bool, Ann, false}},
|
||||
NamedArgsT1 = case NamedArgsT of
|
||||
[Value|Rest] -> [GasCapMock, Value, ProtectedMock|Rest];
|
||||
% generally type error, but will be caught
|
||||
_ -> [GasCapMock, ProtectedMock|NamedArgsT]
|
||||
end,
|
||||
check_contract_construction(Env, true, RetT, Fun, NamedArgsT1, ArgTypes, RetT),
|
||||
{fun_t, Ann, NamedArgsT, ArgTypes, RetT};
|
||||
{qid, _, ["Chain", "clone"]} ->
|
||||
{fun_t, _, NamedArgsT, var_args, RetT} = FunType0,
|
||||
ContractT =
|
||||
case [ContractT || {named_arg, _, {id, _, "ref"}, {typed, _, _, ContractT}} <- NamedArgs] of
|
||||
[C] -> C;
|
||||
_ -> type_error({clone_no_contract, Ann}),
|
||||
fresh_uvar(Ann)
|
||||
end,
|
||||
NamedArgsTNoRef =
|
||||
lists:filter(fun({named_arg_t, _, {id, _, "ref"}, _, _}) -> false; (_) -> true end, NamedArgsT),
|
||||
check_contract_construction(Env, false, ContractT, Fun, NamedArgsTNoRef, ArgTypes, RetT),
|
||||
{fun_t, Ann, NamedArgsT, ArgTypes,
|
||||
{if_t, Ann, {id, Ann, "protected"}, {app_t, Ann, {id, Ann, "option"}, [RetT]}, RetT}};
|
||||
_ -> FunType0
|
||||
end,
|
||||
{typed, Ann, Fun, FunType}.
|
||||
|
||||
-spec check_contract_construction(env(), boolean(), utype(), utype(), named_args_t(), [utype()], utype()) -> ok.
|
||||
check_contract_construction(Env, ForceDef, ContractT, Fun, NamedArgsT, ArgTypes, RetT) ->
|
||||
Ann = aeso_syntax:get_ann(Fun),
|
||||
InitT = fresh_uvar(Ann),
|
||||
unify(Env, InitT, {fun_t, Ann, NamedArgsT, ArgTypes, fresh_uvar(Ann)}, {checking_init_args, Ann, ContractT, ArgTypes}),
|
||||
unify(Env, RetT, ContractT, {return_contract, Fun, ContractT}),
|
||||
constrain(
|
||||
[ #field_constraint{
|
||||
record_t = unfold_types_in_type(Env, ContractT),
|
||||
field = {id, Ann, ?CONSTRUCTOR_MOCK_NAME},
|
||||
field_t = InitT,
|
||||
kind = project,
|
||||
context = {var_args, Ann, Fun} }
|
||||
, #is_contract_constraint{ contract_t = ContractT,
|
||||
context = {var_args, Ann, Fun},
|
||||
force_def = ForceDef
|
||||
}
|
||||
]),
|
||||
ok.
|
||||
|
||||
split_args(Args0) ->
|
||||
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
|
||||
Args = Args0 -- NamedArgs,
|
||||
{NamedArgs, Args}.
|
||||
|
||||
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
||||
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
||||
check_stateful_named_arg(Env, Id, E),
|
||||
@@ -1604,11 +1848,18 @@ infer_pattern(Env, Pattern) ->
|
||||
NewPattern = infer_expr(NewEnv, Pattern),
|
||||
{NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}.
|
||||
|
||||
infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
||||
infer_case(Env, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) ->
|
||||
{NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern),
|
||||
NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType),
|
||||
InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) ->
|
||||
NewGuards = lists:map(fun(Guard) ->
|
||||
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"})
|
||||
end, Guards),
|
||||
NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType),
|
||||
{guarded, Ann, NewGuards, NewBranch}
|
||||
end,
|
||||
NewGuardedBranches = lists:map(InferGuardedBranches, GuardedBranches),
|
||||
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
||||
{'case', Attrs, NewPattern, NewBranch}.
|
||||
{'case', Attrs, NewPattern, NewGuardedBranches}.
|
||||
|
||||
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
||||
infer_block(_Env, Attrs, [], BlockType) ->
|
||||
@@ -1622,9 +1873,11 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
|
||||
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
||||
infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, E),
|
||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
|
||||
{'case', _, NewPattern, [{guarded, _, [], {typed, _, {block, _, NewRest}, _}}]} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, [{guarded, Attrs, [], {block, Attrs, Rest}}], BlockType),
|
||||
[{letval, Attrs, NewPattern, NewE}|NewRest];
|
||||
infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) ->
|
||||
infer_block(check_usings(Env, Using), Attrs, Rest, BlockType);
|
||||
infer_block(Env, Attrs, [E|Rest], BlockType) ->
|
||||
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
|
||||
|
||||
@@ -1687,6 +1940,8 @@ free_vars({record, _, Fields}) ->
|
||||
free_vars([E || {field, _, _, E} <- Fields]);
|
||||
free_vars({typed, _, A, _}) ->
|
||||
free_vars(A);
|
||||
free_vars({letpat, _, Id, Pat}) ->
|
||||
free_vars(Id) ++ free_vars(Pat);
|
||||
free_vars(L) when is_list(L) ->
|
||||
[V || Elem <- L,
|
||||
V <- free_vars(Elem)].
|
||||
@@ -1704,7 +1959,7 @@ next_count() ->
|
||||
|
||||
ets_tables() ->
|
||||
[options, type_vars, type_defs, record_fields, named_argument_constraints,
|
||||
field_constraints, freshen_tvars, type_errors].
|
||||
field_constraints, freshen_tvars, type_errors, defined_contracts].
|
||||
|
||||
clean_up_ets() ->
|
||||
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
|
||||
@@ -1814,7 +2069,7 @@ solve_named_argument_constraints(Env, Constraints0) ->
|
||||
[ C || C <- dereference_deep(Constraints0),
|
||||
unsolved == check_named_argument_constraint(Env, C) ].
|
||||
|
||||
%% If false, a type error have been emitted, so it's safe to drop the constraint.
|
||||
%% If false, a type error has been emitted, so it's safe to drop the constraint.
|
||||
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
|
||||
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
|
||||
unsolved;
|
||||
@@ -1978,12 +2233,20 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
||||
end,
|
||||
check_record_create_constraints(Env, Cs).
|
||||
|
||||
is_contract_defined(C) ->
|
||||
ets_lookup(defined_contracts, qname(C)) =/= [].
|
||||
|
||||
check_is_contract_constraints(_Env, []) -> ok;
|
||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||
#is_contract_constraint{ contract_t = Type, context = Cxt } = C,
|
||||
#is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C,
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
case lookup_type(Env, record_type_name(Type1)) of
|
||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
||||
TypeName = record_type_name(Type1),
|
||||
case lookup_type(Env, TypeName) of
|
||||
{_, {_Ann, {[], {contract_t, _}}}} ->
|
||||
case not ForceDef orelse is_contract_defined(TypeName) of
|
||||
true -> ok;
|
||||
false -> type_error({contract_lacks_definition, Type1, Cxt})
|
||||
end;
|
||||
_ -> type_error({not_a_contract_type, Type1, Cxt})
|
||||
end,
|
||||
check_is_contract_constraints(Env, Cs).
|
||||
@@ -2180,8 +2443,8 @@ unfold_types(Env, {type_def, Ann, Name, Args, Def}, Options) ->
|
||||
{type_def, Ann, Name, Args, unfold_types_in_type(Env, Def, Options)};
|
||||
unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) ->
|
||||
{fun_decl, Ann, Name, unfold_types(Env, Type, Options)};
|
||||
unfold_types(Env, {letfun, Ann, Name, Args, Type, Body}, Options) ->
|
||||
{letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Body, Options)};
|
||||
unfold_types(Env, {letfun, Ann, Name, Args, Type, [{guarded, AnnG, [], Body}]}, Options) ->
|
||||
{letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [{guarded, AnnG, [], unfold_types(Env, Body, Options)}]};
|
||||
unfold_types(Env, T, Options) when is_tuple(T) ->
|
||||
list_to_tuple(unfold_types(Env, tuple_to_list(T), Options));
|
||||
unfold_types(Env, [H|T], Options) ->
|
||||
@@ -2309,8 +2572,13 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) ->
|
||||
unify(Env, Then1, Then2, When) andalso
|
||||
unify(Env, Else1, Else2, When);
|
||||
|
||||
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, When) ->
|
||||
type_error({unify_varargs, When});
|
||||
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, When) ->
|
||||
type_error({unify_varargs, When});
|
||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When)
|
||||
when length(Args1) == length(Args2) ->
|
||||
when length(Args1) == length(Args2) ->
|
||||
unify(Env, Named1, Named2, When) andalso
|
||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When)
|
||||
@@ -2319,6 +2587,9 @@ unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When
|
||||
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, When)
|
||||
when length(As) == length(Bs) ->
|
||||
unify(Env, As, Bs, When);
|
||||
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, When) ->
|
||||
unify1(Env, Id1, Id2, {arg_name, Id1, Id2, When}),
|
||||
unify1(Env, Type1, Type2, When);
|
||||
%% The grammar is a bit inconsistent about whether types without
|
||||
%% arguments are represented as applications to an empty list of
|
||||
%% parameters or not. We therefore allow them to unify.
|
||||
@@ -2376,8 +2647,6 @@ occurs_check1(R, [H | T]) ->
|
||||
occurs_check(R, H) orelse occurs_check(R, T);
|
||||
occurs_check1(_, []) -> false.
|
||||
|
||||
fresh_uvar([{origin, system}]) ->
|
||||
error(oh_no_you_dont);
|
||||
fresh_uvar(Attrs) ->
|
||||
{uvar, Attrs, make_ref()}.
|
||||
|
||||
@@ -2426,7 +2695,11 @@ apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
|
||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
||||
add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
||||
add_bytes_constraint({add_bytes, Ann, split, A, B, C}).
|
||||
add_bytes_constraint({add_bytes, Ann, split, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) ->
|
||||
constrain([#is_contract_constraint{ contract_t = Con,
|
||||
context = {bytecode_hash, Ann} }]).
|
||||
|
||||
|
||||
%% Dereferences all uvars and replaces the uninstantiated ones with a
|
||||
%% succession of tvars.
|
||||
@@ -2555,6 +2828,9 @@ mk_error({not_a_contract_type, Type, Cxt}) ->
|
||||
end,
|
||||
{Pos, Cxt1} =
|
||||
case Cxt of
|
||||
{var_args, Ann, Fun} ->
|
||||
{pos(Ann),
|
||||
io_lib:format("when calling variadic function\n~s\n", [pp_expr(" ", Fun)])};
|
||||
{contract_literal, Lit} ->
|
||||
{pos(Lit),
|
||||
io_lib:format("when checking that the contract literal\n~s\n"
|
||||
@@ -2653,7 +2929,7 @@ 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}) ->
|
||||
mk_error({Contract, _Pos, {con, Pos, Name}, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
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);
|
||||
@@ -2669,6 +2945,10 @@ 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)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({stateful_not_allowed_in_guards, Id}) ->
|
||||
Msg = io_lib:format("Cannot reference stateful function ~s (at ~s) in a pattern guard.\n",
|
||||
[pp(Id), pp_loc(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({value_arg_not_allowed, Value, Fun}) ->
|
||||
Msg = io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp_expr("", Value), pp_loc(Value), pp(Fun)]),
|
||||
@@ -2728,8 +3008,8 @@ mk_error({contract_has_no_entrypoints, Con}) ->
|
||||
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||
mk_t_err(pos(Con), Msg);
|
||||
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) ->
|
||||
Msg = "Only the main contract can contain defined functions or entrypoints.\n",
|
||||
mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
|
||||
Msg = "Contract interfaces cannot contain defined functions or entrypoints.\n",
|
||||
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({unbound_type, Type}) ->
|
||||
@@ -2798,6 +3078,40 @@ mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
|
||||
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({ambiguous_main_contract, Ann}) ->
|
||||
Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({main_contract_undefined, Ann}) ->
|
||||
Msg = "No contract defined.\n",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({multiple_main_contracts, Ann}) ->
|
||||
Msg = "Only one main contract can be defined.\n",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unify_varargs, When}) ->
|
||||
Msg = "Cannot unify variable argument list.\n",
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({clone_no_contract, Ann}) ->
|
||||
Msg = "Chain.clone requires `ref` named argument of contract type.\n",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({contract_lacks_definition, Type, When}) ->
|
||||
Msg = io_lib:format(
|
||||
"~s is not implemented.\n",
|
||||
[pp_type(Type)]
|
||||
),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({ambiguous_name, QIds = [{qid, Ann, _} | _]}) ->
|
||||
Names = lists:map(fun(QId) -> io_lib:format("~s at ~s\n", [pp(QId), pp_loc(QId)]) end, QIds),
|
||||
Msg = "Ambiguous name: " ++ lists:concat(Names),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace, Ann, Namespace}) ->
|
||||
Msg = io_lib:format("Cannot use undefined namespace ~s", [Namespace]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace_parts, Ann, Namespace, Parts}) ->
|
||||
PartsStr = lists:concat(lists:join(", ", Parts)),
|
||||
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
@@ -2833,6 +3147,12 @@ pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||
InferredType = instantiate(InferredType0),
|
||||
{pos(Fld),
|
||||
case Fld of
|
||||
{var_args, _Ann, _Fun} ->
|
||||
io_lib:format("when checking contract construction of type\n~s (at ~s)\nagainst the expected type\n~s\n",
|
||||
[pp_type(" ", FieldType),
|
||||
pp_loc(Fld),
|
||||
pp_type(" ", InferredType)
|
||||
]);
|
||||
{field, _Ann, LV, Id, E} ->
|
||||
io_lib:format("when checking the assignment of the field\n~s (at ~s)\nto the old value ~s and the new value\n~s\n",
|
||||
[pp_typed(" ", {lvalue, [], LV}, FieldType),
|
||||
@@ -2855,6 +3175,13 @@ pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
|
||||
InferredType = instantiate(InferredType0),
|
||||
{Pos, WhyRec} = pp_why_record(Fld),
|
||||
case Fld of
|
||||
{var_args, _Ann, _Fun} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that contract construction of type\n~s\n~s\n"
|
||||
"matches the expected type\n~s\n",
|
||||
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)]
|
||||
)
|
||||
};
|
||||
{field, _Ann, _LV, _Id, _E} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||
@@ -2908,17 +3235,42 @@ pp_when({check_named_arg_constraint, C}) ->
|
||||
Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s",
|
||||
[pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]),
|
||||
{pos(Arg), Err};
|
||||
pp_when({checking_init_args, Ann, Con0, ArgTypes0}) ->
|
||||
Con = instantiate(Con0),
|
||||
ArgTypes = instantiate(ArgTypes0),
|
||||
{pos(Ann),
|
||||
io_lib:format("when checking arguments of ~s's init entrypoint to match\n(~s)",
|
||||
[pp_type(Con), string:join([pp_type(A) || A <- ArgTypes], ", ")])
|
||||
};
|
||||
pp_when({return_contract, App, Con0}) ->
|
||||
Con = instantiate(Con0),
|
||||
{pos(App)
|
||||
, io_lib:format("when checking that expression returns contract of type\n~s", [pp_type(" ", Con)])
|
||||
};
|
||||
pp_when({arg_name, Id1, Id2, When}) ->
|
||||
{Pos, Ctx} = pp_when(When),
|
||||
{Pos
|
||||
, io_lib:format("when unifying names of named arguments: ~s and ~s\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx])
|
||||
};
|
||||
pp_when({var_args, Ann, Fun}) ->
|
||||
{pos(Ann)
|
||||
, io_lib:format("when resolving arguments of variadic function\n~s\n", [pp_expr(" ", Fun)])
|
||||
};
|
||||
pp_when(unknown) -> {pos(0,0), ""}.
|
||||
|
||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||
pp_why_record(Fld = {field, _Ann, LV, _Id, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
||||
pp_why_record({var_args, Ann, Fun}) ->
|
||||
{pos(Ann),
|
||||
io_lib:format("arising from resolution of variadic function ~s (at ~s)",
|
||||
[pp_expr(Fun), pp_loc(Fun)])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||
[pp_expr("", {lvalue, [], LV}), pp_loc(Fld)])};
|
||||
[pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field ~s (at ~s)",
|
||||
[pp_expr({lvalue, [], LV}), pp_loc(Fld)])};
|
||||
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
||||
{pos(Rec),
|
||||
io_lib:format("arising from the projection of the field ~s (at ~s)",
|
||||
@@ -2938,9 +3290,13 @@ pp_typed(Label, {typed, _, Expr, _}, Type) ->
|
||||
pp_typed(Label, Expr, Type) ->
|
||||
pp_expr(Label, {typed, [], Expr, Type}).
|
||||
|
||||
pp_expr(Expr) ->
|
||||
pp_expr("", Expr).
|
||||
pp_expr(Label, Expr) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated]))).
|
||||
|
||||
pp_type(Type) ->
|
||||
pp_type("", Type).
|
||||
pp_type(Label, Type) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated]))).
|
||||
|
||||
|
||||
+186
-72
@@ -12,6 +12,8 @@
|
||||
-export([ast_to_fcode/2, format_fexpr/1]).
|
||||
-export_type([fcode/0, fexpr/0, fun_def/0]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
%% -- Type definitions -------------------------------------------------------
|
||||
|
||||
-type option() :: term().
|
||||
@@ -53,6 +55,7 @@
|
||||
| {oracle_pubkey, binary()}
|
||||
| {oracle_query_id, binary()}
|
||||
| {bool, false | true}
|
||||
| {contract_code, string()} %% for CREATE, by name
|
||||
| {typerep, ftype()}.
|
||||
|
||||
-type fexpr() :: {lit, flit()}
|
||||
@@ -93,7 +96,8 @@
|
||||
| nil
|
||||
| {'::', var_name(), var_name()}
|
||||
| {con, arities(), tag(), [var_name()]}
|
||||
| {tuple, [var_name()]}.
|
||||
| {tuple, [var_name()]}
|
||||
| {assign, var_name(), var_name()}.
|
||||
|
||||
-type ftype() :: integer
|
||||
| boolean
|
||||
@@ -136,24 +140,27 @@
|
||||
-type type_env() :: #{ sophia_name() => type_def() }.
|
||||
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
|
||||
-type con_env() :: #{ sophia_name() => con_tag() }.
|
||||
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }.
|
||||
-type child_con_env() :: #{sophia_name() => fcode()}.
|
||||
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }.
|
||||
|
||||
-type context() :: {main_contract, string()}
|
||||
-type context() :: {contract_def, string()}
|
||||
| {namespace, string()}
|
||||
| {abstract_contract, string()}.
|
||||
|
||||
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
|
||||
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() } }.
|
||||
-type env() :: #{ type_env := type_env(),
|
||||
fun_env := fun_env(),
|
||||
con_env := con_env(),
|
||||
child_con_env := child_con_env(),
|
||||
event_type => aeso_syntax:typedef(),
|
||||
builtins := builtins(),
|
||||
options := [option()],
|
||||
state_layout => state_layout(),
|
||||
context => context(),
|
||||
vars => [var_name()],
|
||||
functions := #{ fun_name() => fun_def() }
|
||||
}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
@@ -161,17 +168,26 @@
|
||||
|
||||
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
|
||||
%% and produces Fate intermediate code.
|
||||
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode().
|
||||
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
|
||||
ast_to_fcode(Code, Options) ->
|
||||
Verbose = lists:member(pp_fcode, Options),
|
||||
init_fresh_names(),
|
||||
FCode1 = to_fcode(init_env(Options), Code),
|
||||
{Env1, FCode1} = to_fcode(init_env(Options), Code),
|
||||
FCode2 = optimize(FCode1, Options),
|
||||
Env2 = Env1#{ child_con_env :=
|
||||
maps:map(
|
||||
fun (_, FC) -> optimize(FC, Options) end,
|
||||
maps:get(child_con_env, Env1)
|
||||
)},
|
||||
clear_fresh_names(),
|
||||
{Env2, FCode2}.
|
||||
|
||||
optimize(FCode1, Options) ->
|
||||
Verbose = lists:member(pp_fcode, Options),
|
||||
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
|
||||
FCode2 = optimize_fcode(FCode1),
|
||||
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
|
||||
FCode3 = lambda_lift(FCode2),
|
||||
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
|
||||
clear_fresh_names(),
|
||||
FCode3.
|
||||
|
||||
%% -- Environment ------------------------------------------------------------
|
||||
@@ -182,6 +198,7 @@ init_env(Options) ->
|
||||
#{ type_env => init_type_env(),
|
||||
fun_env => #{},
|
||||
builtins => builtins(),
|
||||
child_con_env => #{},
|
||||
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
|
||||
["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
|
||||
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
|
||||
@@ -217,7 +234,8 @@ init_env(Options) ->
|
||||
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
||||
},
|
||||
options => Options,
|
||||
functions => #{} }.
|
||||
functions => #{}
|
||||
}.
|
||||
|
||||
-spec builtins() -> builtins().
|
||||
builtins() ->
|
||||
@@ -227,9 +245,9 @@ builtins() ->
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
||||
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
|
||||
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
|
||||
{"gas_limit", none}]},
|
||||
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
|
||||
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
|
||||
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
|
||||
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"fee", none},
|
||||
{"gas_left", 0}]},
|
||||
{["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
|
||||
{"respond", 4}, {"extend", 3}, {"get_answer", 2},
|
||||
@@ -269,7 +287,7 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}).
|
||||
-spec init_type_env() -> type_env().
|
||||
init_type_env() ->
|
||||
BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string],
|
||||
[hash], [hash], [address, hash], [address],
|
||||
[{bytes, 32}], [{bytes, 32}], [address, {bytes, 32}], [address],
|
||||
[address, integer], [address, integer], [address],
|
||||
[address], [address], [address], [address], [address],
|
||||
[integer], [address, integer], []]},
|
||||
@@ -307,31 +325,45 @@ get_option(Opt, Env, Default) ->
|
||||
|
||||
%% -- Compilation ------------------------------------------------------------
|
||||
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
|
||||
to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
|
||||
#{ builtins := Builtins } = Env,
|
||||
MainEnv = Env#{ context => {main_contract, Main},
|
||||
builtins => Builtins#{[Main, "state"] => {get_state, none},
|
||||
[Main, "put"] => {set_state, 1},
|
||||
[Main, "Chain", "event"] => {chain_event, 1}} },
|
||||
#{ functions := Funs } = Env1 =
|
||||
decls_to_fcode(MainEnv, Decls),
|
||||
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
|
||||
EventType = lookup_type(Env1, [Main, "event"], [], none),
|
||||
StateLayout = state_layout(Env1),
|
||||
Payable = proplists:get_value(payable, Attrs, false),
|
||||
#{ contract_name => Main,
|
||||
state_type => StateType,
|
||||
state_layout => StateLayout,
|
||||
event_type => EventType,
|
||||
payable => Payable,
|
||||
functions => add_init_function(Env1, MainCon, StateType,
|
||||
add_event_function(Env1, EventType, Funs)) };
|
||||
to_fcode(_Env, [NotContract]) ->
|
||||
fcode_error({last_declaration_must_be_contract, NotContract});
|
||||
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
|
||||
to_fcode(Env1, Code);
|
||||
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
|
||||
to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
#{ builtins := Builtins } = Env,
|
||||
ConEnv = maps:remove(state_layout,
|
||||
Env#{ context => {contract_def, Name},
|
||||
builtins => Builtins#{[Name, "state"] => {get_state, none},
|
||||
[Name, "put"] => {set_state, 1},
|
||||
[Name, "Chain", "event"] => {chain_event, 1}} }),
|
||||
#{ functions := PrevFuns } = ConEnv,
|
||||
#{ functions := Funs } = Env1 =
|
||||
decls_to_fcode(ConEnv, Decls),
|
||||
StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}),
|
||||
EventType = lookup_type(Env1, [Name, "event"], [], none),
|
||||
StateLayout = state_layout(Env1),
|
||||
Payable = proplists:get_value(payable, Attrs, false),
|
||||
ConFcode = #{ contract_name => Name,
|
||||
state_type => StateType,
|
||||
state_layout => StateLayout,
|
||||
event_type => EventType,
|
||||
payable => Payable,
|
||||
functions => add_init_function(
|
||||
Env1, Con, StateType,
|
||||
add_event_function(Env1, EventType, Funs)) },
|
||||
case Contract of
|
||||
contract_main -> [] = Rest, {Env1, ConFcode};
|
||||
contract_child ->
|
||||
Env2 = add_child_con(Env1, Name, ConFcode),
|
||||
Env3 = Env2#{ functions := PrevFuns },
|
||||
to_fcode(Env3, Rest)
|
||||
end;
|
||||
true ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
|
||||
to_fcode(Env1, Rest)
|
||||
end;
|
||||
to_fcode(_Env, [NotMain = {NotMainHead, _ ,_ , _}]) when NotMainHead =/= contract_def ->
|
||||
fcode_error({last_declaration_must_be_contract_def, NotMain});
|
||||
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||
to_fcode(Env1, Code).
|
||||
@@ -341,13 +373,11 @@ decls_to_fcode(Env, Decls) ->
|
||||
%% First compute mapping from Sophia names to fun_names and add it to the
|
||||
%% environment.
|
||||
Env1 = add_fun_env(Env, Decls),
|
||||
lists:foldl(fun(D, E) ->
|
||||
R = decl_to_fcode(E, D),
|
||||
R
|
||||
lists:foldl(fun(D, E) -> decl_to_fcode(E, D)
|
||||
end, Env1, Decls).
|
||||
|
||||
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
|
||||
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
|
||||
decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
|
||||
case is_no_code(Env) of
|
||||
false -> fcode_error({missing_definition, Id});
|
||||
true -> Env
|
||||
@@ -355,7 +385,7 @@ decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
|
||||
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
|
||||
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
|
||||
typedef_to_fcode(Env, Name, Args, Def);
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
|
||||
Attrs = get_attributes(Ann),
|
||||
FName = lookup_fun(Env, qname(Env, Name)),
|
||||
FArgs = args_to_fcode(Env, Args),
|
||||
@@ -410,7 +440,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
||||
Env3 = compute_state_layout(Env2, Name, FDef),
|
||||
bind_type(Env3, Q, FDef).
|
||||
|
||||
compute_state_layout(Env = #{ context := {main_contract, _} }, "state", Type) ->
|
||||
compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) ->
|
||||
NoLayout = get_option(no_flatten_state, Env),
|
||||
Layout =
|
||||
case Type([]) of
|
||||
@@ -436,7 +466,7 @@ compute_state_layout(R, [H | T]) ->
|
||||
compute_state_layout(R, _) ->
|
||||
{R + 1, {reg, R}}.
|
||||
|
||||
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
|
||||
check_state_and_event_types(#{ context := {contract_def, _} }, Id, [_ | _]) ->
|
||||
case Id of
|
||||
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
||||
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
||||
@@ -461,8 +491,12 @@ type_to_fcode(Env, Sub, {record_t, Fields}) ->
|
||||
type_to_fcode(Env, Sub, {tuple_t, [], lists:map(FieldType, Fields)});
|
||||
type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
||||
{bytes, N};
|
||||
type_to_fcode(_Env, _Sub, {tvar, Ann, "void"}) ->
|
||||
fcode_error({found_void, Ann});
|
||||
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
|
||||
maps:get(X, Sub, {tvar, X});
|
||||
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
|
||||
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
|
||||
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
|
||||
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
|
||||
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
|
||||
@@ -628,8 +662,8 @@ expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {ty
|
||||
Arg = fresh_name(),
|
||||
Env1 = bind_var(Env, Arg),
|
||||
Bind = {lam, [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType},
|
||||
[{'case', As, Pat, {list_comp, As, Yield, Rest}},
|
||||
{'case', As, {id, As, "_"}, {list, As, []}}]})},
|
||||
[{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]},
|
||||
{'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})},
|
||||
{def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]),
|
||||
{def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]};
|
||||
expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) ->
|
||||
@@ -649,9 +683,9 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) ->
|
||||
expr_to_fcode(Env, Else));
|
||||
|
||||
%% Switch
|
||||
expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
|
||||
expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
|
||||
Switch = fun(X) ->
|
||||
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}
|
||||
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)}
|
||||
end,
|
||||
case E of
|
||||
{id, _, X} -> Switch(X);
|
||||
@@ -678,14 +712,31 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
||||
end;
|
||||
|
||||
%% Function calls
|
||||
expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
|
||||
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, _}}, Args}) ->
|
||||
Args1 = get_named_args(NamedArgsT, Args),
|
||||
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
|
||||
case expr_to_fcode(Env, Fun) of
|
||||
{builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs);
|
||||
{builtin_u, chain_clone, _Ar} ->
|
||||
case ArgsT of
|
||||
var_args -> fcode_error({var_args_not_set, FunE});
|
||||
_ ->
|
||||
%% Here we little cheat on the typechecker, but this inconsistency
|
||||
%% is to be solved in `aeso_fcode_to_fate:type_to_scode/1`
|
||||
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
|
||||
builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs])
|
||||
end;
|
||||
{builtin_u, chain_create, _Ar} ->
|
||||
case {ArgsT, Type} of
|
||||
{var_args, _} -> fcode_error({var_args_not_set, FunE});
|
||||
{_, {con, _, Contract}} ->
|
||||
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
|
||||
builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]);
|
||||
{_, _} -> fcode_error({not_a_contract_type, Type})
|
||||
end;
|
||||
{builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs);
|
||||
{def_u, F, _Ar} -> {def, F, FArgs};
|
||||
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
|
||||
{remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs};
|
||||
FFun ->
|
||||
%% FFun is a closure, with first component the function name and
|
||||
%% second component the environment
|
||||
@@ -747,6 +798,13 @@ make_if(Cond, Then, Else) ->
|
||||
X = fresh_name(),
|
||||
{'let', X, Cond, make_if({var, X}, Then, Else)}.
|
||||
|
||||
make_if_no_else({var, X}, Then) ->
|
||||
{switch, {split, boolean, X,
|
||||
[{'case', {bool, true}, {nosplit, Then}}]}};
|
||||
make_if_no_else(Cond, Then) ->
|
||||
X = fresh_name(),
|
||||
{'let', X, Cond, make_if_no_else({var, X}, Then)}.
|
||||
|
||||
-spec make_tuple([fexpr()]) -> fexpr().
|
||||
make_tuple([E]) -> E;
|
||||
make_tuple(Es) -> {tuple, Es}.
|
||||
@@ -812,9 +870,9 @@ is_first_order(_) -> true.
|
||||
|
||||
%% -- Pattern matching --
|
||||
|
||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit().
|
||||
alts_to_fcode(Env, Type, X, Alts) ->
|
||||
FAlts = [alt_to_fcode(Env, Alt) || Alt <- Alts],
|
||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
|
||||
alts_to_fcode(Env, Type, X, Alts, Switch) ->
|
||||
FAlts = remove_guards(Env, Alts, Switch),
|
||||
split_tree(Env, [{X, Type}], FAlts).
|
||||
|
||||
%% Intermediate format before case trees (fcase() and fsplit()).
|
||||
@@ -825,7 +883,43 @@ alts_to_fcode(Env, Type, X, Alts) ->
|
||||
| {string, binary()}
|
||||
| nil | {'::', fpat(), fpat()}
|
||||
| {tuple, [fpat()]}
|
||||
| {con, arities(), tag(), [fpat()]}.
|
||||
| {con, arities(), tag(), [fpat()]}
|
||||
| {assign, fpat(), fpat()}.
|
||||
|
||||
remove_guards(_Env, [], _Switch) ->
|
||||
[];
|
||||
remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) ->
|
||||
[alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)];
|
||||
remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], Switch = {switch, Ann, Expr, _}) ->
|
||||
FPat = pat_to_fcode(Env, Pat),
|
||||
FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard),
|
||||
FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body),
|
||||
case Guards of
|
||||
[] ->
|
||||
R = case GuardedBodies of
|
||||
[] -> Rest;
|
||||
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
|
||||
end,
|
||||
case R of
|
||||
[] ->
|
||||
[{'case', [FPat], make_if_no_else(FGuard, FBody)} | remove_guards(Env, Rest, Switch)];
|
||||
_ ->
|
||||
FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}),
|
||||
[{'case', [FPat], make_if(FGuard, FBody, FSwitch)} | remove_guards(Env, Rest, Switch)]
|
||||
end;
|
||||
_ ->
|
||||
R1 = case GuardedBodies of
|
||||
[] -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body}]} | Rest];
|
||||
_ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest]
|
||||
end,
|
||||
R2 = case GuardedBodies of
|
||||
[] -> Rest;
|
||||
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
|
||||
end,
|
||||
FSwitch1 = expr_to_fcode(Env, {switch, Ann, Expr, R1}),
|
||||
FSwitch2 = expr_to_fcode(Env, {switch, Ann, Expr, R2}),
|
||||
[{'case', [FPat], make_if(FGuard, FSwitch1, FSwitch2)} | remove_guards(Env, Rest, Switch)]
|
||||
end.
|
||||
|
||||
%% %% Invariant: the number of variables matches the number of patterns in each falt.
|
||||
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
|
||||
@@ -925,6 +1019,8 @@ split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]};
|
||||
split_pat({con, As, I, Pats}) ->
|
||||
Xs = [fresh_name() || _ <- Pats],
|
||||
{{con, As, I, Xs}, Pats};
|
||||
split_pat({assign, X = {var, _}, P}) ->
|
||||
{{assign, fresh_name(), fresh_name()}, [X, P]};
|
||||
split_pat({tuple, Pats}) ->
|
||||
Xs = [fresh_name() || _ <- Pats],
|
||||
{{tuple, Xs}, Pats}.
|
||||
@@ -935,6 +1031,7 @@ split_vars({int, _}, integer) -> [];
|
||||
split_vars({string, _}, string) -> [];
|
||||
split_vars(nil, {list, _}) -> [];
|
||||
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
|
||||
split_vars({assign, X, P}, T) -> [{X, T}, {P, T}];
|
||||
split_vars({con, _, I, Xs}, {variant, Cons}) ->
|
||||
lists:zip(Xs, lists:nth(I + 1, Cons));
|
||||
split_vars({tuple, Xs}, {tuple, Ts}) ->
|
||||
@@ -950,7 +1047,7 @@ next_split(Pats) ->
|
||||
end.
|
||||
|
||||
-spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt().
|
||||
alt_to_fcode(Env, {'case', _, Pat, Expr}) ->
|
||||
alt_to_fcode(Env, {'case', _, Pat, [{guarded, _, [], Expr}]}) ->
|
||||
FPat = pat_to_fcode(Env, Pat),
|
||||
FExpr = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Expr),
|
||||
{'case', [FPat], FExpr}.
|
||||
@@ -990,6 +1087,8 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) ->
|
||||
end end,
|
||||
make_tuple([pat_to_fcode(Env, FieldPat(Field))
|
||||
|| Field <- Fields]);
|
||||
pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) ->
|
||||
{assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)};
|
||||
|
||||
pat_to_fcode(_Env, Type, Pat) ->
|
||||
error({todo, Pat, ':', Type}).
|
||||
@@ -1026,8 +1125,8 @@ decision_tree_to_fcode({'if', A, Then, Else}) ->
|
||||
stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, Expr} | Stmts]) ->
|
||||
{'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)};
|
||||
stmts_to_fcode(Env, [{letval, Ann, Pat, Expr} | Stmts]) ->
|
||||
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, {block, Ann, Stmts}}]});
|
||||
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, Expr} | Stmts]) ->
|
||||
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [{guarded, Ann, [], {block, Ann, Stmts}}]}]});
|
||||
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Expr}]} | Stmts]) ->
|
||||
LamArgs = [ case Arg of
|
||||
{typed, Ann1, Id, T} -> {arg, Ann1, Id, T};
|
||||
_ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared
|
||||
@@ -1480,6 +1579,7 @@ match_pat(L, {lit, L}) -> [];
|
||||
match_pat(nil, nil) -> [];
|
||||
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
|
||||
match_pat({var, X}, E) -> [{X, E}];
|
||||
match_pat({assign, X, P}, E) -> [{X, E}, {P, E}];
|
||||
match_pat(_, _) -> false.
|
||||
|
||||
constructor_form(Env, Expr) ->
|
||||
@@ -1614,6 +1714,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) ->
|
||||
|
||||
%% -- Names --
|
||||
|
||||
-spec add_child_con(env(), sophia_name(), fcode()) -> env().
|
||||
add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) ->
|
||||
Env#{ child_con_env := CEnv#{Name => Fcode} }.
|
||||
|
||||
-spec add_fun_env(env(), [aeso_syntax:decl()]) -> env().
|
||||
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
|
||||
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
||||
@@ -1628,7 +1732,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
|
||||
make_fun_name(#{ context := Context }, Ann, Name) ->
|
||||
Entrypoint = proplists:get_value(entrypoint, Ann, false),
|
||||
case Context of
|
||||
{main_contract, Main} ->
|
||||
{contract_def, Main} ->
|
||||
if Entrypoint -> {entrypoint, list_to_binary(Name)};
|
||||
true -> {local_fun, [Main, Name]}
|
||||
end;
|
||||
@@ -1640,7 +1744,7 @@ make_fun_name(#{ context := Context }, Ann, Name) ->
|
||||
current_namespace(#{ context := Cxt }) ->
|
||||
case Cxt of
|
||||
{abstract_contract, Con} -> Con;
|
||||
{main_contract, Con} -> Con;
|
||||
{contract_def, Con} -> Con;
|
||||
{namespace, NS} -> NS
|
||||
end.
|
||||
|
||||
@@ -1711,6 +1815,7 @@ pat_vars(nil) -> [];
|
||||
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
|
||||
pat_vars({tuple, Ps}) -> pat_vars(Ps);
|
||||
pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
|
||||
pat_vars({assign, X, P}) -> pat_vars(X) ++ pat_vars(P);
|
||||
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
|
||||
|
||||
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
|
||||
@@ -1931,7 +2036,11 @@ rename_spat(Ren, {con, Ar, C, Xs}) ->
|
||||
{{con, Ar, C, Zs}, Ren1};
|
||||
rename_spat(Ren, {tuple, Xs}) ->
|
||||
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
||||
{{tuple, Zs}, Ren1}.
|
||||
{{tuple, Zs}, Ren1};
|
||||
rename_spat(Ren, {assign, X, P}) ->
|
||||
{X1, Ren1} = rename_binding(Ren, X),
|
||||
{P1, Ren2} = rename_binding(Ren1, P),
|
||||
{{assign, X1, P1}, Ren2}.
|
||||
|
||||
rename_split(Ren, {split, Type, X, Cases}) ->
|
||||
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};
|
||||
@@ -1987,8 +2096,11 @@ internal_error(Error) ->
|
||||
%% -- Pretty printing --------------------------------------------------------
|
||||
|
||||
format_fcode(#{ functions := Funs }) ->
|
||||
prettypr:format(pp_above(
|
||||
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])).
|
||||
prettypr:format(format_funs(Funs)).
|
||||
|
||||
format_funs(Funs) ->
|
||||
pp_above(
|
||||
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]).
|
||||
|
||||
format_fexpr(E) ->
|
||||
prettypr:format(pp_fexpr(E)).
|
||||
@@ -2105,7 +2217,9 @@ pp_fexpr({set_state, R, A}) ->
|
||||
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
|
||||
pp_fexpr({get_state, R}) ->
|
||||
pp_call(pp_text("get_state"), [{lit, {int, R}}]);
|
||||
pp_fexpr({switch, Split}) -> pp_split(Split).
|
||||
pp_fexpr({switch, Split}) -> pp_split(Split);
|
||||
pp_fexpr({contract_code, Contract}) ->
|
||||
pp_beside(pp_text("contract "), pp_text(Contract)).
|
||||
|
||||
pp_call(Fun, Args) ->
|
||||
pp_beside(Fun, pp_fexpr({tuple, Args})).
|
||||
|
||||
@@ -14,12 +14,13 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
|
||||
convert_typed(TypedTree, Options) ->
|
||||
{Payable, Name} =
|
||||
case lists:last(TypedTree) of
|
||||
{contract, Attrs, {con, _, Con}, _} ->
|
||||
{Contr, Attrs, {con, _, Con}, _} when ?IS_CONTRACT_HEAD(Contr) ->
|
||||
{proplists:get_value(payable, Attrs, false), Con};
|
||||
Decl ->
|
||||
gen_error({last_declaration_must_be_contract, Decl})
|
||||
@@ -29,7 +30,8 @@ convert_typed(TypedTree, Options) ->
|
||||
Icode = code(TypedTree, NewIcode, Options),
|
||||
deadcode_elimination(Icode).
|
||||
|
||||
code([{contract, _Attribs, Con, Code}|Rest], Icode, Options) ->
|
||||
code([{Contract, _Attribs, Con, Code}|Rest], Icode, Options)
|
||||
when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
|
||||
code(Rest, NewIcode, Options);
|
||||
code([{namespace, _Ann, Name, Code}|Rest], Icode, Options) ->
|
||||
@@ -94,7 +96,7 @@ contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | R
|
||||
_ -> Icode1
|
||||
end,
|
||||
contract_to_icode(Rest, Icode2);
|
||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, [{guarded, _, [], Body={typed,_,_,T}}]}|Rest], Icode) ->
|
||||
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
||||
[ payable || proplists:get_value(payable, Attrib, false) ] ++
|
||||
[ private || is_private(Attrib, Icode) ],
|
||||
@@ -321,8 +323,8 @@ ast_body({list_comp, _, Yield, []}, Icode) ->
|
||||
ast_body({list_comp, As, Yield, [{comprehension_bind, {typed, _, Pat, ArgType}, BindExpr}|Rest]}, Icode) ->
|
||||
Arg = "%lc",
|
||||
Body = {switch, As, {typed, As, {id, As, Arg}, ArgType},
|
||||
[{'case', As, Pat, {list_comp, As, Yield, Rest}},
|
||||
{'case', As, {id, As, "_"}, {list, As, []}}]},
|
||||
[{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]},
|
||||
{'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]},
|
||||
#funcall
|
||||
{ function = #var_ref{ name = ["ListInternal", "flat_map"] }
|
||||
, args =
|
||||
@@ -347,14 +349,14 @@ ast_body({switch,_,A,Cases}, Icode) ->
|
||||
%% patterns appear in cases.
|
||||
#switch{expr=ast_body(A, Icode),
|
||||
cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)}
|
||||
|| {'case',_,Pat,Body} <- Cases]};
|
||||
|| {'case',_,Pat,[{guarded, _, [], Body}]} <- Cases]};
|
||||
ast_body({block, As, [{letval, _, Pat, E} | Rest]}, Icode) ->
|
||||
E1 = ast_body(E, Icode),
|
||||
Pat1 = ast_body(Pat, Icode),
|
||||
Rest1 = ast_body({block, As, Rest}, Icode),
|
||||
#switch{expr = E1,
|
||||
cases = [{Pat1, Rest1}]};
|
||||
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
|
||||
ast_body({block, As, [{letfun, Ann, F, Args, _Type, [{guarded, _, [], Expr}]} | Rest]}, Icode) ->
|
||||
ToArg = fun({typed, Ann1, Id, T}) -> {arg, Ann1, Id, T} end, %% Pattern matching has been desugared
|
||||
LamArgs = lists:map(ToArg, Args),
|
||||
ast_body({block, As, [{letval, Ann, F, {lam, Ann, LamArgs, Expr}} | Rest]}, Icode);
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
-export([format/1, pos/1]).
|
||||
|
||||
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
|
||||
[C]),
|
||||
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",
|
||||
[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)]),
|
||||
@@ -87,6 +87,12 @@ 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",
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({var_args_not_set, Expr}) ->
|
||||
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
|
||||
, "When compiling " ++ pp_expr(Expr)
|
||||
);
|
||||
format({found_void, Ann}) ->
|
||||
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
|
||||
|
||||
format(Err) ->
|
||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||
|
||||
+12
-8
@@ -28,6 +28,7 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code
|
||||
@@ -137,8 +138,9 @@ from_string1(aevm, ContractString, Options) ->
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||
from_string1(fate, ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||
pp_assembler(fate, FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
@@ -178,8 +180,9 @@ string_to_code(ContractString, Options) ->
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast };
|
||||
fate ->
|
||||
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
|
||||
{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
|
||||
@@ -241,8 +244,9 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
||||
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(OrgFcode, []),
|
||||
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,
|
||||
@@ -468,12 +472,12 @@ error_missing_call_function() ->
|
||||
Msg = "Internal error: missing '__call'-function",
|
||||
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
||||
|
||||
get_call_type([{contract, _, _, Defs}]) ->
|
||||
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case [ {lists:last(QFunName), FunType}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
{typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
|
||||
[{guarded, _, [], {typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}}]} <- Defs ] of
|
||||
[Call] -> {ok, Call};
|
||||
[] -> error_missing_call_function()
|
||||
end;
|
||||
@@ -482,7 +486,7 @@ get_call_type([_ | Contracts]) ->
|
||||
get_call_type(Contracts).
|
||||
|
||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
|
||||
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,
|
||||
|
||||
+96
-42
@@ -9,7 +9,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_fcode_to_fate).
|
||||
|
||||
-export([compile/2, term_to_fate/1]).
|
||||
-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
@@ -45,7 +45,7 @@
|
||||
-define(s(N), {store, N}).
|
||||
-define(void, {var, 9999}).
|
||||
|
||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
|
||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
|
||||
@@ -70,9 +70,11 @@ code_error(Err) ->
|
||||
|
||||
%% @doc Main entry point.
|
||||
compile(FCode, Options) ->
|
||||
compile(#{}, FCode, Options).
|
||||
compile(ChildContracts, FCode, Options) ->
|
||||
#{ contract_name := ContractName,
|
||||
functions := Functions } = FCode,
|
||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
FateCode = to_basic_blocks(SFuns1),
|
||||
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||
@@ -85,19 +87,20 @@ make_function_name(event) -> <<"Chain.event">>;
|
||||
make_function_name({entrypoint, Name}) -> Name;
|
||||
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
|
||||
|
||||
functions_to_scode(ContractName, Functions, Options) ->
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
|
||||
FunNames = maps:keys(Functions),
|
||||
maps:from_list(
|
||||
[ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
||||
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
||||
|| {Name, #{args := Args,
|
||||
body := Body,
|
||||
attrs := Attrs,
|
||||
return := Type}} <- maps:to_list(Functions)]).
|
||||
|
||||
function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) ->
|
||||
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
|
||||
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
||||
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
||||
SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body),
|
||||
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
|
||||
SCode = to_scode(Env, Body),
|
||||
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||
|
||||
-define(tvars, '$tvars').
|
||||
@@ -133,7 +136,9 @@ type_to_scode({tvar, X}) ->
|
||||
put(?tvars, {I + 1, Vars#{ X => I }}),
|
||||
{tvar, I};
|
||||
J -> {tvar, J}
|
||||
end.
|
||||
end;
|
||||
type_to_scode(L) when is_list(L) -> {tuple, types_to_scode(L)}.
|
||||
|
||||
|
||||
types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||
|
||||
@@ -142,11 +147,13 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||
|
||||
%% -- Environment functions --
|
||||
|
||||
init_env(ContractName, FunNames, Name, Args) ->
|
||||
init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
options = Options,
|
||||
tailpos = true }.
|
||||
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
@@ -169,7 +176,7 @@ lookup_var(#env{vars = Vars}, X) ->
|
||||
|
||||
%% -- The compiler --
|
||||
|
||||
lit_to_fate(L) ->
|
||||
lit_to_fate(Env, L) ->
|
||||
case L of
|
||||
{int, N} -> aeb_fate_data:make_integer(N);
|
||||
{string, S} -> aeb_fate_data:make_string(S);
|
||||
@@ -179,63 +186,80 @@ lit_to_fate(L) ->
|
||||
{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,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
FateCode = compile(Env#env.child_contracts, FCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = aeso_compiler:version(),
|
||||
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
|
||||
Code = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
source_hash => crypto:hash(sha256, OriginalSourceCode ++ [0] ++ C),
|
||||
type_info => [],
|
||||
abi_version => aeb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode)
|
||||
},
|
||||
Serialized = aeser_contract_code:serialize(Code),
|
||||
aeb_fate_data:make_contract_bytearray(Serialized);
|
||||
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
|
||||
end.
|
||||
|
||||
term_to_fate(E) -> term_to_fate(#{}, E).
|
||||
term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
|
||||
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
|
||||
|
||||
term_to_fate(_Env, {lit, L}) ->
|
||||
lit_to_fate(L);
|
||||
term_to_fate(GlobEnv, _Env, {lit, L}) ->
|
||||
lit_to_fate(GlobEnv, L);
|
||||
%% negative literals are parsed as 0 - N
|
||||
term_to_fate(_Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
|
||||
aeb_fate_data:make_integer(-N);
|
||||
term_to_fate(_Env, nil) ->
|
||||
term_to_fate(_GlobEnv, _Env, nil) ->
|
||||
aeb_fate_data:make_list([]);
|
||||
term_to_fate(Env, {op, '::', [Hd, Tl]}) ->
|
||||
term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) ->
|
||||
%% The Tl will translate into a list, because FATE lists are just lists
|
||||
[term_to_fate(Env, Hd) | term_to_fate(Env, Tl)];
|
||||
term_to_fate(Env, {tuple, As}) ->
|
||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As]));
|
||||
term_to_fate(Env, {con, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(Env, A) || A <- As ],
|
||||
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
|
||||
term_to_fate(GlobEnv, Env, {tuple, As}) ->
|
||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
|
||||
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
|
||||
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
|
||||
term_to_fate(_Env, {builtin, bits_all, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) ->
|
||||
aeb_fate_data:make_bits(-1);
|
||||
term_to_fate(_Env, {builtin, bits_none, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
|
||||
aeb_fate_data:make_bits(0);
|
||||
term_to_fate(_Env, {op, bits_set, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(B),
|
||||
J = term_to_fate(I),
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N bor (1 bsl J)};
|
||||
term_to_fate(_Env, {op, bits_clear, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(B),
|
||||
J = term_to_fate(I),
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N band bnot (1 bsl J)};
|
||||
term_to_fate(Env, {'let', X, E, Body}) ->
|
||||
Env1 = Env#{ X => term_to_fate(Env, E) },
|
||||
term_to_fate(Env1, Body);
|
||||
term_to_fate(Env, {var, X}) ->
|
||||
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
|
||||
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
|
||||
term_to_fate(GlobEnv, Env1, Body);
|
||||
term_to_fate(_GlobEnv, Env, {var, X}) ->
|
||||
case maps:get(X, Env, undefined) of
|
||||
undefined -> throw(not_a_fate_value);
|
||||
V -> V
|
||||
end;
|
||||
term_to_fate(_Env, {builtin, map_empty, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
|
||||
aeb_fate_data:make_map(#{});
|
||||
term_to_fate(Env, {op, map_set, [M, K, V]}) ->
|
||||
Map = term_to_fate(Env, M),
|
||||
Map#{term_to_fate(Env, K) => term_to_fate(Env, V)};
|
||||
term_to_fate(_Env, _) ->
|
||||
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
|
||||
Map = term_to_fate(GlobEnv, Env, M),
|
||||
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
|
||||
term_to_fate(_GlobEnv, _Env, _) ->
|
||||
throw(not_a_fate_value).
|
||||
|
||||
to_scode(Env, T) ->
|
||||
try term_to_fate(T) of
|
||||
try term_to_fate(Env, T) of
|
||||
V -> [push(?i(V))]
|
||||
catch throw:not_a_fate_value ->
|
||||
to_scode1(Env, T)
|
||||
end.
|
||||
|
||||
to_scode1(_Env, {lit, L}) ->
|
||||
[push(?i(lit_to_fate(L)))];
|
||||
to_scode1(Env, {lit, L}) ->
|
||||
[push(?i(lit_to_fate(Env, L)))];
|
||||
|
||||
to_scode1(_Env, nil) ->
|
||||
[aeb_fate_ops:nil(?a)];
|
||||
@@ -503,6 +527,8 @@ builtin_to_scode(_Env, call_value, []) ->
|
||||
[aeb_fate_ops:call_value(?a)];
|
||||
builtin_to_scode(_Env, call_gas_price, []) ->
|
||||
[aeb_fate_ops:gasprice(?a)];
|
||||
builtin_to_scode(_Env, call_fee, []) ->
|
||||
[aeb_fate_ops:fee(?a)];
|
||||
builtin_to_scode(_Env, call_gas_left, []) ->
|
||||
[aeb_fate_ops:gas(?a)];
|
||||
builtin_to_scode(Env, oracle_register, [_Sign,_Account,_QFee,_TTL,_QType,_RType] = Args) ->
|
||||
@@ -555,7 +581,27 @@ builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
|
||||
builtin_to_scode(_Env, auth_tx_hash, []) ->
|
||||
[aeb_fate_ops:auth_tx_hash(?a)];
|
||||
builtin_to_scode(_Env, auth_tx, []) ->
|
||||
[aeb_fate_ops:auth_tx(?a)].
|
||||
[aeb_fate_ops:auth_tx(?a)];
|
||||
builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args);
|
||||
builtin_to_scode(Env, chain_clone,
|
||||
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
|
||||
case GasCap of
|
||||
{builtin, call_gas_left, _} ->
|
||||
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
|
||||
[Contract, InitArgsT, Value, Prot | InitArgs]
|
||||
);
|
||||
_ ->
|
||||
call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a),
|
||||
[Contract, InitArgsT, Value, GasCap, Prot | InitArgs]
|
||||
)
|
||||
end;
|
||||
|
||||
builtin_to_scode(Env, chain_create,
|
||||
[ Code, InitArgsT, Value | InitArgs]) ->
|
||||
call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a),
|
||||
[Code, InitArgsT, Value | InitArgs]
|
||||
).
|
||||
|
||||
%% -- Operators --
|
||||
|
||||
@@ -881,6 +927,7 @@ attributes(I) ->
|
||||
{'ORIGIN', A} -> Pure(A, []);
|
||||
{'CALLER', A} -> Pure(A, []);
|
||||
{'GASPRICE', A} -> Pure(A, []);
|
||||
{'FEE', A} -> Pure(A, []);
|
||||
{'BLOCKHASH', A, B} -> Pure(A, [B]);
|
||||
{'BENEFICIARY', A} -> Pure(A, []);
|
||||
{'TIMESTAMP', A} -> Pure(A, []);
|
||||
@@ -941,6 +988,10 @@ attributes(I) ->
|
||||
{'STR_TO_LOWER', A, B} -> Pure(A, [B]);
|
||||
{'CHAR_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'CHAR_FROM_INT', A, B} -> Pure(A, [B]);
|
||||
{'CREATE', A, B, C} -> Impure(?a, [A, B, C]);
|
||||
{'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||
{'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
||||
{'BYTECODE_HASH', A, B} -> Impure(A, [B]);
|
||||
{'ABORT', A} -> Impure(pc, A);
|
||||
{'EXIT', A} -> Impure(pc, A);
|
||||
'NOP' -> Pure(none, [])
|
||||
@@ -1694,6 +1745,9 @@ split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
|
||||
element(1, I) == 'CALL_R';
|
||||
element(1, I) == 'CALL_GR';
|
||||
element(1, I) == 'CALL_PGR';
|
||||
element(1, I) == 'CREATE';
|
||||
element(1, I) == 'CLONE';
|
||||
element(1, I) == 'CLONE_G';
|
||||
element(1, I) == 'jumpif' ->
|
||||
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
|
||||
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
false -> fail()
|
||||
end).
|
||||
|
||||
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
|
||||
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
|
||||
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
|
||||
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
||||
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
||||
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})).
|
||||
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
|
||||
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
|
||||
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
|
||||
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
||||
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
||||
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F} )).
|
||||
-define(RULE(A, B, C, D, E, F, G, Do), map(fun({_1, _2, _3, _4, _5, _6, _7}) -> Do end, {A, B, C, D, E, F, G} )).
|
||||
-define(RULE(A, B, C, D, E, F, G, H, Do), map(fun({_1, _2, _3, _4, _5, _6, _7, _8}) -> Do end, {A, B, C, D, E, F, G, H})).
|
||||
|
||||
-import(aeso_parse_lib,
|
||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||
|
||||
+53
-6
@@ -93,10 +93,23 @@ decl() ->
|
||||
?LAZY_P(
|
||||
choice(
|
||||
%% Contract declaration
|
||||
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
|
||||
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
|
||||
[ ?RULE(token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
||||
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||
, using()
|
||||
, pragma()
|
||||
|
||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||
@@ -123,6 +136,21 @@ fundef_or_decl() ->
|
||||
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
|
||||
fundef()]).
|
||||
|
||||
using() ->
|
||||
Alias = {keyword(as), con()},
|
||||
For = ?RULE(keyword(for), bracket_list(id()), {for, _2}),
|
||||
Hiding = ?RULE(keyword(hiding), bracket_list(id()), {hiding, _2}),
|
||||
?RULE(keyword(using), con(), optional(Alias), optional(choice(For, Hiding)), using(get_ann(_1), _2, _3, _4)).
|
||||
|
||||
using(Ann, Con, none, none) ->
|
||||
{using, Ann, Con, none, none};
|
||||
using(Ann, Con, {ok, {_, Alias}}, none) ->
|
||||
{using, Ann, Con, Alias, none};
|
||||
using(Ann, Con, none, {ok, List}) ->
|
||||
{using, Ann, Con, none, List};
|
||||
using(Ann, Con, {ok, {_, Alias}}, {ok, List}) ->
|
||||
{using, Ann, Con, Alias, List}.
|
||||
|
||||
pragma() ->
|
||||
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
|
||||
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
|
||||
@@ -183,10 +211,16 @@ letdef() -> choice(valdef(), fundef()).
|
||||
valdef() ->
|
||||
?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}).
|
||||
|
||||
guarded_fundefs() ->
|
||||
choice(
|
||||
[ ?RULE(keyword('='), body(), [{guarded, _1, [], _2}])
|
||||
, maybe_block(?RULE(keyword('|'), comma_sep(expr()), tok('='), body(), {guarded, _1, _2, _4}))
|
||||
]).
|
||||
|
||||
fundef() ->
|
||||
choice(
|
||||
[ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4})
|
||||
, ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6})
|
||||
[ ?RULE(id(), args(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _3})
|
||||
, ?RULE(id(), args(), tok(':'), type(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, _4, _5})
|
||||
]).
|
||||
|
||||
args() -> paren_list(pattern()).
|
||||
@@ -196,6 +230,9 @@ arg() -> choice(
|
||||
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
|
||||
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
|
||||
|
||||
letpat() ->
|
||||
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
|
||||
|
||||
%% -- Types ------------------------------------------------------------------
|
||||
|
||||
type_vars() -> paren_list(tvar()).
|
||||
@@ -242,7 +279,8 @@ body() ->
|
||||
|
||||
stmt() ->
|
||||
?LAZY_P(choice(
|
||||
[ expr()
|
||||
[ using()
|
||||
, expr()
|
||||
, letdecl()
|
||||
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
|
||||
, {'if', keyword('if'), parens(expr()), body()}
|
||||
@@ -251,7 +289,13 @@ stmt() ->
|
||||
])).
|
||||
|
||||
branch() ->
|
||||
?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}).
|
||||
?RULE(pattern(), guarded_branches(), {'case', get_ann(lists:nth(1, _2)), _1, _2}).
|
||||
|
||||
guarded_branches() ->
|
||||
choice(
|
||||
[ ?RULE(keyword('=>'), body(), [{guarded, _1, [], _2}])
|
||||
, maybe_block(?RULE(tok('|'), comma_sep(expr()), keyword('=>'), body(), {guarded, _3, _2, _4}))
|
||||
]).
|
||||
|
||||
pattern() ->
|
||||
?LET_P(E, expr(), parse_pattern(E)).
|
||||
@@ -299,6 +343,7 @@ exprAtom() ->
|
||||
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
|
||||
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
||||
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
||||
, letpat()
|
||||
])
|
||||
end).
|
||||
|
||||
@@ -559,6 +604,8 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
||||
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
||||
|
||||
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
||||
parse_pattern({letpat, Ann, Id, Pat}) ->
|
||||
{letpat, Ann, Id, parse_pattern(Pat)};
|
||||
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
|
||||
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
||||
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
|
||||
|
||||
+26
-6
@@ -13,6 +13,8 @@
|
||||
|
||||
-export_type([options/0]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
-type doc() :: prettypr:document().
|
||||
-type options() :: [{indent, non_neg_integer()} | show_generated].
|
||||
|
||||
@@ -131,6 +133,10 @@ typed(A, Type) ->
|
||||
false -> follow(hsep(A, text(":")), type(Type))
|
||||
end.
|
||||
|
||||
contract_head(contract_main) -> text("main contract");
|
||||
contract_head(contract_child) -> text("contract");
|
||||
contract_head(contract_interface) -> text("contract interface").
|
||||
|
||||
%% -- Exports ----------------------------------------------------------------
|
||||
|
||||
-spec decls([aeso_syntax:decl()], options()) -> doc().
|
||||
@@ -145,11 +151,11 @@ decl(D, Options) ->
|
||||
with_options(Options, fun() -> decl(D) end).
|
||||
|
||||
-spec decl(aeso_syntax:decl()) -> doc().
|
||||
decl({contract, Attrs, C, Ds}) ->
|
||||
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
Mod = fun({Mod, true}) when Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")])
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||
, hsep(name(C), text("="))), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
@@ -206,8 +212,10 @@ name({typed, _, Name, _}) -> name(Name).
|
||||
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
|
||||
letdecl(Let, {letval, _, P, E}) ->
|
||||
block_expr(0, hsep([text(Let), expr(P), text("=")]), E);
|
||||
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
||||
block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E).
|
||||
letdecl(Let, {letfun, _, F, Args, T, [GuardedBody]}) ->
|
||||
beside(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), guarded_body(GuardedBody, "="));
|
||||
letdecl(Let, {letfun, _, F, Args, T, GuardedBodies}) ->
|
||||
block(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), above(lists:map(fun(GB) -> guarded_body(GB, "=") end, GuardedBodies))).
|
||||
|
||||
-spec args([aeso_syntax:arg()]) -> doc().
|
||||
args(Args) ->
|
||||
@@ -293,6 +301,8 @@ tuple_type(Factors) ->
|
||||
]).
|
||||
|
||||
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
||||
expr_p(P, {letpat, _, Id, Pat}) ->
|
||||
paren(P > 100, follow(hsep(expr(Id), text("=")), expr(Pat)));
|
||||
expr_p(P, {named_arg, _, Name, E}) ->
|
||||
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
|
||||
expr_p(P, {lam, _, Args, E}) ->
|
||||
@@ -474,8 +484,18 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj));
|
||||
elim1(Get={map_get, _, _}) -> elim(Get);
|
||||
elim1(Get={map_get, _, _, _}) -> elim(Get).
|
||||
|
||||
alt({'case', _, Pat, Body}) ->
|
||||
block_expr(0, hsep(expr(Pat), text("=>")), Body).
|
||||
alt({'case', _, Pat, [GuardedBody]}) ->
|
||||
beside(expr(Pat), guarded_body(GuardedBody, "=>"));
|
||||
alt({'case', _, Pat, GuardedBodies}) ->
|
||||
block(expr(Pat), above(lists:map(fun(GB) -> guarded_body(GB, "=>") end, GuardedBodies))).
|
||||
|
||||
guarded_body({guarded, _, Guards, Body}, Then) ->
|
||||
block_expr(0, hsep(guards(Guards), text(Then)), Body).
|
||||
|
||||
guards([]) ->
|
||||
text("");
|
||||
guards(Guards) ->
|
||||
hsep([text(" |"), par(punctuate(text(","), lists:map(fun expr/1, Guards)), 0)]).
|
||||
|
||||
block_expr(_, Header, {block, _, Ss}) ->
|
||||
block(Header, statements(Ss));
|
||||
|
||||
+3
-1
@@ -44,7 +44,9 @@ lexer() ->
|
||||
, {"[^/*]+|[/*]", skip()} ],
|
||||
|
||||
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"],
|
||||
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
|
||||
"interface", "main", "using", "as", "for", "hiding"
|
||||
],
|
||||
KW = string:join(Keywords, "|"),
|
||||
|
||||
Rules =
|
||||
|
||||
+17
-5
@@ -25,7 +25,8 @@
|
||||
-type ann_origin() :: system | user.
|
||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||
|
||||
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
|
||||
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface].
|
||||
|
||||
-type name() :: string().
|
||||
-type id() :: {id, ann(), name()}.
|
||||
@@ -34,13 +35,19 @@
|
||||
-type qcon() :: {qcon, ann(), [name()]}.
|
||||
-type tvar() :: {tvar, ann(), name()}.
|
||||
|
||||
-type decl() :: {contract, ann(), con(), [decl()]}
|
||||
-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()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
|
||||
| {block, ann(), [decl()]}
|
||||
| {using, ann(), con(), namespace_alias(), namespace_parts()}
|
||||
| fundecl()
|
||||
| letfun()
|
||||
| letval(). % Only for error msgs
|
||||
@@ -49,9 +56,12 @@
|
||||
|
||||
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
|
||||
|
||||
-type guard() :: expr().
|
||||
-type guarded_expr() :: {guarded, ann(), [guard()], expr()}.
|
||||
|
||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}.
|
||||
-type letpat() :: {letpat, ann(), id(), pat()}.
|
||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||
|
||||
-type letbind()
|
||||
@@ -116,7 +126,8 @@
|
||||
| {block, ann(), [stmt()]}
|
||||
| {op(), ann()}
|
||||
| id() | qid() | con() | qcon()
|
||||
| constant().
|
||||
| constant()
|
||||
| letpat().
|
||||
|
||||
-type record_or_map() :: record | map | record_or_map_error.
|
||||
|
||||
@@ -137,7 +148,7 @@
|
||||
-type stmt() :: letbind()
|
||||
| expr().
|
||||
|
||||
-type alt() :: {'case', ann(), pat(), expr()}.
|
||||
-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.
|
||||
|
||||
-type lvalue() :: nonempty_list(elim()).
|
||||
|
||||
@@ -150,6 +161,7 @@
|
||||
| {list, ann(), [pat()]}
|
||||
| {typed, ann(), pat(), type()}
|
||||
| {record, ann(), [field(pat())]}
|
||||
| letpat()
|
||||
| constant()
|
||||
| con()
|
||||
| id().
|
||||
|
||||
@@ -41,15 +41,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
Top = Fun(K, X),
|
||||
Rec = case X of
|
||||
%% lists (bound things in head scope over tail)
|
||||
[A | As] -> Scoped(Same(A), Same(As));
|
||||
[A | As] -> Scoped(Same(A), Same(As));
|
||||
%% decl()
|
||||
{contract, _, _, Ds} -> Decl(Ds);
|
||||
{namespace, _, _, Ds} -> Decl(Ds);
|
||||
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
||||
{fun_decl, _, _, T} -> Type(T);
|
||||
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
|
||||
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
|
||||
{contract, _, _, Ds} -> Decl(Ds);
|
||||
{namespace, _, _, Ds} -> Decl(Ds);
|
||||
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
||||
{fun_decl, _, _, T} -> Type(T);
|
||||
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{letfun, _, F, Xs, T, GEs} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ GEs)]);
|
||||
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
|
||||
%% typedef()
|
||||
{alias_t, T} -> Type(T);
|
||||
{record_t, Fs} -> Type(Fs);
|
||||
@@ -88,13 +88,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
{map_get, _, A, B} -> Expr([A, B]);
|
||||
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
||||
{block, _, Ss} -> Expr(Ss);
|
||||
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
|
||||
{guarded, _, Gs, E} -> Expr([E | Gs]);
|
||||
%% field()
|
||||
{field, _, LV, E} -> Expr([LV, E]);
|
||||
{field, _, LV, _, E} -> Expr([LV, E]);
|
||||
%% arg()
|
||||
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
|
||||
%% alt()
|
||||
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{'case', _, P, GEs} -> Scoped(BindExpr(P), Expr(GEs));
|
||||
%% elim()
|
||||
{proj, _, _} -> Zero;
|
||||
{map_get, _, E} -> Expr(E);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-define(IS_CONTRACT_HEAD(X),
|
||||
(X =:= contract_main orelse
|
||||
X =:= contract_interface orelse
|
||||
X =:= contract_child
|
||||
)
|
||||
).
|
||||
@@ -120,10 +120,98 @@ from_fate({constr_t, _, Con, Types}, Args)
|
||||
when length(Types) == length(Args) ->
|
||||
{app, [], Con, [ from_fate(Type, Arg)
|
||||
|| {Type, Arg} <- lists:zip(Types, Args) ]};
|
||||
from_fate({qid, _, QType}, Val) ->
|
||||
from_fate_builtin(QType, Val);
|
||||
from_fate(_Type, _Data) ->
|
||||
throw(cannot_translate_to_sophia).
|
||||
|
||||
|
||||
from_fate_builtin(QType, Val) ->
|
||||
Con = fun([Name | _] = Names) when is_list(Name) -> {qcon, [], Names};
|
||||
(Name) -> {con, [], Name} end,
|
||||
App = fun(Name, []) -> Con(Name);
|
||||
(Name, Value) -> {app, [], Con(Name), Value} end,
|
||||
Chk = fun(Type, Value) -> from_fate(Type, Value) end,
|
||||
Int = {id, [], "int"},
|
||||
Str = {id, [], "string"},
|
||||
Adr = {id, [], "address"},
|
||||
Hsh = {bytes_t, [], 32},
|
||||
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],
|
||||
|
||||
case {QType, Val} of
|
||||
{["Chain", "ttl"], {variant, [1, 1], 0, {X}}} -> App("RelativeTTL", [Chk(Int, X)]);
|
||||
{["Chain", "ttl"], {variant, [1, 1], 1, {X}}} -> App("FixedTTL", [Chk(Int, X)]);
|
||||
|
||||
{["AENS", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} ->
|
||||
App(["AENS","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL),
|
||||
Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]);
|
||||
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 0, {Addr}}} ->
|
||||
App(["AENS","AccountPt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 1, {Addr}}} ->
|
||||
App(["AENS","OraclePt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 2, {Addr}}} ->
|
||||
App(["AENS","ContractPt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 3, {Addr}}} ->
|
||||
App(["AENS","ChannelPt"], [Chk(Adr, Addr)]);
|
||||
|
||||
{["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} ->
|
||||
App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]);
|
||||
|
||||
{["Chain", "paying_for_tx"], {variant, [2], 0, {Addr, X}}} ->
|
||||
App(["Chain","PayingForTx"], [Chk(Adr, Addr), Chk(Int, X)]);
|
||||
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 0, {Addr, Fee, Payload}}} ->
|
||||
App(["Chain","SpendTx"], [Chk(Adr, Addr), Chk(Int, Fee), Chk(Str, Payload)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 1, {}}} ->
|
||||
App(["Chain","OracleRegisterTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 2, {}}} ->
|
||||
App(["Chain","OracleQueryTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 3, {}}} ->
|
||||
App(["Chain","OracleResponseTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 4, {}}} ->
|
||||
App(["Chain","OracleExtendTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 5, {}}} ->
|
||||
App(["Chain","NamePreclaimTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 6, {Name}}} ->
|
||||
App(["Chain","NameClaimTx"], [Chk(Str, Name)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 7, {NameHash}}} ->
|
||||
App(["Chain","NameUpdateTx"], [Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 8, {NameHash}}} ->
|
||||
App(["Chain","NameRevokeTx"], [Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 9, {NewOwner, NameHash}}} ->
|
||||
App(["Chain","NameTransferTx"], [Chk(Adr, NewOwner), Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 10, {Addr}}} ->
|
||||
App(["Chain","ChannelCreateTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 11, {Addr, Amount}}} ->
|
||||
App(["Chain","ChannelDepositTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 12, {Addr, Amount}}} ->
|
||||
App(["Chain","ChannelWithdrawTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 13, {Addr}}} ->
|
||||
App(["Chain","ChannelForceProgressTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 14, {Addr}}} ->
|
||||
App(["Chain","ChannelCloseMutualTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 15, {Addr}}} ->
|
||||
App(["Chain","ChannelCloseSoloTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 16, {Addr}}} ->
|
||||
App(["Chain","ChannelSlashTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 17, {Addr}}} ->
|
||||
App(["Chain","ChannelSettleTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 18, {Addr}}} ->
|
||||
App(["Chain","ChannelSnapshotSoloTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 19, {Amount}}} ->
|
||||
App(["Chain","ContractCreateTx"], [Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 20, {Addr, Amount}}} ->
|
||||
App(["Chain","ContractCallTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} ->
|
||||
App(["Chain","GAAttachTx"], []);
|
||||
|
||||
_ ->
|
||||
throw(cannot_translate_to_sophia)
|
||||
end.
|
||||
|
||||
make_bits(N) ->
|
||||
Id = fun(F) -> {qid, [], ["Bits", F]} end,
|
||||
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Contract Language for aeternity"},
|
||||
{vsn, "5.0.0"},
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "6.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -190,7 +190,7 @@ parameterized_contract(ExtraCode, FunName, Types) ->
|
||||
lists:flatten(
|
||||
["contract Remote =\n"
|
||||
" entrypoint bla : () => unit\n\n"
|
||||
"contract Dummy =\n",
|
||||
"main contract Dummy =\n",
|
||||
ExtraCode, "\n",
|
||||
" type an_alias('a) = string * 'a\n"
|
||||
" record r = {x : an_alias(int), y : variant}\n"
|
||||
|
||||
+54
-45
@@ -22,7 +22,8 @@ test_cases(1) ->
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>,
|
||||
type_defs => [],
|
||||
payable => true,
|
||||
payable => true,
|
||||
kind => contract_main,
|
||||
functions =>
|
||||
[#{name => <<"a">>,
|
||||
arguments =>
|
||||
@@ -31,56 +32,57 @@ test_cases(1) ->
|
||||
returns => <<"int">>,
|
||||
stateful => true,
|
||||
payable => true}]}},
|
||||
DecACI = <<"payable contract C =\n"
|
||||
" payable entrypoint a : (int) => int\n">>,
|
||||
DecACI = <<"payable main contract C =\n"
|
||||
" payable stateful entrypoint a : (int) => int\n">>,
|
||||
{Contract,MapACI,DecACI};
|
||||
|
||||
test_cases(2) ->
|
||||
Contract = <<"contract C =\n"
|
||||
Contract = <<"main contract C =\n"
|
||||
" type allan = int\n"
|
||||
" entrypoint a(i : allan) = i+1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>, payable => false,
|
||||
type_defs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type => <<"C.allan">>}],
|
||||
name => <<"a">>,
|
||||
returns => <<"int">>,
|
||||
stateful => false,
|
||||
payable => false}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
#{name => <<"C">>, payable => false,
|
||||
kind => contract_main,
|
||||
type_defs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type => <<"C.allan">>}],
|
||||
name => <<"a">>,
|
||||
returns => <<"int">>,
|
||||
stateful => false,
|
||||
payable => false}]}},
|
||||
DecACI = <<"main contract C =\n"
|
||||
" type allan = int\n"
|
||||
" entrypoint a : (C.allan) => int\n">>,
|
||||
{Contract,MapACI,DecACI};
|
||||
test_cases(3) ->
|
||||
Contract = <<"contract C =\n"
|
||||
Contract = <<"main contract C =\n"
|
||||
" type state = unit\n"
|
||||
" datatype event = SingleEventDefined\n"
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
" entrypoint a(i : bert(string)) = 1\n">>,
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
" entrypoint a(i : bert(string)) = 1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{functions =>
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type =>
|
||||
#{<<"C.bert">> => [<<"string">>]}}],
|
||||
name => <<"a">>,returns => <<"int">>,
|
||||
stateful => false, payable => false}],
|
||||
name => <<"C">>, payable => false,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
[#{arguments =>
|
||||
[#{name => <<"i">>,
|
||||
type =>
|
||||
#{<<"C.bert">> => [<<"string">>]}}],
|
||||
name => <<"a">>,returns => <<"int">>,
|
||||
stateful => false, payable => false}],
|
||||
name => <<"C">>, payable => false, kind => contract_main,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
type_defs =>
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||
vars => [#{name => <<"'a">>}]}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||
vars => [#{name => <<"'a">>}]}]}},
|
||||
DecACI = <<"main contract C =\n"
|
||||
" type state = unit\n"
|
||||
" datatype event = SingleEventDefined\n"
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
@@ -101,17 +103,24 @@ aci_test_contract(Name) ->
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++ [{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),
|
||||
JSON = case aeso_aci:contract_interface(json, String, Opts) of
|
||||
{ok, J} -> J;
|
||||
{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
|
||||
{ok, #{aci := JSON1}} ->
|
||||
?assertEqual(JSON, JSON1),
|
||||
io:format("JSON:\n~p\n", [JSON]),
|
||||
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
|
||||
|
||||
io:format("JSON:\n~p\n", [JSON]),
|
||||
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
|
||||
io:format("STUB:\n~s\n", [ContractStub]),
|
||||
check_stub(ContractStub, [{src_file, Name}]),
|
||||
|
||||
io:format("STUB:\n~s\n", [ContractStub]),
|
||||
check_stub(ContractStub, [{src_file, Name}]),
|
||||
|
||||
ok.
|
||||
ok;
|
||||
{error, ErrorString} when is_binary(ErrorString) -> error(ErrorString);
|
||||
{error, Error} -> aeso_compiler_tests:print_and_throw(Error)
|
||||
end.
|
||||
|
||||
check_stub(Stub, Options) ->
|
||||
try aeso_parser:string(binary_to_list(Stub), Options) of
|
||||
|
||||
@@ -59,8 +59,8 @@ calldata_aci_test_() ->
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
||||
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
strip_ann(AST).
|
||||
|
||||
strip_ann(T) when is_tuple(T) ->
|
||||
@@ -112,8 +112,28 @@ compilable_contracts() ->
|
||||
{"funargs", "traffic_light", ["Green"]},
|
||||
{"funargs", "traffic_light", ["Pantone(12)"]},
|
||||
{"funargs", "tuples", ["()"]},
|
||||
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
|
||||
{"funargs", "due", ["FixedTTL(1020)"]},
|
||||
{"funargs", "singleton_rec", ["{x = 1000}"]},
|
||||
{"funargs", "aens_name", ["AENS.Name(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, RelativeTTL(100), {[\"pt1\"] = AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)})"]},
|
||||
{"funargs", "aens_pointee", ["AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.OraclePt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.ContractPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.ChannelPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "chain_ga_meta_tx", ["Chain.GAMetaTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
|
||||
{"funargs", "chain_paying_for_tx", ["Chain.PayingForTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.SpendTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42,\"foo\")"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.ContractCreateTx(12234)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.ContractCallTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 12234)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleRegisterTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleQueryTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleResponseTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleExtendTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NamePreclaimTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameClaimTx(\"acoolname.chain\")"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameUpdateTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.GAAttachTx"]},
|
||||
{"variant_types", "init", []},
|
||||
{"basic_auth", "init", []},
|
||||
{"address_literals", "init", []},
|
||||
|
||||
+122
-26
@@ -34,9 +34,7 @@ simple_compile_test_() ->
|
||||
#{fate_code := Code} when Backend == fate ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
ErrBin ->
|
||||
io:format("\n~s", [ErrBin]),
|
||||
error(ErrBin)
|
||||
Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
||||
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
||||
@@ -92,6 +90,23 @@ simple_compile_test_() ->
|
||||
end} || Backend <- [aevm, fate] ] ++
|
||||
[].
|
||||
|
||||
%% Check if all modules in the standard library compile
|
||||
stdlib_test_() ->
|
||||
{ok, Files} = file:list_dir(aeso_stdlib:stdlib_include_path()),
|
||||
[ { "Testing " ++ File ++ " from the stdlib",
|
||||
fun() ->
|
||||
String = "include \"" ++ File ++ "\"\nmain contract Test =\n entrypoint f(x) = x",
|
||||
Options = [{src_file, File}, {backend, fate}],
|
||||
case aeso_compiler:from_string(String, Options) of
|
||||
{ok, #{fate_code := Code}} ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
{error, Error} -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || File <- Files,
|
||||
lists:suffix(".aes", File)
|
||||
].
|
||||
|
||||
check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
|
||||
check_errors(Expect, #{}) ->
|
||||
?assertEqual({error, Expect}, ok);
|
||||
@@ -179,17 +194,20 @@ compilable_contracts() ->
|
||||
"lhs_matching",
|
||||
"more_strings",
|
||||
"protected_call",
|
||||
"hermetization_turnoff"
|
||||
"hermetization_turnoff",
|
||||
"multiple_contracts",
|
||||
"clone",
|
||||
"clone_simple",
|
||||
"create",
|
||||
"child_contract_init_bug",
|
||||
"using_namespace",
|
||||
"assign_patterns",
|
||||
"patterns_guards",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
not_compilable_on(fate) -> [];
|
||||
not_compilable_on(aevm) ->
|
||||
[ "stdlib_include", "manual_stdlib_include", "pairing_crypto"
|
||||
, "aens_update", "basic_auth_tx", "more_strings"
|
||||
, "unapplied_builtins", "bytes_to_x", "state_handling", "protected_call"
|
||||
, "hermetization_turnoff"
|
||||
|
||||
].
|
||||
not_compilable_on(aevm) -> compilable_contracts().
|
||||
|
||||
debug_mode_contracts() ->
|
||||
["hermetization_turnoff"].
|
||||
@@ -220,6 +238,7 @@ failing_contracts() ->
|
||||
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
|
||||
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
|
||||
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
|
||||
, ?PARSE_ERROR(assign_pattern_to_pattern, [<<?Pos(3, 22) "Unexpected token '='.">>])
|
||||
|
||||
%% Type errors
|
||||
, ?TYPE_ERROR(name_clash,
|
||||
@@ -635,9 +654,9 @@ failing_contracts() ->
|
||||
<<?Pos(2, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
||||
, ?TYPE_ERROR(multiple_contracts,
|
||||
, ?TYPE_ERROR(interface_with_defs,
|
||||
[<<?Pos(2, 3)
|
||||
"Only the main contract can contain defined functions or entrypoints.\n"
|
||||
"Contract interfaces cannot contain defined functions or entrypoints.\n"
|
||||
"Fix: replace the definition of 'foo' by a type signature.">>])
|
||||
, ?TYPE_ERROR(contract_as_namespace,
|
||||
[<<?Pos(5, 28)
|
||||
@@ -733,6 +752,71 @@ failing_contracts() ->
|
||||
, ?TYPE_ERROR(bad_state,
|
||||
[<<?Pos(4, 16)
|
||||
"Conflicting updates for field 'foo'">>])
|
||||
, ?TYPE_ERROR(factories_type_errors,
|
||||
[<<?Pos(10,18)
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify (gas : int, value : int, protected : bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool, int, bool) => 'b\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
|
||||
<<?Pos(12,37)
|
||||
"Cannot unify int\n and bool\n"
|
||||
"when checking named argument\n gas : int\nagainst inferred type\n bool">>,
|
||||
<<?Pos(13,18),
|
||||
"Kaboom is not implemented.\n"
|
||||
"when resolving arguments of variadic function\n Chain.create">>,
|
||||
<<?Pos(18,18)
|
||||
"Cannot unify (gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool) => 'a\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool, int, bool) =>\n if(protected, option(void), void) (at line 18, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool) => 'a">>,
|
||||
<<?Pos(19,42),
|
||||
"Named argument protected (at line 19, column 42) is not one of the expected named arguments\n - value : int">>,
|
||||
<<?Pos(20,42),
|
||||
"Cannot unify int\n and bool\n"
|
||||
"when checking named argument\n value : int\nagainst inferred type\n bool">>
|
||||
])
|
||||
, ?TYPE_ERROR(ambiguous_main,
|
||||
[<<?Pos(1,1)
|
||||
"Could not deduce the main contract. You can point it out manually with the `main` keyword.">>
|
||||
])
|
||||
, ?TYPE_ERROR(no_main_contract,
|
||||
[<<?Pos(0,0)
|
||||
"No contract defined.">>
|
||||
])
|
||||
, ?TYPE_ERROR(multiple_main_contracts,
|
||||
[<<?Pos(1,6)
|
||||
"Only one main contract can be defined.">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_ambiguous_name,
|
||||
[ <<?Pos(2,3)
|
||||
"Ambiguous name: Xa.f at line 2, column 3\nXb.f at line 5, column 3">>
|
||||
, <<?Pos(13,23)
|
||||
"Unbound variable A.f at line 13, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_wrong_scope,
|
||||
[ <<?Pos(19,5)
|
||||
"Unbound variable f at line 19, column 5">>
|
||||
, <<?Pos(21,23)
|
||||
"Unbound variable f at line 21, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined,
|
||||
[<<?Pos(2,3)
|
||||
"Cannot use undefined namespace MyUndefinedNamespace">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined_parts,
|
||||
[<<?Pos(5,3)
|
||||
"The namespace Nsp does not define the following names: a">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_hidden_parts,
|
||||
[<<?Pos(8,23)
|
||||
"Unbound variable g at line 8, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(stateful_pattern_guard,
|
||||
[<<?Pos(8,12)
|
||||
"Cannot reference stateful function g (at line 8, column 12) in a pattern guard.">>
|
||||
])
|
||||
, ?TYPE_ERROR(non_boolean_pattern_guard,
|
||||
[<<?Pos(4,24)
|
||||
"Cannot unify string\n and bool\nwhen checking the type of the expression at line 4, column 24\n \"y\" : string\nagainst the expected type\n bool">>
|
||||
])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
@@ -746,9 +830,7 @@ failing_contracts() ->
|
||||
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
|
||||
|
||||
failing_code_gen_contracts() ->
|
||||
[ ?SAME(last_declaration_must_be_contract, 1, 1,
|
||||
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
|
||||
, ?SAME(missing_definition, 2, 14,
|
||||
[ ?SAME(missing_definition, 2, 14,
|
||||
"Missing definition of function 'foo'.")
|
||||
, ?AEVM(polymorphic_entrypoint, 2, 17,
|
||||
"The argument\n"
|
||||
@@ -846,6 +928,8 @@ failing_code_gen_contracts() ->
|
||||
"Invalid state type\n"
|
||||
" {f : (int) => int}\n"
|
||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
||||
, ?FATE(child_with_decls, 2, 14,
|
||||
"Missing definition of function 'f'.")
|
||||
].
|
||||
|
||||
validation_test_() ->
|
||||
@@ -883,14 +967,26 @@ validation_fails() ->
|
||||
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||
|
||||
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()]}}]).
|
||||
case compile(fate, Contract1) of
|
||||
ByteCode = #{ fate_code := FCode } ->
|
||||
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()]}}]);
|
||||
Error -> print_and_throw(Error)
|
||||
end.
|
||||
|
||||
print_and_throw(Err) ->
|
||||
case Err of
|
||||
ErrBin when is_binary(ErrBin) ->
|
||||
io:format("\n~s", [ErrBin]),
|
||||
error(ErrBin);
|
||||
Errors ->
|
||||
io:format("Compilation error:\n~s", [string:join([aeso_errors:pp(E) || E <- Errors], "\n\n")]),
|
||||
error(compilation_error)
|
||||
end.
|
||||
|
||||
@@ -12,12 +12,12 @@ simple_contracts_test_() ->
|
||||
fun(_) -> ok end,
|
||||
[{"Parse a contract with an identity function.",
|
||||
fun() ->
|
||||
Text = "contract Identity =\n"
|
||||
Text = "main contract Identity =\n"
|
||||
" function id(x) = x\n",
|
||||
?assertMatch(
|
||||
[{contract, _, {con, _, "Identity"},
|
||||
[{contract_main, _, {con, _, "Identity"},
|
||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||
{id, _, "x"}}]}], parse_string(Text)),
|
||||
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
|
||||
ok
|
||||
end},
|
||||
{"Operator precedence test.",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Identity =
|
||||
function main (x:int) = x
|
||||
function main_fun (x:int) = x
|
||||
|
||||
function __call() = 12
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
contract Remote =
|
||||
entrypoint main : (int) => unit
|
||||
contract interface Remote =
|
||||
entrypoint main_fun : (int) => unit
|
||||
|
||||
contract AddrChain =
|
||||
type o_type = oracle(string, map(string, int))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : () => unit
|
||||
|
||||
contract AddressLiterals =
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
entrypoint f() = 123
|
||||
|
||||
contract D =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,4 @@
|
||||
contract AssignPatternToPattern =
|
||||
entrypoint f() =
|
||||
let x::(t::z = y) = [1, 2, 3]
|
||||
(x + t)::y
|
||||
@@ -0,0 +1,16 @@
|
||||
include "List.aes"
|
||||
|
||||
contract AssignPatterns =
|
||||
|
||||
entrypoint test() = foo([1, 0, 2], (2, Some(3)), Some([4, 5]))
|
||||
|
||||
entrypoint foo(xs : list(int), p : int * option(int), some : option(list(int))) =
|
||||
let x::(t = y::_) = xs
|
||||
let z::_ = t
|
||||
|
||||
let (a, (o = Some(b))) = p
|
||||
|
||||
let Some((f = g::_)) = some
|
||||
g + List.get(1, f)
|
||||
|
||||
x + y + z + a + b
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : () => unit
|
||||
|
||||
contract AddressLiterals =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint id : int => int
|
||||
|
||||
contract ProtectedCall =
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
function square(x) = x ^ 2
|
||||
contract Main =
|
||||
entrypoint main() = square(10)
|
||||
entrypoint main_fun() = square(10)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
contract Identity =
|
||||
record state = {foo: int, bar: string}
|
||||
entrypoint init() = {foo = 0, bar = ""}
|
||||
|
||||
main contract IdentityService =
|
||||
stateful entrypoint createNewIdentity() : Identity =
|
||||
put(())
|
||||
Chain.create()
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
contract interface HigherOrderState =
|
||||
entrypoint init : () => void
|
||||
entrypoint apply : int => int
|
||||
stateful entrypoint inc : int => unit
|
||||
|
||||
contract interface LowerDisorderAnarchy =
|
||||
entrypoint init : (int) => void
|
||||
|
||||
|
||||
main contract C =
|
||||
// both `s` and `l` should be of type `HigherOrderState` in this test
|
||||
stateful entrypoint run_clone(s : HigherOrderState, l : LowerDisorderAnarchy) : HigherOrderState =
|
||||
let s1 = Chain.clone(ref=s)
|
||||
let Some(s2) = Chain.clone(ref=s, protected=true)
|
||||
let None = Chain.clone(ref=s, protected=true, gas=1)
|
||||
let None = Chain.clone(ref=l, protected=true, 123) // since it should be HigherOrderState underneath
|
||||
let s3 = Chain.clone(ref=s1)
|
||||
require(s1.apply(2137) == 2137, "APPLY_S1_0")
|
||||
require(s2.apply(2137) == 2137, "APPLY_S2_0")
|
||||
require(s3.apply(2137) == 2137, "APPLY_S3_0")
|
||||
s1.inc(1)
|
||||
s2.inc(1)
|
||||
s1.inc(1)
|
||||
require(s1.apply(2137) == 2139, "APPLY_S1_2")
|
||||
require(s2.apply(2137) == 2138, "APPLY_S2_1")
|
||||
require(s3.apply(2137) == 2137, "APPLY_S3_0")
|
||||
s1
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
entrypoint init : () => void
|
||||
|
||||
contract C =
|
||||
stateful entrypoint f(i : I) =
|
||||
let Some(c1) = Chain.clone(ref=i, protected = true)
|
||||
2
|
||||
@@ -5,5 +5,5 @@ contract BadAENSresolve =
|
||||
function fail() : t(int) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
entrypoint f : () => unit
|
||||
|
||||
main contract M =
|
||||
entrypoint f() = 123
|
||||
@@ -3,4 +3,4 @@ contract MapAsMapKey =
|
||||
|
||||
function foo(m) : t(int => int) = {[m] = 0}
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract HigherOrderQueryType =
|
||||
stateful function foo(o) : oracle_query(_, string ) =
|
||||
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract HigherOrderResponseType =
|
||||
stateful function foo(o, q : oracle_query(string, _)) =
|
||||
Oracle.respond(o, q, (x) => x + 1)
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
namespace LastDeclarationIsNotAContract =
|
||||
function add(x, y) = x + y
|
||||
@@ -1,3 +1,3 @@
|
||||
contract MissingDefinition =
|
||||
entrypoint foo : int => int
|
||||
entrypoint main() = foo(0)
|
||||
entrypoint main_fun() = foo(0)
|
||||
|
||||
@@ -3,5 +3,5 @@ contract PolymorphicAENSresolve =
|
||||
function fail() : option('a) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ contract MapAsMapKey =
|
||||
|
||||
function foo(m) : t('a) = {[m] = 0}
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract PolymorphicQueryType =
|
||||
stateful function is_oracle(o) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -2,4 +2,4 @@ contract PolymorphicResponseType =
|
||||
function is_oracle(o : oracle(string, 'r)) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main(o : oracle(string, int)) = is_oracle(o)
|
||||
entrypoint main_fun(o : oracle(string, int)) = is_oracle(o)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint foo : int => int
|
||||
|
||||
contract UnappliedContractCall =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
contract UnappliedNamedArgBuiltin =
|
||||
// Allowed in FATE, but not AEVM
|
||||
stateful entrypoint main(s) =
|
||||
stateful entrypoint main_fun(s) =
|
||||
let reg = Oracle.register
|
||||
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint up_to : (int) => list(int)
|
||||
entrypoint sum : (list(int)) => int
|
||||
entrypoint some_string : () => string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Foo =
|
||||
contract interface Foo =
|
||||
entrypoint foo : () => int
|
||||
|
||||
contract Fail =
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
contract IntegerAdder =
|
||||
entrypoint init() = ()
|
||||
entrypoint addIntegers(x, y) = x + y
|
||||
|
||||
contract IntegerAdderHolder =
|
||||
type state = IntegerAdder
|
||||
stateful entrypoint init() = Chain.create() : IntegerAdder
|
||||
entrypoint get() = state
|
||||
|
||||
contract IntegerAdderFactory =
|
||||
entrypoint init() = ()
|
||||
stateful entrypoint new() =
|
||||
let i = Chain.create() : IntegerAdderHolder
|
||||
i.get()
|
||||
|
||||
payable contract ValueAdder =
|
||||
entrypoint init() = ()
|
||||
stateful entrypoint addValue(x) =
|
||||
let integerAdderFactory = Chain.create()
|
||||
let adder = integerAdderFactory.new()
|
||||
adder.addIntegers(x, Contract.balance)
|
||||
|
||||
main contract EnterpriseContract =
|
||||
entrypoint init() = ()
|
||||
stateful payable entrypoint increaseByThree(x) =
|
||||
require(Call.value >= 3, "Price for addition = 3AEtto, insufficient funds")
|
||||
let threeAdder = Chain.create(value = 3)
|
||||
threeAdder.addValue(x)
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
// Testing primitives for accessing the block chain environment
|
||||
contract Interface =
|
||||
contract interface Interface =
|
||||
entrypoint contract_address : () => address
|
||||
entrypoint call_origin : () => address
|
||||
entrypoint call_caller : () => address
|
||||
@@ -44,6 +44,9 @@ contract Environment =
|
||||
// Gas price
|
||||
entrypoint call_gas_price() : int = Call.gas_price
|
||||
|
||||
// Fee
|
||||
entrypoint call_fee() : int = Call.fee
|
||||
|
||||
// -- Information about the chain ---
|
||||
|
||||
// Account balances
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint dummy : () => unit
|
||||
|
||||
contract Events =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// An implementation of the factorial function where each recursive
|
||||
// call is to another contract. Not the cheapest way to compute factorial.
|
||||
contract FactorialServer =
|
||||
contract interface FactorialServer =
|
||||
entrypoint fac : (int) => int
|
||||
|
||||
contract Factorial =
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
contract interface Kaboom =
|
||||
entrypoint init : () => void
|
||||
|
||||
contract Bakoom =
|
||||
type state = int
|
||||
entrypoint init(x, b) = if(b) x else 0
|
||||
|
||||
main contract Test =
|
||||
stateful entrypoint test(k : Kaboom) =
|
||||
let k_bad1 = Chain.clone() : Kaboom
|
||||
let k_bad2 = Chain.clone(ref=k, 123, true)
|
||||
let k_bad3 = Chain.clone(ref=k, gas=true)
|
||||
let k_bad4 = Chain.create() : Kaboom
|
||||
let k_gud1 = Chain.clone(ref=k)
|
||||
let Some(k_gud2) = Chain.clone(ref=k, protected=true)
|
||||
let Some(k_gud3) = Chain.clone(ref=k, value=10, protected=true, gas=123)
|
||||
|
||||
let b_bad1 = Chain.create() : Bakoom
|
||||
let b_bad2 = Chain.create(123, true, protected=true) : Bakoom
|
||||
let b_bad3 = Chain.create(123, true, value=true) : Bakoom
|
||||
let b_gud1 = Chain.create(123, true) : Bakoom
|
||||
let b_gud2 = Chain.create(123, true, value=100) : Bakoom
|
||||
|
||||
b_gud1
|
||||
@@ -50,3 +50,10 @@ contract FunctionArguments =
|
||||
|
||||
entrypoint singleton_rec(r : singleton_r) =
|
||||
r.x
|
||||
|
||||
entrypoint aens_name(n : AENS.name) = true
|
||||
entrypoint aens_pointee(p : AENS.pointee) = true
|
||||
|
||||
entrypoint chain_ga_meta_tx(tx : Chain.ga_meta_tx) = true
|
||||
entrypoint chain_paying_for_tx(tx : Chain.paying_for_tx) = true
|
||||
entrypoint chain_base_tx(tx : Chain.base_tx) = true
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
|
||||
contract Identity =
|
||||
entrypoint main (x:int) = x
|
||||
main contract Identity =
|
||||
entrypoint main_fun (x:int) = x
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
||||
@@ -16,7 +16,7 @@ contract LHSMatching =
|
||||
let null(_ :: _) = false
|
||||
!null(xs)
|
||||
|
||||
entrypoint main() =
|
||||
entrypoint main_fun() =
|
||||
from_some(Some([0]))
|
||||
++ append([length([true]), 2, 3], [4, 5, 6])
|
||||
++ [7 | if (local_match([false]))]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
contract MissingEventType =
|
||||
entrypoint main() =
|
||||
entrypoint main_fun() =
|
||||
Chain.event("MAIN")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
contract ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
contract Child =
|
||||
entrypoint
|
||||
add2 : int => int
|
||||
add2(x) = x + 2
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
||||
main contract Main =
|
||||
entrypoint add4(x, c : Child) = c.add2(x) + 2
|
||||
@@ -0,0 +1,5 @@
|
||||
main contract C =
|
||||
entrypoint f() = 123
|
||||
|
||||
main contract D =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,2 @@
|
||||
contract interface C =
|
||||
entrypoint f : () => unit
|
||||
@@ -0,0 +1,4 @@
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init(x) | "y" = 1
|
||||
@@ -1,4 +1,4 @@
|
||||
contract C1 =
|
||||
contract interface C1 =
|
||||
entrypoint f : int
|
||||
|
||||
contract C =
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
include "List.aes"
|
||||
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init() = f([1, 2, 3, 4])
|
||||
|
||||
function
|
||||
f(x::[])
|
||||
| x > 1, x < 10 = 1
|
||||
| x < 1 = 9
|
||||
f(x::y::[]) = 2
|
||||
f(x::y::z) = switch(z)
|
||||
[] => 4
|
||||
a::[]
|
||||
| a > 10, a < 20 => 5
|
||||
| a > 5 => 8
|
||||
b | List.length(b) > 5 => 6
|
||||
c => 7
|
||||
@@ -1,4 +1,4 @@
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
entrypoint id : int => int
|
||||
|
||||
contract ProtectedCall =
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
|
||||
contract Remote1 =
|
||||
entrypoint main : (int) => int
|
||||
contract interface Remote1 =
|
||||
entrypoint main_fun : (int) => int
|
||||
|
||||
contract Remote2 =
|
||||
contract interface Remote2 =
|
||||
entrypoint call : (Remote1, int) => int
|
||||
|
||||
contract Remote3 =
|
||||
contract interface Remote3 =
|
||||
entrypoint get : () => int
|
||||
entrypoint tick : () => unit
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
stateful entrypoint call(r : Remote1, x : int) : int =
|
||||
r.main(gas = 10000, value = 10, x)
|
||||
r.main_fun(gas = 10000, value = 10, x)
|
||||
|
||||
entrypoint staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
||||
r2.call(r1, x)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract SpendContract =
|
||||
contract interface SpendContract =
|
||||
entrypoint withdraw : (int) => int
|
||||
|
||||
contract SpendTest =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
include "String.aes"
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
record rstate = { i : int, s : string, m : map(int, int) }
|
||||
|
||||
entrypoint look_at : (rstate) => unit
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
stateful entrypoint remote_spend : (address, int) => unit
|
||||
entrypoint remote_pure : int => int
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init() = f(4)
|
||||
|
||||
function
|
||||
f(x) | x > 0 = 1
|
||||
f(x) | g(x) = 2
|
||||
|
||||
stateful function g(x) = x < 0
|
||||
+9
-99
@@ -1,102 +1,12 @@
|
||||
// This is a custom test file if you need to run a compiler without
|
||||
// changing aeso_compiler_tests.erl
|
||||
|
||||
contract Identity =
|
||||
// type xy = {x:int, y:int}
|
||||
// type xz = {x:int, z:int}
|
||||
// type yz = {y:int, z:int}
|
||||
record point = {x:int,y:int}
|
||||
record cp('a) = {color: string, p:'a}
|
||||
//type intpoint = point(int)
|
||||
// //if (x==42) 1 else (x*x)
|
||||
// }
|
||||
//let baz() = {age:3, name:(4:int)}
|
||||
//let foo(a,b,c) = c
|
||||
// let rec fac(n) = if((n:int)==0) 1 else (n*fac(n-1))
|
||||
// and main(x) = x::[x+1]
|
||||
// let lentr(l) = lent(0,l)
|
||||
// let rec len(l) =
|
||||
// switch(l) {
|
||||
// | [] => 0
|
||||
// | x::xs => 1+len(xs)
|
||||
// }
|
||||
// let lent(n,l) =
|
||||
// switch (l) {
|
||||
// | [] => n
|
||||
// | (x::xs) => lent(n+1,xs)
|
||||
// }
|
||||
// let rec app(a,b) =
|
||||
// switch(a) {
|
||||
// | [] => b
|
||||
// | (x::xs) => x::app(xs,b)
|
||||
// }
|
||||
// let rec revt(l,r) =
|
||||
// switch(l) {
|
||||
// | [] => r
|
||||
// | x::xs => revt(xs,x::r)
|
||||
// }
|
||||
// let rev(l) = revt(l,[])
|
||||
// let main(x:int) = {
|
||||
// switch(rev([1,2,3])) {
|
||||
// | h::_ => h
|
||||
// }
|
||||
// }
|
||||
//let fac(n:int) = {
|
||||
// if (n==0) 1 else (n*fac(n-1))
|
||||
//}
|
||||
//let main(x) = switch((12,34)) {
|
||||
//| (13,_) => x
|
||||
//| (_,a) => x+a
|
||||
// | y => y+1
|
||||
// }
|
||||
//let main(x) = ({y:0>1, x:x==0}:point(bool))
|
||||
//let main(x) = x
|
||||
//let main(x) = len(1::2::[])
|
||||
//let main(x) = ((x,x):list('a))
|
||||
// let main(x) = switch("a") {
|
||||
// | "b" => 0
|
||||
// | "a" => 1
|
||||
// | "c" => 2
|
||||
// }
|
||||
//let main(x) = x.color+1
|
||||
//let main(x) = switch(({x:x, y:x+1}:cp(int))) {
|
||||
// | {y:xx} => xx
|
||||
// }
|
||||
//let main(x) = {x:0, y:1, z:2}
|
||||
// let id(x) = x
|
||||
// let double(x) = x+x
|
||||
// let pair(x) = (1,2)
|
||||
// let unit(x) = ()
|
||||
// let tuples(x) = ((1,x),(2,3,4))
|
||||
// let singleton(x) = [x]
|
||||
// let rec seq(n) = if (n==0) [] else (app(seq(n-1),[n]))
|
||||
// let idString(s:string) = s
|
||||
// let pairString(s:string) = (s,s)
|
||||
// let revStrings(ss:list(string))=rev(ss)
|
||||
// let makePoint(x,y) = {x:x, y:y}
|
||||
// let getx(x) = x.x
|
||||
// let updatex(p,x) = p{x:x}
|
||||
// let quad(x) = {let y=x+x; let z=y+y; z;}
|
||||
// let noblock(x) = {x; x}
|
||||
// let unit(x) = ()
|
||||
// let foo(x) = switch (x) {
|
||||
// | y => y+1
|
||||
// }
|
||||
// let p(x) = {color:"blue", p:{x:x, y:x+1}}
|
||||
//let twice(f,x) = f(f(x))
|
||||
// let twice(f,x) = f(f(x))
|
||||
// let double(x) = x+x
|
||||
// let main(x) = twice((y=>y+y),x)
|
||||
// let rec map(f,xs) = switch(xs) {
|
||||
// | [] => []
|
||||
// | (x::ys) => f(x)::map(f,ys)
|
||||
// }
|
||||
// let id(x) = x
|
||||
// let main(xs) = map(double,xs)
|
||||
function z(f,x) = x
|
||||
function s(n) = (f,x)=>f(n(f,x))
|
||||
function add(m,n) = (f,x)=>m(f,n(f,x))
|
||||
include "List.aes"
|
||||
|
||||
entrypoint main() =
|
||||
let three=s(s(s(z)))
|
||||
add(three,three)
|
||||
(((i)=>i+1),0)
|
||||
contract IntegerHolder =
|
||||
type state = int
|
||||
entrypoint init(x) = x
|
||||
entrypoint get() = state
|
||||
|
||||
main contract Test =
|
||||
stateful entrypoint f(c) = Chain.clone(ref=c, 123)
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Remote =
|
||||
contract interface Remote =
|
||||
|
||||
type themap = map(int, string)
|
||||
entrypoint foo : () => themap
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
// Oracle.extend
|
||||
include "String.aes"
|
||||
contract UnappliedBuiltins =
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
type o = oracle(int, int)
|
||||
type t = list(int * string)
|
||||
type m = map(int, int)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
include "Option.aes"
|
||||
include "Pair.aes"
|
||||
include "String.aes"
|
||||
include "Triple.aes"
|
||||
|
||||
using Pair
|
||||
using Triple hiding [fst, snd]
|
||||
|
||||
namespace Nsp =
|
||||
using Option
|
||||
|
||||
function h() =
|
||||
let op = Some((2, 3, 4))
|
||||
if (is_some(op))
|
||||
thd(force(op)) == 4
|
||||
else
|
||||
false
|
||||
|
||||
contract Cntr =
|
||||
using Nsp
|
||||
|
||||
entrypoint init() = ()
|
||||
|
||||
function f() =
|
||||
let p = (1, 2)
|
||||
if (h())
|
||||
fst(p)
|
||||
else
|
||||
snd(p)
|
||||
|
||||
function g() =
|
||||
using String for [concat]
|
||||
|
||||
let s1 = "abc"
|
||||
let s2 = "def"
|
||||
concat(s1, s2)
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Xa =
|
||||
function f() = 1
|
||||
|
||||
namespace Xb =
|
||||
function f() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
|
||||
type state = int
|
||||
|
||||
entrypoint init() = A.f()
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Nsp =
|
||||
function f() = 1
|
||||
function g() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Nsp for [f]
|
||||
|
||||
entrypoint init() = g()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user