Compare commits
212 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 411399a39a | |||
| 8289f4af9d | |||
| 3cd8c37399 | |||
| 8d7025f794 | |||
| 99bb3fe1fb | |||
| 311bf49505 | |||
| 0e3bcba07d | |||
| 699d1f7ab8 | |||
| 1a40a93157 | |||
| c078119bc4 | |||
| 31fd8fe24f | |||
| 9ad8e26e88 | |||
| 5adeb6c93e | |||
| 256df25af4 | |||
| 83abfae32b | |||
| 4ca90feea0 | |||
| 09638daa90 | |||
| d59023a9f4 | |||
| 34b52739fd | |||
| 1c83287d45 | |||
| da92ddbd5d | |||
| c1c169273c | |||
| ad4c341a4a | |||
| f964fa89a1 | |||
| 8d8d9c6b83 | |||
| c98ea25e8b | |||
| 4dbc9858fb | |||
| 51f9eaa934 | |||
| 0ebcf006e2 | |||
| 381a7c98cd | |||
| 4bec4e5107 | |||
| 4dd247b159 | |||
| d926c4a7e3 | |||
| 7b8957b46a | |||
| e46226a693 | |||
| b599d581ee | |||
| b3767071a8 | |||
| b0e6418161 | |||
| a894876f56 | |||
| 0af45dfd19 | |||
| c5bfcd3bdc | |||
| 85879f5380 | |||
| 8897cc6cbd | |||
| 0ec7fdc6ac | |||
| 74aff5401b | |||
| cfcf0a8a81 | |||
| ca31db7cad | |||
| 196460a607 | |||
| bf04362f9a | |||
| d4ea7d5d3b | |||
| c1c3c29393 | |||
| b474bb22cd | |||
| c04f66a00a | |||
| 37d86ad45b | |||
| 60f3a484e6 | |||
| 40c78c1707 | |||
| cf08aeee04 | |||
| a04dd6c86d | |||
| f488b35f2e | |||
| cc1de9baba | |||
| fe5f5545d3 | |||
| 98a4049f03 | |||
| 3dce0e627b | |||
| 6b46fc268b | |||
| 30bedad164 | |||
| 4d6938c741 | |||
| 10fc88a21d | |||
| 3218a2c172 | |||
| 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 | |||
| 6858329faa | |||
| c2a3e333c7 | |||
| 4787830861 | |||
| a0111066e7 | |||
| 2311d19602 | |||
| 3b2ce63fa7 | |||
| 8b4a1aaf0d | |||
| c6e7db2381 | |||
| 4e60d019ca | |||
| b8002029cf | |||
| 1a14602f36 | |||
| e2ef95d6fd | |||
| 22aaeceba8 | |||
| 2a78189f31 | |||
| f1d95484a5 | |||
| 4504fb8dcf | |||
| 7e65f26211 | |||
| 8798e0b2c9 | |||
| 1dfc349065 | |||
| 1266d9ea99 | |||
| bbb049cb2e | |||
| 787551b8bc | |||
| ac673602b9 | |||
| 0b83422189 | |||
| 1a5017ce2b | |||
| 079b3a45c9 | |||
| c0d9759e60 | |||
| b7b242bc66 | |||
| 25fa365c29 | |||
| bb728db51b | |||
| 1fee306daa | |||
| c4eaf2249a | |||
| 6c23fd0d41 | |||
| 3d73e52d48 | |||
| 89b3ec3d17 | |||
| 7e32ef57c2 | |||
| ed5447e430 | |||
| db4de5d926 | |||
| 6b60fde2df | |||
| dd8eea0d55 | |||
| eb71abc665 | |||
| eff1ad4688 | |||
| cb2588fae2 | |||
| 08261a319b | |||
| f21717a9c0 | |||
| 9753f90034 | |||
| 8f240a7ddf | |||
| 54e43764ca | |||
| 05b87fe200 | |||
| cc07e3a638 | |||
| ea5850cf93 | |||
| d2dcb9e249 | |||
| adb3cf5406 | |||
| ad78f440d9 | |||
| 42cd47d1b3 | |||
| 93d2086ddf | |||
| 9487b79f42 | |||
| e64ac9396a | |||
| 4a812b6f3b | |||
| fe2d93ea8a | |||
| ecbc15db1b | |||
| d0caee24d9 | |||
| 57eb77f2f8 | |||
| 53ed60b498 | |||
| e49738c90c | |||
| a38a365181 | |||
| 0dddac3d86 | |||
| 3da694e798 | |||
| e98edd4eef | |||
| 2bad76314f | |||
| b9acf24dca | |||
| 6682b24156 | |||
| b31be6227d | |||
| bbc8555331 | |||
| 13bc821211 | |||
| 34c10e1518 | |||
| bb79e7dd89 | |||
| c3426f0e65 | |||
| db01e237c1 | |||
| 760d2841d1 | |||
| 43013ec920 | |||
| d821de6381 | |||
| 282f743925 | |||
| cf1072140e | |||
| 75797686ad | |||
| ed9384c2af | |||
| 1c24a700dc | |||
| f2e9fbcc51 | |||
| 2d49426fe0 | |||
| f5df2c1a5f | |||
| 04445e4dee | |||
| eec70f03a5 | |||
| c2c8e297ae | |||
| 5c5d3c60ef | |||
| 2a3274ba25 | |||
| 13b7bde44b | |||
| baf527b5fa | |||
| 422baa5b65 | |||
| 126e04ae42 |
+17
-1
@@ -3,11 +3,20 @@ version: 2.1
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
- image: aeternity/builder:bionic-otp24
|
||||
user: builder
|
||||
working_directory: ~/aesophia
|
||||
|
||||
jobs:
|
||||
verify_rebar_lock:
|
||||
executor: aebuilder
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Ensure lock file is up-to-date
|
||||
command: |
|
||||
./rebar3 upgrade
|
||||
git diff --quiet -- rebar.lock || (echo "rebar.lock is not up-to-date" && exit 1)
|
||||
build:
|
||||
executor: aebuilder
|
||||
steps:
|
||||
@@ -35,3 +44,10 @@ jobs:
|
||||
- _build/default/rebar3_20.3.8_plt
|
||||
- store_artifacts:
|
||||
path: _build/test/logs
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_test:
|
||||
jobs:
|
||||
- build
|
||||
- verify_rebar_lock
|
||||
|
||||
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,21 @@
|
||||
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
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push master
|
||||
@@ -0,0 +1,22 @@
|
||||
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
|
||||
- run: pip3 install -r .github/workflows/requirements.txt -U
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push --update-aliases $RELEASE_VERSION latest
|
||||
@@ -0,0 +1,5 @@
|
||||
mkdocs==1.4.2
|
||||
mkdocs-simple-hooks==0.1.5
|
||||
mkdocs-material==9.0.9
|
||||
mike==1.1.2
|
||||
pygments==2.14.0
|
||||
@@ -21,3 +21,6 @@ rebar3.crashdump
|
||||
aesophia
|
||||
.qcci
|
||||
current_counterexample.eqc
|
||||
test/contracts/test.aes
|
||||
__pycache__
|
||||
.docssite/docs/*.md
|
||||
|
||||
+188
-4
@@ -1,12 +1,188 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
All notable changes to this project shall be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
and this project adheres to [Semantic
|
||||
Versioning](https://semver.org/spec/v2.0.0.html). One deviation from _Keep a
|
||||
Changelog_ is that "Unreleased" may suggest a specific version bump in case of
|
||||
breaking changes.
|
||||
|
||||
## [Unreleased]
|
||||
## [Unreleased] [8.x.x]
|
||||
### Added
|
||||
### Changed
|
||||
- `pp_assembler` option to `pp_fate` as it is more specific.
|
||||
### Removed
|
||||
- `pp_sophia_code` option as it was a duplicate of `pp_ast`.
|
||||
- `aeso_ast` module as it was unused.
|
||||
### Fixed
|
||||
- Warning about unused include when there is no include.
|
||||
|
||||
## [7.1.0]
|
||||
### Added
|
||||
- Options to enable/disable certain optimizations.
|
||||
- The ability to call a different instance of the current contract
|
||||
```
|
||||
contract Main =
|
||||
entrypoint spend(x : int) : int = x
|
||||
entrypoint f(c : Main) : int = c.spend(10)
|
||||
```
|
||||
- Return a mapping from variables to FATE registers in the compilation output.
|
||||
- Hole expression.
|
||||
### Changed
|
||||
- Type definitions serialised to ACI as `typedefs` field instead of `type_defs` to increase compatibility.
|
||||
- Check contracts and entrypoints modifiers when implementing interfaces.
|
||||
- Contracts can no longer be used as namespaces.
|
||||
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
|
||||
### Fixed
|
||||
- Typechecker crashes if Chain.create or Chain.clone are used without arguments.
|
||||
|
||||
## [7.0.1]
|
||||
### Added
|
||||
- Add CONTRIBUTING.md file.
|
||||
### Changed
|
||||
- Update Sophia syntax docs to include missing information about existing syntax.
|
||||
### Fixed
|
||||
- [404](https://github.com/aeternity/aesophia/issues/404) Contract polymorphism crashes on non-obvious child contract typing.
|
||||
|
||||
## [7.0.0]
|
||||
### Added
|
||||
- Added support for `EXIT` opcode via `exit : (string) => 'a` function (behaves same as `ABORT`, but consumes all gas).
|
||||
- Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
|
||||
- The pipe operator |>
|
||||
```
|
||||
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
|
||||
```
|
||||
- Allow binary operators to be used as lambdas
|
||||
```
|
||||
function sum(l : list(int)) : int = foldl((+), 0, l)
|
||||
function logical_and(x, y) = (&&)(x, y)
|
||||
```
|
||||
- Contract interfaces polymorphism
|
||||
### Changed
|
||||
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
|
||||
- Ban empty record definitions (e.g. `record r = {}` would give an error).
|
||||
### Removed
|
||||
- Support for AEVM has been entirely wiped
|
||||
|
||||
## [6.1.0] - 2021-10-20
|
||||
### Added
|
||||
- `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)
|
||||
has been added. Use it by `include "String.aes"`. It includes functions for
|
||||
turning strings into lists of characters for detailed manipulation. For
|
||||
example:
|
||||
```
|
||||
include "String.aes"
|
||||
contract C =
|
||||
entrypoint filter_all_a(s: string) : string =
|
||||
String.from_list(List.filter((c : char) => c != 'a', String.to_list(s)))
|
||||
```
|
||||
will return a list with all `a`'s removed.
|
||||
|
||||
There are also convenience functions `split`, `concat`, `to_upper`,
|
||||
`to_lower`, etc.
|
||||
|
||||
All String functions in FATEv2 operate on unicode code points.
|
||||
- Operations for pairing-based cryptography has been added the operations
|
||||
are in the standard library [BLS12_381](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#bls12_381).
|
||||
With these operations it is possible to do Zero Knowledge-proofs, etc.
|
||||
The operations are for the BLS12-381 curve (as the name suggests).
|
||||
- Calls to functions in other contracts (i.e. _remote calls_) can now be
|
||||
[`protected`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#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`).
|
||||
|
||||
If `protected = true` the result of the contract call is wrapped in an
|
||||
`option`, and `Some(value)` indicates a succesful execution and `None`
|
||||
indicates that the contract call failed. Note: any gas consumed until
|
||||
the failure is still charged, but all side effects in the remote
|
||||
contract are rolled back on failure.
|
||||
- A new chain operation [`AENS.update`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#aens-interface)
|
||||
is supported.
|
||||
- New chain exploring operations `AENS.lookup` and `Oracle.expiry` to
|
||||
look up an AENS record and the expiry of an Oracle respectively, are added.
|
||||
- Transaction introspection (`Auth.tx`) has been added. When a Generalized
|
||||
account is authorized, the authorization function needs access to the
|
||||
transaction (and the transaction hash) for the wrapped transaction. The
|
||||
transaction and the transaction hash is available `Auth.tx`, it is only
|
||||
available during authentication if invoked by a normal contract call
|
||||
it returns `None`. Example:
|
||||
```
|
||||
switch(Auth.tx)
|
||||
None => abort("Not in Auth context")
|
||||
Some(tx0) =>
|
||||
switch(tx0.tx)
|
||||
Chain.SpendTx(_, amount, _) => amount > 400
|
||||
Chain.ContractCallTx(_, _) => true
|
||||
_ => false
|
||||
```
|
||||
- A debug mode is a added to the compiler. Right now its only use is to
|
||||
turn off hermetization.
|
||||
### Changed
|
||||
- The function `Chain.block_hash(height)` is now (in FATEv2) defined for
|
||||
the current height - this used to be an error.
|
||||
- Standard library: Sort is optimized to do `mergesort` and a `contains`
|
||||
function is added.
|
||||
- Improved type errors and explicit errors for some syntax errors (empty code
|
||||
blocks, etc.).
|
||||
- Compiler optimization: The ACI is generated alongside bytecode. This means
|
||||
that multiple compiler passes can be avoided.
|
||||
- Compiler optimization: Improved parsing (less stack used when transpiled).
|
||||
- A bug where constraints were handled out of order fixed.
|
||||
- Fixed calldata decoding for singleton records.
|
||||
- Improved the documentation w.r.t. signatures, especially stressing the fact that
|
||||
the network ID is a part of what is signed.
|
||||
### Removed
|
||||
|
||||
## [4.3.0]
|
||||
@@ -211,7 +387,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||
pass a (stubbed) contract string.
|
||||
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD
|
||||
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0
|
||||
[7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1
|
||||
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
|
||||
[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
|
||||
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Contributing to Sophia
|
||||
|
||||
## Checklist For Creating New Pull Requests
|
||||
|
||||
The following points should be considered before creating a new PR to the Sophia compiler.
|
||||
|
||||
### Documentation
|
||||
|
||||
- The [Changelog](CHANGELOG.md) file should be updated for all PRs.
|
||||
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
|
||||
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
|
||||
- If a PR introduces a new compiler option, the new option should be documented in the file
|
||||
[aeso_compiler.md](docs/aeso_compiler.md).
|
||||
|
||||
### Tests
|
||||
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the contract [all_syntax.aes](test/contracts/all_syntax.aes) should be updated to include the new syntax.
|
||||
- If a PR fixes a bug, the code that replicates the bug should be added as a new passing test contract.
|
||||
- If a PR introduces a new feature, add tests for both successful and failing usage of that feature. In order to run the entire compilation pipeline and to avoid erroring during intermediate steps, failing tests should not be mixed with the successful ones.
|
||||
|
||||
### Source Code
|
||||
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the following code should be updated to handle the new syntax:
|
||||
- The function `aeso_syntax_utils:fold/4` in the file [aeso_syntax_utils.erl](src/aeso_syntax_utils.erl).
|
||||
- Any related pretty printing function in the file [aeso_pretty.erl](src/aeso_pretty.erl), depending on the type of the newly added syntax.
|
||||
|
||||
## Checklist For Creating a Release
|
||||
|
||||
- Update the version in the file [aesophia.app.src](src/aesophia.app.src).
|
||||
- Update the version in the file [rebar.config](rebar.config).
|
||||
- In the [Changelog](CHANGELOG.md):
|
||||
- Update the `Unreleased` changes to be under the new version.
|
||||
- Update the version at the bottom of the file.
|
||||
- Commit and the changes and create a new PR (check the commit of [v6.1.0](https://github.com/aeternity/aesophia/commit/5ad5270e381f6e810d7b8b5cdc168d283e7a90bb) for reference).
|
||||
- Create a release after merging the new PR to `master` branch.
|
||||
- After releasing `aesophia`, refer to each of the following repositories and create new releases as well, using the new `aesophia` release:
|
||||
- [aesophia_cli](https://github.com/aeternity/aesophia_cli)
|
||||
- [aesophia_http](https://github.com/aeternity/aesophia_http)
|
||||
- [aerepl](https://github.com/aeternity/aerepl)
|
||||
@@ -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
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
# aesophia
|
||||
|
||||
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code.
|
||||
|
||||
It is an OTP application written in Erlang and is by default included in
|
||||
[the æternity node](https://github.com/aeternity/epoch). However, it can
|
||||
also be included in other systems to compile contracts coded in sophia which
|
||||
can then be loaded into the æternity system.
|
||||
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
|
||||
|
||||
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 [æ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)
|
||||
* [Contributing](CONTRIBUTING.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)
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_defs": [
|
||||
"typedefs": [
|
||||
{
|
||||
"name": "answers",
|
||||
"typedef": {
|
||||
@@ -138,7 +138,7 @@ be included inside another contract.
|
||||
state =>
|
||||
#{record =>
|
||||
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"answers">>,
|
||||
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
|
||||
vars => []}]}}]}
|
||||
|
||||
+29
-12
@@ -49,11 +49,37 @@ The **pp_** options all print to standard output the following:
|
||||
|
||||
`pp_typed_ast` - print the AST with type information at each node
|
||||
|
||||
`pp_icode` - print the internal code structure
|
||||
|
||||
`pp_assembler` - print the generated assembler code
|
||||
|
||||
`pp_bytecode` - print the bytecode instructions
|
||||
The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
|
||||
|
||||
The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers.
|
||||
|
||||
#### Options to control which compiler optimizations should run:
|
||||
|
||||
By default all optimizations are turned on, to disable an optimization, it should be
|
||||
explicitly set to false and passed as a compiler option.
|
||||
|
||||
List of optimizations:
|
||||
|
||||
- optimize_inliner
|
||||
- optimize_inline_local_functions
|
||||
- optimize_bind_subexpressions
|
||||
- optimize_let_floating
|
||||
- optimize_simplifier
|
||||
- optimize_drop_unused_lets
|
||||
- optimize_push_consume
|
||||
- optimize_one_shot_var
|
||||
- optimize_write_to_dead_var
|
||||
- optimize_inline_switch_target
|
||||
- optimize_swap_push
|
||||
- optimize_swap_pop
|
||||
- optimize_swap_write
|
||||
- optimize_constant_propagation
|
||||
- optimize_prune_impossible_branches
|
||||
- optimize_single_successful_branch
|
||||
- optimize_inline_store
|
||||
- optimize_float_switch_bod
|
||||
|
||||
#### check_call(ContractString, Options) -> CheckRet
|
||||
|
||||
@@ -66,15 +92,6 @@ Type = term()
|
||||
```
|
||||
Check a call in contract through the `__call` function.
|
||||
|
||||
#### sophia_type_to_typerep(String) -> TypeRep
|
||||
|
||||
Types
|
||||
``` erlang
|
||||
{ok,TypeRep} | {error, badtype}
|
||||
```
|
||||
|
||||
Get the type representation of a type declaration.
|
||||
|
||||
#### version() -> {ok, Version} | {error, term()}
|
||||
|
||||
Types
|
||||
|
||||
@@ -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
-1071
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.
|
||||
File diff suppressed because it is too large
Load Diff
+1809
-1159
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
||||
# Syntax
|
||||
|
||||
## Lexical syntax
|
||||
|
||||
### Comments
|
||||
|
||||
Single line comments start with `//` and block comments are enclosed in `/*`
|
||||
and `*/` and can be nested.
|
||||
|
||||
### Keywords
|
||||
|
||||
```
|
||||
contract include let switch type record datatype if elif else function
|
||||
stateful payable true false mod public entrypoint private indexed namespace
|
||||
interface main using as for hiding
|
||||
```
|
||||
|
||||
### 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'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
|
||||
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
|
||||
| 'namespace' Con '=' Block(Decl)
|
||||
| '@compiler' PragmaOp Version
|
||||
| 'include' String
|
||||
| Using
|
||||
|
||||
Implement ::= ':' Sep1(Con, ',')
|
||||
|
||||
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||
| Using
|
||||
|
||||
FunDecl ::= Id ':' Type // Type signature
|
||||
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
||||
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
|
||||
|
||||
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
|
||||
|
||||
Using ::= 'using' Con ['as' Con] [UsingParts]
|
||||
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
|
||||
| 'hiding' '[' Sep1(Id, ',') ']'
|
||||
|
||||
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
|
||||
| Using
|
||||
| Expr
|
||||
|
||||
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
||||
| Pattern '=' Block(Stmt) // Value definition
|
||||
|
||||
Case ::= Pattern '=>' Block(Stmt)
|
||||
| Pattern Block(GuardedCase)
|
||||
|
||||
GuardedCase ::= '|' Sep1(Expr, ',') '=>' 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
|
||||
| '(' BinOp ')' // Operator lambda (+)
|
||||
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
|
||||
| Expr ':' Type // Type annotation 5 : int
|
||||
| Expr BinOp Expr // Binary operator x + y
|
||||
| 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
|
||||
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
|
||||
| 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
|
||||
| '???' // Hole expression 1 + ???
|
||||
|
||||
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
|
||||
| `|>` | functional operators
|
||||
|
||||
## Operator precedence
|
||||
|
||||
In order of highest to lowest precedence.
|
||||
|
||||
| Operators | Associativity
|
||||
| --- | ---
|
||||
| `!` | right
|
||||
| `^` | left
|
||||
| `*` `/` `mod` | left
|
||||
| `-` (unary) | right
|
||||
| `+` `-` | left
|
||||
| `::` `++` | right
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `&&` | right
|
||||
| `||` | right
|
||||
| `|>` | left
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace BLS12_381 =
|
||||
type fr = MCL_BLS12_381.fr
|
||||
type fp = MCL_BLS12_381.fp
|
||||
record fp2 = { x1 : fp, x2 : fp }
|
||||
record g1 = { x : fp, y : fp, z : fp }
|
||||
record g2 = { x : fp2, y : fp2, z : fp2 }
|
||||
record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp,
|
||||
x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }
|
||||
|
||||
function pairing_check(us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => true
|
||||
(x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys)
|
||||
|
||||
function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => gt_is_one(acc)
|
||||
(x :: xs, y :: ys) =>
|
||||
pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys)
|
||||
|
||||
function int_to_fr(x : int) = MCL_BLS12_381.int_to_fr(x)
|
||||
function int_to_fp(x : int) = MCL_BLS12_381.int_to_fp(x)
|
||||
function fr_to_int(x : fr) = MCL_BLS12_381.fr_to_int(x)
|
||||
function fp_to_int(x : fp) = MCL_BLS12_381.fp_to_int(x)
|
||||
|
||||
function mk_g1(x : int, y : int, z : int) : g1 =
|
||||
{ x = int_to_fp(x), y = int_to_fp(y), z = int_to_fp(z) }
|
||||
|
||||
function mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 =
|
||||
{ x = {x1 = int_to_fp(x1), x2 = int_to_fp(x2)},
|
||||
y = {x1 = int_to_fp(y1), x2 = int_to_fp(y2)},
|
||||
z = {x1 = int_to_fp(z1), x2 = int_to_fp(z2)} }
|
||||
|
||||
function pack_g1(t) = switch(t)
|
||||
(x, y, z) => {x = x, y = y, z = z} : g1
|
||||
function pack_g2(t) = switch(t)
|
||||
((x1, x2), (y1, y2), (z1, z2)) =>
|
||||
{x = {x1 = x1, x2 = x2}, y = {x1 = y1, x2 = y2}, z = {x1 = z1, x2 = z2}} : g2
|
||||
function pack_gt(t) = switch(t)
|
||||
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) =>
|
||||
{x1 = x1, x2 = x2, x3 = x3, x4 = x4, x5 = x5, x6 = x6,
|
||||
x7 = x7, x8 = x8, x9 = x9, x10 = x10, x11 = x11, x12 = x12} : gt
|
||||
|
||||
function g1_neg(p : g1) = pack_g1(MCL_BLS12_381.g1_neg((p.x, p.y, p.z)))
|
||||
function g1_norm(p : g1) = pack_g1(MCL_BLS12_381.g1_norm((p.x, p.y, p.z)))
|
||||
function g1_valid(p : g1) = MCL_BLS12_381.g1_valid((p.x, p.y, p.z))
|
||||
function g1_is_zero(p : g1) = MCL_BLS12_381.g1_is_zero((p.x, p.y, p.z))
|
||||
function g1_add(p : g1, q : g1) = pack_g1(MCL_BLS12_381.g1_add((p.x, p.y, p.z), (q.x, q.y, q.z)))
|
||||
function g1_mul(k : fr, p : g1) = pack_g1(MCL_BLS12_381.g1_mul(k, (p.x, p.y, p.z)))
|
||||
|
||||
function g2_neg(p : g2) = pack_g2(MCL_BLS12_381.g2_neg(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
|
||||
function g2_norm(p : g2) = pack_g2(MCL_BLS12_381.g2_norm(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
|
||||
function g2_valid(p : g2) = MCL_BLS12_381.g2_valid(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))
|
||||
function g2_is_zero(p : g2) = MCL_BLS12_381.g2_is_zero(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))
|
||||
function g2_add(p : g2, q : g2) = pack_g2(MCL_BLS12_381.g2_add(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)),
|
||||
((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
|
||||
function g2_mul(k : fr, p : g2) = pack_g2(MCL_BLS12_381.g2_mul(k, ((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
|
||||
|
||||
function gt_inv(p : gt) = pack_gt(MCL_BLS12_381.gt_inv((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12)))
|
||||
function gt_add(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_add((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12),
|
||||
(q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12)))
|
||||
function gt_mul(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_mul((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12),
|
||||
(q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12)))
|
||||
function gt_pow(p : gt, k : fr) = pack_gt(MCL_BLS12_381.gt_pow((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12), k))
|
||||
function gt_is_one(p : gt) = MCL_BLS12_381.gt_is_one((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12))
|
||||
function pairing(p : g1, q : g2) = pack_gt(MCL_BLS12_381.pairing((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
|
||||
function miller_loop(p : g1, q : g2) = pack_gt(MCL_BLS12_381.miller_loop((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
|
||||
function final_exp(p : gt) = pack_gt(MCL_BLS12_381.final_exp((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12)))
|
||||
@@ -0,0 +1,126 @@
|
||||
@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)
|
||||
|
||||
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) =
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Func =
|
||||
|
||||
function id(x : 'a) : 'a = x
|
||||
|
||||
function const(x : 'a) : 'b => 'a = (y) => x
|
||||
function const(x : 'a) : 'b => 'a = (_) => x
|
||||
|
||||
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
|
||||
|
||||
|
||||
+158
-89
@@ -15,10 +15,24 @@ namespace List =
|
||||
_::t => Some(t)
|
||||
|
||||
function last(l : list('a)) : option('a) = switch(l)
|
||||
[] => None
|
||||
[x] => Some(x)
|
||||
[] => None
|
||||
[x] => Some(x)
|
||||
_::t => last(t)
|
||||
|
||||
function drop_last(l : list('a)) : option(list('a)) = switch(l)
|
||||
[] => None
|
||||
_ => Some(drop_last_unsafe(l))
|
||||
|
||||
function drop_last_unsafe(l : list('a)) : list('a) = switch(l)
|
||||
[_] => []
|
||||
h::t => h::drop_last_unsafe(t)
|
||||
[] => abort("drop_last_unsafe: list empty")
|
||||
|
||||
|
||||
function contains(e : 'a, l : list('a)) = switch(l)
|
||||
[] => false
|
||||
h::t => h == e || contains(e, t)
|
||||
|
||||
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
|
||||
* if no such element exists.
|
||||
*/
|
||||
@@ -28,14 +42,15 @@ namespace List =
|
||||
|
||||
/** Returns list of all indices of elements from `l` that fulfill the predicate `p`.
|
||||
*/
|
||||
function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0, [])
|
||||
function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0)
|
||||
private function find_indices_( p : 'a => bool
|
||||
, l : list('a)
|
||||
, n : int
|
||||
, acc : list(int)
|
||||
) : list(int) = switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => find_indices_(p, t, n+1, if(p(h)) n::acc else acc)
|
||||
[] => []
|
||||
h::t =>
|
||||
let rest = find_indices_(p, t, n+1)
|
||||
if(p(h)) n::rest else rest
|
||||
|
||||
function nth(n : int, l : list('a)) : option('a) =
|
||||
switch(l)
|
||||
@@ -64,44 +79,45 @@ namespace List =
|
||||
* `a` and `b` jumping by given `step`. Includes `a` and takes
|
||||
* `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) = from_to_step_(a, b, s, [])
|
||||
private function from_to_step_(a, b, s, acc) =
|
||||
if (a > b) reverse(acc) else from_to_step_(a + s, b, s, a :: acc)
|
||||
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
|
||||
else from_to_step_(a, b - s, s, b::acc)
|
||||
|
||||
|
||||
/** Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow
|
||||
*/
|
||||
function replace_at(n : int, e : 'a, l : list('a)) : list('a) =
|
||||
if(n<0) abort("insert_at underflow") else replace_at_(n, e, l, [])
|
||||
private function replace_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
|
||||
if(n<0) abort("insert_at underflow") else replace_at_(n, e, l)
|
||||
private function replace_at_(n : int, e : 'a, l : list('a)) : list('a) =
|
||||
switch(l)
|
||||
[] => abort("replace_at overflow")
|
||||
h::t => if (n == 0) reverse(e::acc) ++ t
|
||||
else replace_at_(n-1, e, t, h::acc)
|
||||
h::t => if (n == 0) e::t
|
||||
else h::replace_at_(n-1, e, t)
|
||||
|
||||
/** Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow
|
||||
*/
|
||||
function insert_at(n : int, e : 'a, l : list('a)) : list('a) =
|
||||
if(n<0) abort("insert_at underflow") else insert_at_(n, e, l, [])
|
||||
private function insert_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
|
||||
if (n == 0) reverse(e::acc) ++ l
|
||||
if(n<0) abort("insert_at underflow") else insert_at_(n, e, l)
|
||||
private function insert_at_(n : int, e : 'a, l : list('a)) : list('a) =
|
||||
if (n == 0) e::l
|
||||
else switch(l)
|
||||
[] => abort("insert_at overflow")
|
||||
h::t => insert_at_(n-1, e, t, h::acc)
|
||||
h::t => h::insert_at_(n-1, e, t)
|
||||
|
||||
/** Assuming that cmp represents `<` comparison, inserts `x` before
|
||||
* the first element in the list `l` which is greater than it
|
||||
*/
|
||||
function insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
|
||||
insert_by_(cmp, x, l, [])
|
||||
private function insert_by_(cmp : (('a, 'a) => bool), x : 'a, l : list('a), acc : list('a)) : list('a) =
|
||||
switch(l)
|
||||
[] => reverse(x::acc)
|
||||
[] => [x]
|
||||
h::t =>
|
||||
if(cmp(x, h)) // x < h
|
||||
reverse(acc) ++ (x::l)
|
||||
x::l
|
||||
else
|
||||
insert_by_(cmp, x, t, h::acc)
|
||||
h::insert_by(cmp, x, t)
|
||||
|
||||
|
||||
function foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b = switch(l)
|
||||
@@ -119,49 +135,52 @@ namespace List =
|
||||
f(e)
|
||||
foreach(l', f)
|
||||
|
||||
function reverse(l : list('a)) : list('a) = foldl((lst, el) => el :: lst, [], l)
|
||||
function reverse(l : list('a)) : list('a) = reverse_(l, [])
|
||||
private function reverse_(l : list('a), acc : list('a)) : list('a) = switch(l)
|
||||
[] => acc
|
||||
h::t => reverse_(t, h::acc)
|
||||
|
||||
function map(f : 'a => 'b, l : list('a)) : list('b) = map_(f, l, [])
|
||||
private function map_(f : 'a => 'b, l : list('a), acc : list('b)) : list('b) = switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => map_(f, t, f(h)::acc)
|
||||
function map(f : 'a => 'b, l : list('a)) : list('b) = switch(l)
|
||||
[] => []
|
||||
h::t => f(h)::map(f, t)
|
||||
|
||||
/** Effectively composition of `map` and `flatten`
|
||||
*/
|
||||
function flat_map(f : 'a => list('b), l : list('a)) : list('b) =
|
||||
ListInternal.flat_map(f, l)
|
||||
|
||||
function filter(p : 'a => bool, l : list('a)) : list('a) = filter_(p, l, [])
|
||||
private function filter_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => filter_(p, t, if(p(h)) h::acc else acc)
|
||||
function filter(p : 'a => bool, l : list('a)) : list('a) = switch(l)
|
||||
[] => []
|
||||
h::t =>
|
||||
let rest = filter(p, t)
|
||||
if(p(h)) h::rest else rest
|
||||
|
||||
/** Take `n` first elements
|
||||
/** Take up to `n` first elements
|
||||
*/
|
||||
function take(n : int, l : list('a)) : list('a) =
|
||||
if(n < 0) abort("Take negative number of elements") else take_(n, l, [])
|
||||
private function take_(n : int, l : list('a), acc : list('a)) : list('a) =
|
||||
if(n == 0) reverse(acc)
|
||||
if(n < 0) abort("Take negative number of elements") else take_(n, l)
|
||||
private function take_(n : int, l : list('a)) : list('a) =
|
||||
if(n == 0) []
|
||||
else switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => take_(n-1, t, h::acc)
|
||||
[] => []
|
||||
h::t => h::take_(n-1, t)
|
||||
|
||||
/** Drop `n` first elements
|
||||
/** Drop up to `n` first elements
|
||||
*/
|
||||
function drop(n : int, l : list('a)) : list('a) =
|
||||
if(n < 0) abort("Drop negative number of elements")
|
||||
elif (n == 0) l
|
||||
if(n < 0) abort("Drop negative number of elements") else drop_(n, l)
|
||||
private function drop_(n : int, l : list('a)) : list('a) =
|
||||
if (n == 0) l
|
||||
else switch(l)
|
||||
[] => []
|
||||
h::t => drop(n-1, t)
|
||||
_::t => drop_(n-1, t)
|
||||
|
||||
/** Get the longest prefix of a list in which every element
|
||||
* matches predicate `p`
|
||||
*/
|
||||
function take_while(p : 'a => bool, l : list('a)) : list('a) = take_while_(p, l, [])
|
||||
private function take_while_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => if(p(h)) take_while_(p, t, h::acc) else reverse(acc)
|
||||
function take_while(p : 'a => bool, l : list('a)) : list('a) = switch(l)
|
||||
[] => []
|
||||
h::t => if(p(h)) h::take_while(p, t) else []
|
||||
|
||||
/** Drop elements from `l` until `p` holds
|
||||
*/
|
||||
@@ -172,18 +191,15 @@ namespace List =
|
||||
/** Splits list into two lists of elements that respectively
|
||||
* match and don't match predicate `p`
|
||||
*/
|
||||
function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = partition_(p, l, [], [])
|
||||
private function partition_( p : 'a => bool
|
||||
, l : list('a)
|
||||
, acc_t : list('a)
|
||||
, acc_f : list('a)
|
||||
) : (list('a) * list('a)) = switch(l)
|
||||
[] => (reverse(acc_t), reverse(acc_f))
|
||||
h::t => if(p(h)) partition_(p, t, h::acc_t, acc_f) else partition_(p, t, acc_t, h::acc_f)
|
||||
function partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst)
|
||||
[] => ([], [])
|
||||
h::t =>
|
||||
let (l, r) = partition(p, t)
|
||||
if(p(h)) (h::l, r) else (l, h::r)
|
||||
|
||||
/** Flattens list of lists into a single list
|
||||
*/
|
||||
function flatten(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll)
|
||||
function flatten(l : list(list('a))) : list('a) = switch(l)
|
||||
[] => []
|
||||
h::t => h ++ flatten(t)
|
||||
|
||||
function all(p : 'a => bool, l : list('a)) : bool = switch(l)
|
||||
[] => true
|
||||
@@ -193,55 +209,108 @@ 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 longer tail.
|
||||
* Drops the tail of the longer list.
|
||||
*/
|
||||
function zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) = zip_with_(f, l1, l2, [])
|
||||
private function zip_with_( f : ('a, 'b) => 'c
|
||||
private function zip_with( f : ('a, 'b) => 'c
|
||||
, l1 : list('a)
|
||||
, l2 : list('b)
|
||||
, acc : list('c)
|
||||
) : list('c) = switch ((l1, l2))
|
||||
(h1::t1, h2::t2) => zip_with_(f, t1, t2, f(h1, h2)::acc)
|
||||
_ => reverse(acc)
|
||||
(h1::t1, h2::t2) => f(h1, h2)::zip_with(f, t1, t2)
|
||||
_ => []
|
||||
|
||||
/** Zips two lists into list of pairs. Drops longer tail.
|
||||
/** Zips two lists into list of pairs.
|
||||
* Drops the tail of the longer list.
|
||||
*/
|
||||
function zip(l1 : list('a), l2 : list('b)) : list('a * 'b) = zip_with((a, b) => (a, b), l1, l2)
|
||||
|
||||
function unzip(l : list('a * 'b)) : list('a) * list('b) = unzip_(l, [], [])
|
||||
private function unzip_( l : list('a * 'b)
|
||||
, acc_l : list('a)
|
||||
, acc_r : list('b)
|
||||
) : (list('a) * list('b)) = switch(l)
|
||||
[] => (reverse(acc_l), reverse(acc_r))
|
||||
(left, right)::t => unzip_(t, left::acc_l, right::acc_r)
|
||||
function unzip(l : list('a * 'b)) : (list('a) * list('b)) = switch(l)
|
||||
[] => ([], [])
|
||||
(h1, h2)::t =>
|
||||
let (t1, t2) = unzip(t)
|
||||
(h1::t1, h2::t2)
|
||||
|
||||
|
||||
// TODO: Improve?
|
||||
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
|
||||
[] => []
|
||||
h::t => switch (partition((x) => lesser_cmp(x, h), t))
|
||||
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
|
||||
/** Merges two sorted lists using `lt` comparator
|
||||
*/
|
||||
function
|
||||
merge : (('a, 'a) => bool, list('a), list('a)) => list('a)
|
||||
merge(lt, x::xs, y::ys) =
|
||||
if(lt(x, y)) x::merge(lt, xs, y::ys)
|
||||
else y::merge(lt, x::xs, ys)
|
||||
merge(_, [], ys) = ys
|
||||
merge(_, xs, []) = xs
|
||||
|
||||
|
||||
/** Mergesort inspired by
|
||||
* https://hackage.haskell.org/package/base-4.14.1.0/docs/src/Data.OldList.html#sort
|
||||
*/
|
||||
function
|
||||
sort : (('a, 'a) => bool, list('a)) => list('a)
|
||||
sort(_, []) = []
|
||||
sort(lt, l) =
|
||||
merge_all(lt, monotonic_subs(lt, l))
|
||||
|
||||
/** Splits list into compound increasing sublists
|
||||
*/
|
||||
private function
|
||||
monotonic_subs : (('a, 'a) => bool, list('a)) => list(list('a))
|
||||
monotonic_subs(lt, x::y::rest) =
|
||||
if(lt(y, x)) desc(lt, y, [x], rest)
|
||||
else asc(lt, y, [x], rest)
|
||||
monotonic_subs(_, l) = [l]
|
||||
|
||||
/** Extracts the longest descending prefix and proceeds with monotonic split
|
||||
*/
|
||||
private function
|
||||
desc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
|
||||
desc(lt, x, acc, h::t) =
|
||||
if(lt(x, h)) (x::acc) :: monotonic_subs(lt, h::t)
|
||||
else desc(lt, h, x::acc, t)
|
||||
desc(_, x, acc, []) = [x::acc]
|
||||
|
||||
/** Extracts the longest ascending prefix and proceeds with monotonic split
|
||||
*/
|
||||
private function
|
||||
asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
|
||||
asc(lt, x, acc, h::t) =
|
||||
if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t)
|
||||
else asc(lt, h, x::acc, t)
|
||||
asc(_, x, acc, []) = [List.reverse(x::acc)]
|
||||
|
||||
/** Merges list of sorted lists
|
||||
*/
|
||||
private function
|
||||
merge_all : (('a, 'a) => bool, list(list('a))) => list('a)
|
||||
merge_all(_, [part]) = part
|
||||
merge_all(lt, parts) = merge_all(lt, merge_pairs(lt, parts))
|
||||
|
||||
/** Single round of `merge_all` – pairs of lists in a list of list
|
||||
*/
|
||||
private function
|
||||
merge_pairs : (('a, 'a) => bool, list(list('a))) => list(list('a))
|
||||
merge_pairs(lt, x::y::rest) = merge(lt, x, y) :: merge_pairs(lt, rest)
|
||||
merge_pairs(_, l) = l
|
||||
|
||||
|
||||
/** Puts `delim` between every two members of the list
|
||||
*/
|
||||
function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, [])
|
||||
private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l)
|
||||
[] => reverse(acc)
|
||||
[e] => reverse(e::acc)
|
||||
h::t => intersperse_(delim, t, delim::h::acc)
|
||||
|
||||
function intersperse(delim : 'a, l : list('a)) : list('a) = switch(l)
|
||||
[] => []
|
||||
[e] => [e]
|
||||
h::t => h::delim::intersperse(delim, t)
|
||||
|
||||
/** Effectively a zip with an infinite sequence of natural numbers
|
||||
*/
|
||||
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0, [])
|
||||
private function enumerate_(l : list('a), n : int, acc : list(int * 'a)) : list(int * 'a) = switch(l)
|
||||
[] => reverse(acc)
|
||||
h::t => enumerate_(t, n + 1, (n, h)::acc)
|
||||
|
||||
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0)
|
||||
private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l)
|
||||
[] => []
|
||||
h::t => (n, h)::enumerate_(t, n + 1)
|
||||
|
||||
@@ -2,8 +2,8 @@ namespace ListInternal =
|
||||
|
||||
// -- Flatmap ----------------------------------------------------------------
|
||||
|
||||
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
|
||||
switch(xs)
|
||||
function flat_map(f : 'a => list('b), lst : list('a)) : list('b) =
|
||||
switch(lst)
|
||||
[] => []
|
||||
x :: xs => f(x) ++ flat_map(f, xs)
|
||||
|
||||
|
||||
+22
-14
@@ -1,5 +1,3 @@
|
||||
include "List.aes"
|
||||
|
||||
namespace Option =
|
||||
|
||||
function is_none(o : option('a)) : bool = switch(o)
|
||||
@@ -22,7 +20,17 @@ namespace Option =
|
||||
|
||||
/** Assume it is `Some`
|
||||
*/
|
||||
function force(o : option('a)) : 'a = default(abort("Forced None value"), o)
|
||||
function force(o : option('a)) : 'a = switch(o)
|
||||
None => abort("Forced None value")
|
||||
Some(x) => x
|
||||
|
||||
/** 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)
|
||||
|
||||
@@ -65,20 +73,21 @@ namespace Option =
|
||||
/** Turns list of options into a list of elements that are under `Some`s.
|
||||
* Safe.
|
||||
*/
|
||||
function filter_options(l : list(option('a))) : list('a) = filter_options_(l, [])
|
||||
private function filter_options_(l : list (option('a)), acc : list('a)) : list('a) = switch(l)
|
||||
[] => List.reverse(acc)
|
||||
None::t => filter_options_(t, acc)
|
||||
Some(x)::t => filter_options_(t, x::acc)
|
||||
function filter_options(l : list(option('a))) : list('a) = switch(l)
|
||||
[] => []
|
||||
None::t => filter_options(t)
|
||||
Some(x)::t => x::filter_options(t)
|
||||
|
||||
/** Just like `filter_options` but requires all elements to be `Some` and returns
|
||||
* None if any of them is not
|
||||
*/
|
||||
function seq_options(l : list (option('a))) : option (list('a)) = seq_options_(l, [])
|
||||
private function seq_options_(l : list (option('a)), acc : list('a)) : option(list('a)) = switch(l)
|
||||
[] => Some(List.reverse(acc))
|
||||
None::t => None
|
||||
Some(x)::t => seq_options_(t, x::acc)
|
||||
function seq_options(l : list (option('a))) : option (list('a)) = switch(l)
|
||||
[] => Some([])
|
||||
None::_ => None
|
||||
Some(x)::t => switch(seq_options(t))
|
||||
None => None
|
||||
Some(st) => Some(x::st)
|
||||
|
||||
|
||||
/** Choose `Some` out of two if possible
|
||||
*/
|
||||
@@ -91,4 +100,3 @@ namespace Option =
|
||||
[] => None
|
||||
None::t => choose_first(t)
|
||||
Some(x)::_ => Some(x)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,117 @@
|
||||
include "List.aes"
|
||||
namespace String =
|
||||
// Computes the SHA3/Keccak hash of the string
|
||||
function sha3(s : string) : hash = StringInternal.sha3(s)
|
||||
// Computes the SHA256 hash of the string.
|
||||
function sha256(s : string) : hash = StringInternal.sha256(s)
|
||||
// Computes the Blake2B hash of the string.
|
||||
function blake2b(s : string) : hash = StringInternal.blake2b(s)
|
||||
|
||||
// The length of a string - equivalent to List.lenght(to_list(s))
|
||||
function length(s : string) : int = StringInternal.length(s)
|
||||
// Concatenates `s1` and `s2`.
|
||||
function concat(s1 : string, s2 : string) : string = StringInternal.concat(s1, s2)
|
||||
// Concatenates a list of strings.
|
||||
function
|
||||
concats : (list(string)) => string
|
||||
concats([]) = ""
|
||||
concats(s :: ss) = List.foldl(StringInternal.concat, s, ss)
|
||||
|
||||
// Converts a `string` to a list of `char` - the code points are normalized, but
|
||||
// composite characters are possibly converted to multiple `char`s.
|
||||
function from_list(cs : list(char)) : string = StringInternal.from_list(cs)
|
||||
// Converts a list of characters into a normalized UTF-8 string.
|
||||
function to_list(s : string) : list(char) = StringInternal.to_list(s)
|
||||
|
||||
// Converts a string to lowercase.
|
||||
function to_lower(s : string) = StringInternal.to_lower(s)
|
||||
// Converts a string to uppercase.
|
||||
function to_upper(s : string) = StringInternal.to_upper(s)
|
||||
|
||||
// Splits a string at (zero-based) index `ix`.
|
||||
function split(i : int, s : string) : string * string =
|
||||
let cs = StringInternal.to_list(s)
|
||||
(StringInternal.from_list(List.take(i, cs)), StringInternal.from_list(List.drop(i, cs)))
|
||||
|
||||
// Returns the character/codepoint at (zero-based) index `ix`.
|
||||
function at(ix : int, s : string) =
|
||||
switch(List.drop(ix, StringInternal.to_list(s)))
|
||||
[] => None
|
||||
x :: _ => Some(x)
|
||||
|
||||
// Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring
|
||||
// of `str` starting at position `ix`, otherwise returns `None`.
|
||||
function contains(str : string, substr : string) : option(int) =
|
||||
if(substr == "") Some(0)
|
||||
else
|
||||
contains_(0, StringInternal.to_list(str), StringInternal.to_list(substr))
|
||||
|
||||
// Splits `s` into tokens, `pat` is the divider of tokens.
|
||||
function tokens(s : string, pat : string) =
|
||||
require(pat != "", "String.tokens: empty pattern")
|
||||
tokens_(StringInternal.to_list(pat), StringInternal.to_list(s), [])
|
||||
|
||||
// Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string
|
||||
// into an integer. If the string doesn't contain a valid number `None` is returned.
|
||||
function to_int(str : string) : option(int) =
|
||||
let lst = StringInternal.to_list(str)
|
||||
switch(is_prefix(['-'], lst))
|
||||
None => to_int_pos(lst)
|
||||
Some(s) => switch(to_int_pos(s))
|
||||
None => None
|
||||
Some(x) => Some(-x)
|
||||
|
||||
// Private helper functions below
|
||||
private function to_int_pos(chs : list(char)) =
|
||||
switch(is_prefix(['0', 'x'], chs))
|
||||
None =>
|
||||
to_int_(chs, ch_to_int_10, 0, 10)
|
||||
Some(str) =>
|
||||
to_int_(str, ch_to_int_16, 0, 16)
|
||||
|
||||
private function
|
||||
tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))]
|
||||
tokens_(pat, str, acc) =
|
||||
switch(is_prefix(pat, str))
|
||||
Some(str') =>
|
||||
StringInternal.from_list(List.reverse(acc)) :: tokens_(pat, str', [])
|
||||
None =>
|
||||
let c :: cs = str
|
||||
tokens_(pat, cs, c :: acc)
|
||||
|
||||
private function
|
||||
contains_(_, [], _) = None
|
||||
contains_(ix, str, substr) =
|
||||
switch(is_prefix(substr, str))
|
||||
None =>
|
||||
let _ :: tailstr = str
|
||||
contains_(ix + 1, tailstr, substr)
|
||||
Some(_) =>
|
||||
Some(ix)
|
||||
|
||||
private function
|
||||
is_prefix([], ys) = Some(ys)
|
||||
is_prefix(_, []) = None
|
||||
is_prefix(x :: xs, y :: ys) =
|
||||
if(x == y) is_prefix(xs, ys)
|
||||
else None
|
||||
|
||||
private function
|
||||
to_int_([], _, x, _) = Some(x)
|
||||
to_int_(i :: is, value, x, b) =
|
||||
switch(value(i))
|
||||
None => None
|
||||
Some(n) => to_int_(is, value, x * b + n, b)
|
||||
|
||||
private function ch_to_int_10(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
else None
|
||||
|
||||
private function ch_to_int_16(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
elif(c >= 65 && c =< 70) Some(c - 55)
|
||||
elif(c >= 97 && c =< 102) Some(c - 87)
|
||||
else None
|
||||
|
||||
+3
-4
@@ -2,11 +2,10 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
{tag, "2.8.0"}}}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
|
||||
]}.
|
||||
|
||||
{dialyzer, [
|
||||
@@ -15,7 +14,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "4.3.0"},
|
||||
{relx, [{release, {aesophia, "7.1.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+8
-5
@@ -1,11 +1,11 @@
|
||||
{"1.1.0",
|
||||
{"1.2.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
|
||||
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
|
||||
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
|
||||
1},
|
||||
{<<"base58">>,
|
||||
{git,"https://github.com/aeternity/erl-base58.git",
|
||||
@@ -14,7 +14,7 @@
|
||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||
{<<"enacl">>,
|
||||
{git,"https://github.com/aeternity/enacl.git",
|
||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
||||
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
|
||||
2},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||
{<<"jsx">>,
|
||||
@@ -24,5 +24,8 @@
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
|
||||
{pkg_hash_ext,[
|
||||
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
|
||||
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
|
||||
].
|
||||
|
||||
+37
-20
@@ -14,15 +14,21 @@
|
||||
, contract_interface/2
|
||||
, contract_interface/3
|
||||
|
||||
, from_typed_ast/2
|
||||
|
||||
, render_aci_json/1
|
||||
|
||||
, 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().
|
||||
|
||||
-export_type([aci_type/0]).
|
||||
|
||||
%% External API
|
||||
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
|
||||
file(Type, File) ->
|
||||
@@ -64,20 +70,20 @@ 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]),
|
||||
%% io:format("~p\n", [TypedAst]),
|
||||
JArray = [ encode_contract(C) || C <- TypedAst ],
|
||||
|
||||
case Type of
|
||||
json -> {ok, JArray};
|
||||
string -> do_render_aci_json(JArray)
|
||||
end
|
||||
{TypedAst, _, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
from_typed_ast(Type, TypedAst)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
||||
from_typed_ast(Type, TypedAst) ->
|
||||
JArray = [ encode_contract(C) || C <- TypedAst ],
|
||||
case Type of
|
||||
json -> {ok, JArray};
|
||||
string -> do_render_aci_json(JArray)
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
C0 = #{name => encode_name(Name)},
|
||||
|
||||
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
||||
@@ -85,7 +91,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|
||||
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
|
||||
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
|
||||
|
||||
C1 = C0#{type_defs => Tdefs},
|
||||
C1 = C0#{typedefs => Tdefs},
|
||||
|
||||
C2 = case Es of
|
||||
[] -> C1;
|
||||
@@ -101,11 +107,11 @@ 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),
|
||||
type_defs => Tdefs}}.
|
||||
typedefs => Tdefs}}.
|
||||
|
||||
%% Encode a function definition. Currently we are only interested in
|
||||
%% the interface and type.
|
||||
@@ -226,15 +232,21 @@ 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,
|
||||
typedefs := 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 /= [] ->
|
||||
decode_contract(#{namespace := #{name := Name, typedefs := Ts}}) when Ts /= [] ->
|
||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts)];
|
||||
decode_contract(_) -> [].
|
||||
@@ -242,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) ->
|
||||
@@ -324,12 +336,17 @@ 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) ->
|
||||
[ D || D <- Decls, is_fun(D)].
|
||||
|
||||
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
|
||||
contract_types({namespace, _, _, Decls}) ->
|
||||
[ D || D <- Decls, is_type(D) ];
|
||||
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_type(D) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
-module(aeso_ast).
|
||||
|
||||
-export([int/2,
|
||||
line/1,
|
||||
pp/1,
|
||||
pp_typed/1,
|
||||
symbol/2,
|
||||
symbol_name/1
|
||||
]).
|
||||
|
||||
|
||||
symbol(Line, Chars) -> {symbol, Line, Chars}.
|
||||
int(Line, Int) -> {'Int', Line, Int}.
|
||||
|
||||
line({symbol, Line, _}) -> Line.
|
||||
|
||||
symbol_name({symbol, _, Name}) -> Name.
|
||||
|
||||
pp(Ast) ->
|
||||
String = prettypr:format(aeso_pretty:decls(Ast, [])),
|
||||
io:format("Ast:\n~s\n", [String]).
|
||||
|
||||
pp_typed(TypedAst) ->
|
||||
%% io:format("Typed tree:\n~p\n",[TypedAst]),
|
||||
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
|
||||
io:format("Type ast:\n~s\n",[String]).
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
-module(aeso_ast_code_analysis).
|
||||
|
||||
|
||||
all_warnings() ->
|
||||
[ warn_unused_includes
|
||||
, warn_unused_stateful
|
||||
, warn_unused_variables
|
||||
, warn_unused_typedefs
|
||||
, warn_unused_return_value
|
||||
, warn_unused_functions
|
||||
, warn_shadowing
|
||||
, warn_division_by_zero
|
||||
, warn_negative_spend ].
|
||||
|
||||
|
||||
when_warning(Warn, Do) ->
|
||||
case lists:member(Warn, all_warnings()) of
|
||||
false ->
|
||||
create_type_errors(),
|
||||
type_error({unknown_warning, Warn}),
|
||||
destroy_and_report_type_errors(global_env());
|
||||
true ->
|
||||
case ets_tab_exists(warnings) of
|
||||
true ->
|
||||
IsEnabled = get_option(Warn, false),
|
||||
IsAll = get_option(warn_all, false) andalso lists:member(Warn, all_warnings()),
|
||||
if
|
||||
IsEnabled orelse IsAll -> Do();
|
||||
true -> ok
|
||||
end;
|
||||
false ->
|
||||
ok
|
||||
end
|
||||
end.
|
||||
|
||||
%% Warnings (Unused includes)
|
||||
|
||||
potential_unused_include(Ann, SrcFile) ->
|
||||
IsIncluded = aeso_syntax:get_ann(include_type, Ann, none) =/= none,
|
||||
case IsIncluded of
|
||||
false -> ok;
|
||||
true ->
|
||||
case aeso_syntax:get_ann(file, Ann, no_file) of
|
||||
no_file -> ok;
|
||||
File -> ets_insert(warnings, {unused_include, File, SrcFile})
|
||||
end
|
||||
end.
|
||||
|
||||
used_include(Ann) ->
|
||||
case aeso_syntax:get_ann(file, Ann, no_file) of
|
||||
no_file -> ok;
|
||||
File -> ets_match_delete(warnings, {unused_include, File, '_'})
|
||||
end.
|
||||
|
||||
%% Warnings (Unused stateful)
|
||||
|
||||
potential_unused_stateful(Ann, Fun) ->
|
||||
case aeso_syntax:get_ann(stateful, Ann, false) of
|
||||
false -> ok;
|
||||
true -> ets_insert(warnings, {unused_stateful, Ann, Fun})
|
||||
end.
|
||||
|
||||
used_stateful(Fun) ->
|
||||
ets_match_delete(warnings, {unused_stateful, '_', Fun}).
|
||||
|
||||
%% Warnings (Unused type defs)
|
||||
|
||||
potential_unused_typedefs(Namespace, TypeDefs) ->
|
||||
lists:map(fun({type_def, Ann, Id, Args, _}) ->
|
||||
ets_insert(warnings, {unused_typedef, Ann, Namespace ++ qname(Id), length(Args)}) end, TypeDefs).
|
||||
|
||||
used_typedef(TypeAliasId, Arity) ->
|
||||
ets_match_delete(warnings, {unused_typedef, '_', qname(TypeAliasId), Arity}).
|
||||
|
||||
%% Warnings (Unused variables)
|
||||
|
||||
potential_unused_variables(Namespace, Fun, Vars0) ->
|
||||
Vars = [ Var || Var = {id, _, VarName} <- Vars0, VarName /= "_" ],
|
||||
lists:map(fun({id, Ann, VarName}) ->
|
||||
ets_insert(warnings, {unused_variable, Ann, Namespace, Fun, VarName}) end, Vars).
|
||||
|
||||
used_variable(Namespace, Fun, [VarName]) ->
|
||||
ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
|
||||
used_variable(_, _, _) -> ok.
|
||||
|
||||
%% Warnings (Unused return value)
|
||||
|
||||
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
|
||||
ets_insert(warnings, {unused_return_value, Ann});
|
||||
potential_unused_return_value(_) -> ok.
|
||||
|
||||
%% Warnings (Unused functions)
|
||||
|
||||
create_unused_functions() ->
|
||||
ets_new(function_calls, [bag]),
|
||||
ets_new(all_functions, [set]).
|
||||
|
||||
register_function_call(Caller, Callee) ->
|
||||
ets_insert(function_calls, {Caller, Callee}).
|
||||
|
||||
potential_unused_function(#env{ what = namespace }, Ann, FunQName, FunId) ->
|
||||
ets_insert(all_functions, {Ann, FunQName, FunId, not aeso_syntax:get_ann(private, Ann, false)});
|
||||
potential_unused_function(_Env, Ann, FunQName, FunId) ->
|
||||
ets_insert(all_functions, {Ann, FunQName, FunId, aeso_syntax:get_ann(entrypoint, Ann, false)}).
|
||||
|
||||
remove_used_funs(All) ->
|
||||
{Used, Unused} = lists:partition(fun({_, _, _, IsUsed}) -> IsUsed end, All),
|
||||
CallsByUsed = lists:flatmap(fun({_, F, _, _}) -> ets_lookup(function_calls, F) end, Used),
|
||||
CalledFuns = sets:from_list(lists:map(fun({_, Callee}) -> Callee end, CallsByUsed)),
|
||||
MarkUsedFun = fun(Fun, Acc) ->
|
||||
case lists:keyfind(Fun, 2, Acc) of
|
||||
false -> Acc;
|
||||
T -> lists:keyreplace(Fun, 2, Acc, setelement(4, T, true))
|
||||
end
|
||||
end,
|
||||
NewUnused = sets:fold(MarkUsedFun, Unused, CalledFuns),
|
||||
case lists:keyfind(true, 4, NewUnused) of
|
||||
false -> NewUnused;
|
||||
_ -> remove_used_funs(NewUnused)
|
||||
end.
|
||||
|
||||
destroy_and_report_unused_functions() ->
|
||||
AllFuns = ets_tab2list(all_functions),
|
||||
lists:map(fun({Ann, _, FunId, _}) -> ets_insert(warnings, {unused_function, Ann, name(FunId)}) end,
|
||||
remove_used_funs(AllFuns)),
|
||||
ets_delete(all_functions),
|
||||
ets_delete(function_calls).
|
||||
|
||||
%% Warnings (Shadowing)
|
||||
|
||||
warn_potential_shadowing(_, "_", _) -> ok;
|
||||
warn_potential_shadowing(Ann, Name, Vars) ->
|
||||
case proplists:get_value(Name, Vars, false) of
|
||||
false -> ok;
|
||||
{AnnOld, _} -> ets_insert(warnings, {shadowing, Ann, Name, AnnOld})
|
||||
end.
|
||||
|
||||
%% Warnings (Division by zero)
|
||||
|
||||
warn_potential_division_by_zero(Ann, Op, Args) ->
|
||||
case {Op, Args} of
|
||||
{{'/', _}, [_, {int, _, 0}]} -> ets_insert(warnings, {division_by_zero, Ann});
|
||||
_ -> ok
|
||||
end.
|
||||
|
||||
%% Warnings (Negative spends)
|
||||
|
||||
warn_potential_negative_spend(Ann, Fun, Args) ->
|
||||
case {Fun, Args} of
|
||||
{ {typed, _, {qid, _, ["Chain", "spend"]}, _}
|
||||
, [_, {typed, _, {app, _, {'-', _}, [{typed, _, {int, _, X}, _}]}, _}]} when X > 0 ->
|
||||
ets_insert(warnings, {negative_spend, Ann});
|
||||
_ -> ok
|
||||
end.
|
||||
File diff suppressed because it is too large
Load Diff
+332
-186
@@ -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().
|
||||
@@ -36,7 +38,14 @@
|
||||
bits_intersection | bits_union | bits_difference |
|
||||
contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 |
|
||||
crypto_sha3 | crypto_sha256 | crypto_blake2b |
|
||||
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
|
||||
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 |
|
||||
mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid |
|
||||
mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul |
|
||||
mcl_bls12_381_g2_neg | mcl_bls12_381_g2_norm | mcl_bls12_381_g2_valid |
|
||||
mcl_bls12_381_g2_is_zero | mcl_bls12_381_g2_add | mcl_bls12_381_g2_mul |
|
||||
mcl_bls12_381_gt_inv | mcl_bls12_381_gt_add | mcl_bls12_381_gt_mul | mcl_bls12_381_gt_pow |
|
||||
mcl_bls12_381_gt_is_one | mcl_bls12_381_pairing | mcl_bls12_381_miller_loop | mcl_bls12_381_final_exp |
|
||||
mcl_bls12_381_int_to_fr | mcl_bls12_381_int_to_fp | mcl_bls12_381_fr_to_int | mcl_bls12_381_fp_to_int.
|
||||
|
||||
-type flit() :: {int, integer()}
|
||||
| {string, binary()}
|
||||
@@ -46,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()}
|
||||
@@ -86,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
|
||||
@@ -129,24 +140,28 @@
|
||||
-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() },
|
||||
saved_fresh_names => #{ var_name() => var_name() }
|
||||
}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
@@ -154,58 +169,111 @@
|
||||
|
||||
%% 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) ->
|
||||
init_fresh_names(Options),
|
||||
{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)
|
||||
)},
|
||||
Env3 =
|
||||
case proplists:get_value(debug_info, Options, false) of
|
||||
true -> Env2#{ saved_fresh_names => get(saved_fresh_names) };
|
||||
false -> Env2
|
||||
end,
|
||||
clear_fresh_names(Options),
|
||||
{Env3, FCode2}.
|
||||
|
||||
optimize(FCode1, Options) ->
|
||||
Verbose = lists:member(pp_fcode, Options),
|
||||
init_fresh_names(),
|
||||
FCode1 = to_fcode(init_env(Options), Code),
|
||||
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
|
||||
FCode2 = optimize_fcode(FCode1),
|
||||
FCode2 = optimize_fcode(FCode1, Options),
|
||||
[ 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 ------------------------------------------------------------
|
||||
|
||||
-spec init_env([option()]) -> env().
|
||||
init_env(Options) ->
|
||||
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
#{ 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] },
|
||||
["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] }
|
||||
["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] },
|
||||
["AENS", "AccountPt"] => #con_tag{ tag = 0, arities = [1, 1, 1, 1] },
|
||||
["AENS", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1] },
|
||||
["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] },
|
||||
["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] },
|
||||
["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] },
|
||||
["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] },
|
||||
["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] },
|
||||
["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities },
|
||||
["Chain", "OracleRegisterTx"] => #con_tag{ tag = 1, arities = ChainTxArities },
|
||||
["Chain", "OracleQueryTx"] => #con_tag{ tag = 2, arities = ChainTxArities },
|
||||
["Chain", "OracleResponseTx"] => #con_tag{ tag = 3, arities = ChainTxArities },
|
||||
["Chain", "OracleExtendTx"] => #con_tag{ tag = 4, arities = ChainTxArities },
|
||||
["Chain", "NamePreclaimTx"] => #con_tag{ tag = 5, arities = ChainTxArities },
|
||||
["Chain", "NameClaimTx"] => #con_tag{ tag = 6, arities = ChainTxArities },
|
||||
["Chain", "NameUpdateTx"] => #con_tag{ tag = 7, arities = ChainTxArities },
|
||||
["Chain", "NameRevokeTx"] => #con_tag{ tag = 8, arities = ChainTxArities },
|
||||
["Chain", "NameTransferTx"] => #con_tag{ tag = 9, arities = ChainTxArities },
|
||||
["Chain", "ChannelCreateTx"] => #con_tag{ tag = 10, arities = ChainTxArities },
|
||||
["Chain", "ChannelDepositTx"] => #con_tag{ tag = 11, arities = ChainTxArities },
|
||||
["Chain", "ChannelWithdrawTx"] => #con_tag{ tag = 12, arities = ChainTxArities },
|
||||
["Chain", "ChannelForceProgressTx"] => #con_tag{ tag = 13, arities = ChainTxArities },
|
||||
["Chain", "ChannelCloseMutualTx"] => #con_tag{ tag = 14, arities = ChainTxArities },
|
||||
["Chain", "ChannelCloseSoloTx"] => #con_tag{ tag = 15, arities = ChainTxArities },
|
||||
["Chain", "ChannelSlashTx"] => #con_tag{ tag = 16, arities = ChainTxArities },
|
||||
["Chain", "ChannelSettleTx"] => #con_tag{ tag = 17, arities = ChainTxArities },
|
||||
["Chain", "ChannelSnapshotSoloTx"] => #con_tag{ tag = 18, arities = ChainTxArities },
|
||||
["Chain", "ContractCreateTx"] => #con_tag{ tag = 19, arities = ChainTxArities },
|
||||
["Chain", "ContractCallTx"] => #con_tag{ tag = 20, arities = ChainTxArities },
|
||||
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
|
||||
},
|
||||
options => Options,
|
||||
functions => #{} }.
|
||||
functions => #{}
|
||||
}.
|
||||
|
||||
-spec builtins() -> builtins().
|
||||
builtins() ->
|
||||
MkName = fun(NS, Fun) ->
|
||||
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
|
||||
end,
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
|
||||
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
|
||||
{["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}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
|
||||
{["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
|
||||
{"respond", 4}, {"extend", 3}, {"get_answer", 2},
|
||||
{"check", 1}, {"check_query", 2}]},
|
||||
{["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4},
|
||||
{"revoke", 3}]},
|
||||
{"revoke", 3}, {"update", 6}, {"lookup", 1}]},
|
||||
{["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2},
|
||||
{"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]},
|
||||
{["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3},
|
||||
{"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2},
|
||||
{"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
|
||||
{["Auth"], [{"tx_hash", none}]},
|
||||
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
|
||||
{["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2},
|
||||
{"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2},
|
||||
{"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1},
|
||||
{"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1},
|
||||
{"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]},
|
||||
{["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1},
|
||||
{"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]},
|
||||
{["Char"], [{"to_int", 1}, {"from_int", 1}]},
|
||||
{["Auth"], [{"tx_hash", none}, {"tx", none}]},
|
||||
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
|
||||
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
|
||||
{["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
|
||||
@@ -224,20 +292,32 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}).
|
||||
|
||||
-spec init_type_env() -> type_env().
|
||||
init_type_env() ->
|
||||
#{ ["int"] => ?type(integer),
|
||||
["bool"] => ?type(boolean),
|
||||
["bits"] => ?type(bits),
|
||||
["char"] => ?type(integer),
|
||||
["string"] => ?type(string),
|
||||
["address"] => ?type(address),
|
||||
["hash"] => ?type(hash),
|
||||
["signature"] => ?type(signature),
|
||||
["oracle"] => ?type(Q, R, {oracle, Q, R}),
|
||||
["oracle_query"] => ?type(_, _, oracle_query),
|
||||
["list"] => ?type(T, {list, T}),
|
||||
["map"] => ?type(K, V, {map, K, V}),
|
||||
["option"] => ?type(T, {variant, [[], [T]]}),
|
||||
["Chain", "ttl"] => ?type({variant, [[integer], [integer]]})
|
||||
BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string],
|
||||
[{bytes, 32}], [{bytes, 32}], [address, {bytes, 32}], [address],
|
||||
[address, integer], [address, integer], [address],
|
||||
[address], [address], [address], [address], [address],
|
||||
[integer], [address, integer], []]},
|
||||
#{ ["int"] => ?type(integer),
|
||||
["bool"] => ?type(boolean),
|
||||
["bits"] => ?type(bits),
|
||||
["char"] => ?type(integer),
|
||||
["string"] => ?type(string),
|
||||
["address"] => ?type(address),
|
||||
["hash"] => ?type(hash),
|
||||
["signature"] => ?type(signature),
|
||||
["oracle"] => ?type(Q, R, {oracle, Q, R}),
|
||||
["oracle_query"] => ?type(_, _, oracle_query), %% TODO: not in Fate
|
||||
["list"] => ?type(T, {list, T}),
|
||||
["map"] => ?type(K, V, {map, K, V}),
|
||||
["option"] => ?type(T, {variant, [[], [T]]}),
|
||||
["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}),
|
||||
["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}),
|
||||
["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}),
|
||||
["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}),
|
||||
["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}),
|
||||
["Chain", "base_tx"] => ?type(BaseTx),
|
||||
["MCL_BLS12_381", "fr"] => ?type({bytes, 32}),
|
||||
["MCL_BLS12_381", "fp"] => ?type({bytes, 48})
|
||||
}.
|
||||
|
||||
is_no_code(Env) ->
|
||||
@@ -251,31 +331,43 @@ 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, _, Name}, _Impls, 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,
|
||||
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, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
|
||||
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
|
||||
to_fcode(Env1, Code).
|
||||
@@ -285,28 +377,19 @@ 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, _}) ->
|
||||
case is_no_code(Env) of
|
||||
false -> fcode_error({missing_definition, Id});
|
||||
true -> Env
|
||||
end;
|
||||
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, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
|
||||
Attrs = get_attributes(Ann),
|
||||
FName = lookup_fun(Env, qname(Env, Name)),
|
||||
FArgs = args_to_fcode(Env, Args),
|
||||
FRet = type_to_fcode(Env, Ret),
|
||||
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
|
||||
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|
||||
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
|
||||
Def = #{ attrs => Attrs,
|
||||
args => FArgs,
|
||||
return => FRet,
|
||||
@@ -315,8 +398,7 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Ar
|
||||
Env#{ functions := NewFuns }.
|
||||
|
||||
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
|
||||
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
|
||||
check_state_and_event_types(Env, Id, Xs),
|
||||
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
|
||||
Q = qname(Env, Name),
|
||||
FDef = fun(Args) when length(Args) == length(Xs) ->
|
||||
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
|
||||
@@ -354,7 +436,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
|
||||
@@ -380,14 +462,6 @@ compute_state_layout(R, [H | T]) ->
|
||||
compute_state_layout(R, _) ->
|
||||
{R + 1, {reg, R}}.
|
||||
|
||||
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
|
||||
case Id of
|
||||
{id, _, "state"} -> fcode_error({parameterized_state, Id});
|
||||
{id, _, "event"} -> fcode_error({parameterized_event, Id});
|
||||
_ -> ok
|
||||
end;
|
||||
check_state_and_event_types(_, _, _) -> ok.
|
||||
|
||||
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
|
||||
type_to_fcode(Env, Type) ->
|
||||
type_to_fcode(Env, #{}, Type).
|
||||
@@ -407,10 +481,14 @@ type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
|
||||
{bytes, N};
|
||||
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],
|
||||
{function, FNamed ++ FArgs, type_to_fcode(Env, Sub, Res)};
|
||||
type_to_fcode(Env, Sub, {if_t, _, _, _, Else}) ->
|
||||
type_to_fcode(Env, Sub, Else); %% Hacky: this is only for remote calls, in which case we want the unprotected type
|
||||
type_to_fcode(_Env, _Sub, Type) ->
|
||||
error({todo, Type}).
|
||||
|
||||
@@ -462,7 +540,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
|
||||
|
||||
%% Variables
|
||||
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
|
||||
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
||||
expr_to_fcode(Env, Type, {qid, _, X}) ->
|
||||
case resolve_var(Env, X) of
|
||||
{builtin_u, B, Ar} when B =:= oracle_query;
|
||||
B =:= oracle_get_question;
|
||||
@@ -473,13 +551,11 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
|
||||
B =:= oracle_check_query ->
|
||||
OType = get_oracle_type(B, Type),
|
||||
{oracle, QType, RType} = type_to_fcode(Env, OType),
|
||||
validate_oracle_type(Ann, OType, QType, RType),
|
||||
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
|
||||
{builtin_u, B, Ar, TypeArgs};
|
||||
{builtin_u, B = aens_resolve, Ar} ->
|
||||
{fun_t, _, _, _, ResType} = Type,
|
||||
AensType = type_to_fcode(Env, ResType),
|
||||
validate_aens_resolve_type(Ann, ResType, AensType),
|
||||
TypeArgs = [{lit, {typerep, AensType}}],
|
||||
{builtin_u, B, Ar, TypeArgs};
|
||||
{builtin_u, B = bytes_split, Ar} ->
|
||||
@@ -570,8 +646,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]}) ->
|
||||
@@ -591,9 +667,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);
|
||||
@@ -611,8 +687,11 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
|
||||
expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' ->
|
||||
Tree = expr_to_decision_tree(Env, Expr),
|
||||
decision_tree_to_fcode(Tree);
|
||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
|
||||
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
|
||||
expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
|
||||
case Op of
|
||||
'|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]});
|
||||
_ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}
|
||||
end;
|
||||
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
|
||||
case Op of
|
||||
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
|
||||
@@ -620,14 +699,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, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, 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
|
||||
@@ -689,6 +785,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}.
|
||||
@@ -705,58 +808,11 @@ get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
|
||||
get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
|
||||
get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
|
||||
|
||||
validate_oracle_type(Ann, Type, QType, RType) ->
|
||||
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
|
||||
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
|
||||
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
|
||||
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
|
||||
ok.
|
||||
|
||||
validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
|
||||
case FType of
|
||||
string -> ok;
|
||||
address -> ok;
|
||||
contract -> ok;
|
||||
{oracle, _, _} -> ok;
|
||||
oracle_query -> ok;
|
||||
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
|
||||
end.
|
||||
|
||||
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
|
||||
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|
||||
|| {{typed, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
|
||||
[ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|
||||
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
|
||||
%% rather than being returned.
|
||||
ok.
|
||||
|
||||
ensure_monomorphic(Type, Err) ->
|
||||
case is_monomorphic(Type) of
|
||||
true -> ok;
|
||||
false -> fcode_error(Err)
|
||||
end.
|
||||
|
||||
ensure_first_order(Type, Err) ->
|
||||
case is_first_order(Type) of
|
||||
true -> ok;
|
||||
false -> fcode_error(Err)
|
||||
end.
|
||||
|
||||
is_monomorphic({tvar, _}) -> false;
|
||||
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
|
||||
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
|
||||
is_monomorphic(_) -> true.
|
||||
|
||||
is_first_order({function, _, _}) -> false;
|
||||
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
|
||||
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
|
||||
is_first_order(_) -> true.
|
||||
|
||||
%% -- Pattern matching --
|
||||
|
||||
-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()).
|
||||
@@ -767,7 +823,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().
|
||||
@@ -867,6 +959,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}.
|
||||
@@ -877,6 +971,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}) ->
|
||||
@@ -892,7 +987,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}.
|
||||
@@ -932,6 +1027,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}).
|
||||
@@ -968,8 +1065,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
|
||||
@@ -985,12 +1082,21 @@ stmts_to_fcode(Env, [Expr | Stmts]) ->
|
||||
|
||||
op_builtins() ->
|
||||
[map_from_list, map_to_list, map_delete, map_member, map_size,
|
||||
string_length, string_concat, string_sha3, string_sha256, string_blake2b,
|
||||
stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list,
|
||||
stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b,
|
||||
char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper,
|
||||
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
|
||||
bits_difference, int_to_str, address_to_str, crypto_verify_sig,
|
||||
address_to_contract,
|
||||
crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
|
||||
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
|
||||
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1,
|
||||
mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid,
|
||||
mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul,
|
||||
mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid,
|
||||
mcl_bls12_381_g2_is_zero, mcl_bls12_381_g2_add, mcl_bls12_381_g2_mul,
|
||||
mcl_bls12_381_gt_inv, mcl_bls12_381_gt_add, mcl_bls12_381_gt_mul, mcl_bls12_381_gt_pow,
|
||||
mcl_bls12_381_gt_is_one, mcl_bls12_381_pairing, mcl_bls12_381_miller_loop, mcl_bls12_381_final_exp,
|
||||
mcl_bls12_381_int_to_fr, mcl_bls12_381_int_to_fp, mcl_bls12_381_fr_to_int, mcl_bls12_381_fp_to_int
|
||||
].
|
||||
|
||||
set_state({reg, R}, Val) ->
|
||||
@@ -1034,11 +1140,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
|
||||
|
||||
%% -- Init function --
|
||||
|
||||
add_init_function(Env, Main, StateType, Funs0) ->
|
||||
add_init_function(Env, Funs0) ->
|
||||
case is_no_code(Env) of
|
||||
true -> Funs0;
|
||||
false ->
|
||||
Funs = add_default_init_function(Env, Main, StateType, Funs0),
|
||||
Funs = add_default_init_function(Env, Funs0),
|
||||
InitName = {entrypoint, <<"init">>},
|
||||
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
|
||||
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
|
||||
@@ -1046,16 +1152,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
|
||||
Funs1
|
||||
end.
|
||||
|
||||
add_default_init_function(_Env, Main, StateType, Funs) ->
|
||||
add_default_init_function(_Env, Funs) ->
|
||||
InitName = {entrypoint, <<"init">>},
|
||||
case maps:get(InitName, Funs, none) of
|
||||
%% Only add default init function if state is unit.
|
||||
none when StateType == {tuple, []} ->
|
||||
none ->
|
||||
Funs#{ InitName => #{attrs => [],
|
||||
args => [],
|
||||
return => {tuple, []},
|
||||
body => {tuple, []}} };
|
||||
none -> fcode_error({missing_init_function, Main});
|
||||
_ -> Funs
|
||||
end.
|
||||
|
||||
@@ -1151,8 +1255,8 @@ lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExp
|
||||
lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) ->
|
||||
FVs = free_vars(Ct),
|
||||
Ct1 = lambda_lift_expr(Layout, Ct),
|
||||
GasAndValueArgs = 2,
|
||||
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ],
|
||||
NamedArgCount = 3,
|
||||
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ],
|
||||
Args = [{var, X} || X <- Xs],
|
||||
make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args});
|
||||
lambda_lift_expr(Layout, Expr) ->
|
||||
@@ -1189,20 +1293,28 @@ lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As].
|
||||
%% - Constant propagation
|
||||
%% - Inlining
|
||||
|
||||
-spec optimize_fcode(fcode()) -> fcode().
|
||||
optimize_fcode(Code = #{ functions := Funs }) ->
|
||||
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) },
|
||||
-spec optimize_fcode(fcode(), [option()]) -> fcode().
|
||||
optimize_fcode(Code = #{ functions := Funs }, Options) ->
|
||||
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def, Options) end, Funs) },
|
||||
eliminate_dead_code(Code1).
|
||||
|
||||
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def().
|
||||
optimize_fun(Fcode, Fun, Def = #{ body := Body }) ->
|
||||
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]),
|
||||
Def#{ body := drop_unused_lets(
|
||||
simplifier(
|
||||
let_floating(
|
||||
bind_subexpressions(
|
||||
inline_local_functions(
|
||||
inliner(Fcode, Fun, Body)))))) }.
|
||||
-spec optimize_fun(fcode(), fun_name(), fun_def(), [option()]) -> fun_def().
|
||||
optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) ->
|
||||
Inliner = proplists:get_value(optimize_inliner, Options, true),
|
||||
InlineLocalFunctions = proplists:get_value(optimize_inline_local_functions, Options, true),
|
||||
BindSubexpressions = proplists:get_value(optimize_bind_subexpressions, Options, true),
|
||||
LetFloating = proplists:get_value(optimize_let_floating, Options, true),
|
||||
Simplifier = proplists:get_value(optimize_simplifier, Options, true),
|
||||
DropUnusedLets = proplists:get_value(optimize_drop_unused_lets, Options, true),
|
||||
|
||||
Body1 = if Inliner -> inliner (Fcode, Fun, Body0); true -> Body0 end,
|
||||
Body2 = if InlineLocalFunctions -> inline_local_functions(Body1); true -> Body1 end,
|
||||
Body3 = if BindSubexpressions -> bind_subexpressions (Body2); true -> Body2 end,
|
||||
Body4 = if LetFloating -> let_floating (Body3); true -> Body3 end,
|
||||
Body5 = if Simplifier -> simplifier (Body4); true -> Body4 end,
|
||||
Body6 = if DropUnusedLets -> drop_unused_lets (Body5); true -> Body5 end,
|
||||
|
||||
Def#{ body := Body6 }.
|
||||
|
||||
%% --- Inlining ---
|
||||
|
||||
@@ -1413,6 +1525,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) ->
|
||||
@@ -1547,6 +1660,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) ->
|
||||
@@ -1561,7 +1678,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;
|
||||
@@ -1573,7 +1690,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.
|
||||
|
||||
@@ -1617,12 +1734,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
|
||||
{{Fun, Ar}, _} -> {def_u, Fun, Ar}
|
||||
end.
|
||||
|
||||
init_fresh_names() ->
|
||||
init_fresh_names(Options) ->
|
||||
proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(),
|
||||
put('%fresh', 0).
|
||||
|
||||
clear_fresh_names() ->
|
||||
clear_fresh_names(Options) ->
|
||||
proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(),
|
||||
erase('%fresh').
|
||||
|
||||
init_saved_fresh_names() ->
|
||||
put(saved_fresh_names, #{}).
|
||||
|
||||
clear_saved_fresh_names() ->
|
||||
erase(saved_fresh_names).
|
||||
|
||||
-spec fresh_name_save(string()) -> var_name().
|
||||
fresh_name_save(Name) ->
|
||||
Fresh = fresh_name(),
|
||||
case get(saved_fresh_names) of
|
||||
undefined -> ok;
|
||||
Old -> put(saved_fresh_names, Old#{Fresh => Name})
|
||||
end,
|
||||
Fresh.
|
||||
|
||||
-spec fresh_name() -> var_name().
|
||||
fresh_name() -> fresh_name("%").
|
||||
|
||||
@@ -1644,6 +1778,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()].
|
||||
@@ -1750,7 +1885,7 @@ bottom_up(F, Env, Expr) ->
|
||||
(_) -> true end,
|
||||
case ShouldFreshen(X) of
|
||||
true ->
|
||||
Z = fresh_name(),
|
||||
Z = fresh_name_save(X),
|
||||
Env1 = Env#{ Z => E1 },
|
||||
{'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))};
|
||||
false ->
|
||||
@@ -1864,7 +1999,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]};
|
||||
@@ -1911,7 +2050,9 @@ setnth(I, X, Xs) ->
|
||||
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
|
||||
|
||||
fcode_error(Error) ->
|
||||
aeso_errors:throw(aeso_code_errors:format(Error)).
|
||||
Pos = aeso_errors:pos(0, 0),
|
||||
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Error])),
|
||||
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
|
||||
|
||||
internal_error(Error) ->
|
||||
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
|
||||
@@ -1920,8 +2061,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)).
|
||||
@@ -2038,7 +2182,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})).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,721 @@
|
||||
-module(aeso_ast_types_errors).
|
||||
|
||||
|
||||
cannot_unify(A, B, Cxt, When) ->
|
||||
type_error({cannot_unify, A, B, Cxt, When}).
|
||||
|
||||
|
||||
type_error(Err) ->
|
||||
ets_insert(type_errors, Err).
|
||||
|
||||
|
||||
mk_t_err(Pos, Msg) ->
|
||||
aeso_errors:new(type_error, Pos, lists:flatten(Msg)).
|
||||
mk_t_err(Pos, Msg, Ctxt) ->
|
||||
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
|
||||
|
||||
mk_t_err_from_warn(Warn) ->
|
||||
aeso_warnings:warn_to_err(type_error, Warn).
|
||||
|
||||
mk_error({no_decls, File}) ->
|
||||
Pos = aeso_errors:pos(File, 0, 0),
|
||||
mk_t_err(Pos, "Empty contract");
|
||||
mk_error({mismatched_decl_in_funblock, Name, Decl}) ->
|
||||
Msg = io_lib:format("Mismatch in the function block. Expected implementation/type declaration of ~s function", [Name]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({higher_kinded_typevar, T}) ->
|
||||
Msg = io_lib:format("Type `~s` is a higher kinded type variable "
|
||||
"(takes another type as an argument)", [pp(instantiate(T))]
|
||||
),
|
||||
mk_t_err(pos(T), Msg);
|
||||
mk_error({wrong_type_arguments, X, ArityGiven, ArityReal}) ->
|
||||
Msg = io_lib:format("Arity for ~s doesn't match. Expected ~p, got ~p"
|
||||
, [pp(instantiate(X)), ArityReal, ArityGiven]
|
||||
),
|
||||
mk_t_err(pos(X), Msg);
|
||||
mk_error({unnamed_map_update_with_default, Upd}) ->
|
||||
Msg = "Invalid map update with default",
|
||||
mk_t_err(pos(Upd), Msg);
|
||||
mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) ->
|
||||
Msg = io_lib:format("`~s` was declared with an invalid type `~s`. "
|
||||
"Entrypoints and functions must have functional types"
|
||||
, [pp(Id), pp(instantiate(Type))]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({cannot_unify, A, B, Cxt, When}) ->
|
||||
VarianceContext = case Cxt of
|
||||
none -> "";
|
||||
_ -> io_lib:format(" in a ~p context", [Cxt])
|
||||
end,
|
||||
Msg = io_lib:format("Cannot unify `~s` and `~s`" ++ VarianceContext,
|
||||
[pp(instantiate(A)), pp(instantiate(B))]),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({hole_found, Ann, Type}) ->
|
||||
Msg = io_lib:format("Found a hole of type `~s`", [pp(instantiate(Type))]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unbound_variable, Id}) ->
|
||||
Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]),
|
||||
case Id of
|
||||
{qid, _, ["Chain", "event"]} ->
|
||||
Cxt = "Did you forget to define the event type?",
|
||||
mk_t_err(pos(Id), Msg, Cxt);
|
||||
_ -> mk_t_err(pos(Id), Msg)
|
||||
end;
|
||||
mk_error({undefined_field, Id}) ->
|
||||
Msg = io_lib:format("Unbound field ~s", [pp(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({not_a_record_type, Type, Why}) ->
|
||||
Msg = io_lib:format("Not a record type: `~s`", [pp_type(Type)]),
|
||||
{Pos, Ctxt} = pp_why_record(Why),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({not_a_contract_type, Type, Cxt}) ->
|
||||
Msg =
|
||||
case Type of
|
||||
{tvar, _, _} ->
|
||||
"Unresolved contract type";
|
||||
_ ->
|
||||
io_lib:format("The type `~s` is not a contract type", [pp_type(Type)])
|
||||
end,
|
||||
{Pos, Cxt1} =
|
||||
case Cxt of
|
||||
{var_args, Ann, Fun} ->
|
||||
{pos(Ann),
|
||||
io_lib:format("when calling variadic function `~s`", [pp_expr(Fun)])};
|
||||
{contract_literal, Lit} ->
|
||||
{pos(Lit),
|
||||
io_lib:format("when checking that the contract literal `~s` has the type `~s`",
|
||||
[pp_expr(Lit), pp_type(Type)])};
|
||||
{address_to_contract, Ann} ->
|
||||
{pos(Ann),
|
||||
io_lib:format("when checking that the call to `Address.to_contract` has the type `~s`",
|
||||
[pp_type(Type)])}
|
||||
end,
|
||||
mk_t_err(Pos, Msg, Cxt1);
|
||||
mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||
Msg = io_lib:format("Repeated name~s ~s in the pattern `~s`",
|
||||
[plural("", "s", Nonlinear),
|
||||
string:join(lists:map(fun(F) -> "`" ++ F ++ "`" end, Nonlinear), ", "),
|
||||
pp_expr(Pattern)]),
|
||||
mk_t_err(pos(Pattern), Msg);
|
||||
mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
|
||||
Msg = io_lib:format("Ambiguous record type with field~s ~s could be one of~s",
|
||||
[plural("", "s", Fields),
|
||||
string:join([ "`" ++ pp(F) ++ "`" || {_, F} <- Fields ], ", "),
|
||||
[ ["\n - ", "`" ++ pp(C) ++ "`", " (at ", pp_loc(C), ")"] || C <- Candidates ]]),
|
||||
mk_t_err(pos(First), Msg);
|
||||
mk_error({missing_field, Field, Rec}) ->
|
||||
Msg = io_lib:format("Record type `~s` does not have field `~s`",
|
||||
[pp(Rec), pp(Field)]),
|
||||
mk_t_err(pos(Field), Msg);
|
||||
mk_error({missing_fields, Ann, RecType, Fields}) ->
|
||||
Msg = io_lib:format("The field~s ~s ~s missing when constructing an element of type `~s`",
|
||||
[plural("", "s", Fields),
|
||||
string:join(lists:map(fun(F) -> "`" ++ F ++ "`" end, Fields), ", "),
|
||||
plural("is", "are", Fields), pp(RecType)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
|
||||
Msg = io_lib:format("No record type with field~s ~s",
|
||||
[plural("", "s", Fields),
|
||||
string:join([ "`" ++ pp(F) ++ "`" || {_, F} <- Fields ], ", ")]),
|
||||
mk_t_err(pos(First), Msg);
|
||||
mk_error({recursive_types_not_implemented, Types}) ->
|
||||
S = plural(" is", "s are mutually", Types),
|
||||
Msg = io_lib:format("The following type~s recursive, which is not yet supported:~s",
|
||||
[S, [io_lib:format("\n - `~s` (at ~s)", [pp(T), pp_loc(T)]) || T <- Types]]),
|
||||
mk_t_err(pos(hd(Types)), Msg);
|
||||
mk_error({event_must_be_variant_type, Where}) ->
|
||||
Msg = io_lib:format("The event type must be a variant type", []),
|
||||
mk_t_err(pos(Where), Msg);
|
||||
mk_error({indexed_type_must_be_word, Type, Type}) ->
|
||||
Msg = io_lib:format("The indexed type `~s` is not a word type",
|
||||
[pp_type(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({indexed_type_must_be_word, Type, Type1}) ->
|
||||
Msg = io_lib:format("The indexed type `~s` equals `~s` which is not a word type",
|
||||
[pp_type(Type), pp_type(Type1)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({event_0_to_3_indexed_values, Constr}) ->
|
||||
Msg = io_lib:format("The event constructor `~s` has too many indexed values (max 3)",
|
||||
[name(Constr)]),
|
||||
mk_t_err(pos(Constr), Msg);
|
||||
mk_error({event_0_to_1_string_values, Constr}) ->
|
||||
Msg = io_lib:format("The event constructor `~s` has too many non-indexed values (max 1)",
|
||||
[name(Constr)]),
|
||||
mk_t_err(pos(Constr), Msg);
|
||||
mk_error({repeated_constructor, Cs}) ->
|
||||
Msg = io_lib:format("Variant types must have distinct constructor names~s",
|
||||
[[ io_lib:format("\n`~s` (at ~s)", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]),
|
||||
mk_t_err(pos(element(1, hd(Cs))), Msg);
|
||||
mk_error({bad_named_argument, [], Name}) ->
|
||||
Msg = io_lib:format("Named argument ~s supplied to function expecting no named arguments.",
|
||||
[pp(Name)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({bad_named_argument, Args, Name}) ->
|
||||
Msg = io_lib:format("Named argument `~s` is not one of the expected named arguments~s",
|
||||
[pp(Name),
|
||||
[ io_lib:format("\n - `~s`", [pp_typed("", Arg, Type)])
|
||||
|| {named_arg_t, _, Arg, Type, _} <- Args ]]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({unsolved_named_argument_constraint, #named_argument_constraint{name = Name, type = Type}}) ->
|
||||
Msg = io_lib:format("Named argument ~s supplied to function with unknown named arguments.",
|
||||
[pp_typed("", Name, Type)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({reserved_entrypoint, Name, Def}) ->
|
||||
Msg = io_lib:format("The name '~s' is reserved and cannot be used for a "
|
||||
"top-level contract function.", [Name]),
|
||||
mk_t_err(pos(Def), Msg);
|
||||
mk_error({duplicate_definition, Name, Locs}) ->
|
||||
Msg = io_lib:format("Duplicate definitions of `~s` at~s",
|
||||
[Name, [ ["\n - ", pp_loc(L)] || L <- Locs ]]),
|
||||
mk_t_err(pos(lists:last(Locs)), Msg);
|
||||
mk_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
|
||||
Msg = io_lib:format("The ~p `~s` has the same name as a ~p at ~s",
|
||||
[Kind, pp(Name), OtherKind, pp_loc(L)]),
|
||||
mk_t_err(pos(Name), Msg);
|
||||
mk_error({include, _, {string, Pos, Name}}) ->
|
||||
Msg = io_lib:format("Include of `~s` is not allowed, include only allowed at top level.",
|
||||
[binary_to_list(Name)]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
||||
Msg = io_lib:format("Nested namespaces are not allowed. Namespace `~s` is not defined at top level.",
|
||||
[Name]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({Contract, _Pos, {con, Pos, Name}, _Impls, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
Msg = io_lib:format("Nested contracts are not allowed. Contract `~s` is not defined at top level.",
|
||||
[Name]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({type_decl, _, {id, Pos, Name}, _}) ->
|
||||
Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition",
|
||||
[Name]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({letval, _Pos, {id, Pos, Name}, _Def}) ->
|
||||
Msg = io_lib:format("Toplevel \"let\" definitions are not supported. Value `~s` could be replaced by 0-argument function.",
|
||||
[Name]),
|
||||
mk_t_err(pos(Pos), Msg);
|
||||
mk_error({stateful_not_allowed, Id, Fun}) ->
|
||||
Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.",
|
||||
[pp(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` in a pattern guard.",
|
||||
[pp(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` in the definition of non-stateful function `~s`.",
|
||||
[pp_expr(Value), pp(Fun)]),
|
||||
mk_t_err(pos(Value), Msg);
|
||||
mk_error({init_depends_on_state, Which, [_Init | Chain]}) ->
|
||||
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
|
||||
Msg = io_lib:format("The `init` function should return the initial state as its result and cannot ~s the state, but it calls~s",
|
||||
[if Which == put -> "write"; true -> "read" end,
|
||||
[ io_lib:format("\n - `~s` (at ~s)~s", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|
||||
|| {[_, Fun], Ann} <- Chain]]),
|
||||
mk_t_err(pos(element(2, hd(Chain))), Msg);
|
||||
mk_error({missing_body_for_let, Ann}) ->
|
||||
Msg = io_lib:format("Let binding must be followed by an expression.", []),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({public_modifier_in_contract, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
Msg = io_lib:format("Use `entrypoint` instead of `function` for public function `~s`: `~s`",
|
||||
[pp_expr(element(3, Decl)),
|
||||
prettypr:format(aeso_pretty:decl(Decl1))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({init_must_be_an_entrypoint, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
Msg = io_lib:format("The init function must be an entrypoint: ~s",
|
||||
[prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({init_must_not_be_payable, Decl}) ->
|
||||
Msg = io_lib:format("The init function cannot be payable. "
|
||||
"You don't need the 'payable' annotation to be able to attach "
|
||||
"funds to the create contract transaction.",
|
||||
[]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({proto_must_be_entrypoint, Decl}) ->
|
||||
Decl1 = mk_entrypoint(Decl),
|
||||
Msg = io_lib:format("Use `entrypoint` for declaration of `~s`: `~s`",
|
||||
[pp_expr(element(3, Decl)),
|
||||
prettypr:format(aeso_pretty:decl(Decl1))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({proto_in_namespace, Decl}) ->
|
||||
Msg = io_lib:format("Namespaces cannot contain function prototypes.", []),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({entrypoint_in_namespace, Decl}) ->
|
||||
Msg = io_lib:format("Namespaces cannot contain entrypoints. Use `function` instead.", []),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({private_entrypoint, Decl}) ->
|
||||
Msg = io_lib:format("The entrypoint `~s` cannot be private. Use `function` instead.",
|
||||
[pp_expr(element(3, Decl))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({private_and_public, Decl}) ->
|
||||
Msg = io_lib:format("The function `~s` cannot be both public and private.",
|
||||
[pp_expr(element(3, Decl))]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({contract_has_no_entrypoints, Con}) ->
|
||||
Msg = io_lib:format("The contract `~s` has no entrypoints. Since Sophia version 3.2, public "
|
||||
"contract functions must be declared with the `entrypoint` keyword instead of "
|
||||
"`function`.", [pp_expr(Con)]),
|
||||
mk_t_err(pos(Con), Msg);
|
||||
mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
|
||||
Msg = "Contract interfaces cannot contain defined functions or entrypoints.",
|
||||
Cxt = io_lib:format("Fix: replace the definition of `~s` by a type signature.", [Id]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({unbound_type, Type}) ->
|
||||
Msg = io_lib:format("Unbound type ~s.", [pp_type(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({new_tuple_syntax, Ann, Ts}) ->
|
||||
Msg = io_lib:format("Invalid type `~s`. The syntax of tuple types changed in Sophia version 4.0. Did you mean `~s`",
|
||||
[pp_type({args_t, Ann, Ts}), pp_type({tuple_t, Ann, Ts})]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({map_in_map_key, Ann, KeyType}) ->
|
||||
Msg = io_lib:format("Invalid key type `~s`", [pp_type(KeyType)]),
|
||||
Cxt = "Map keys cannot contain other maps.",
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({cannot_call_init_function, Ann}) ->
|
||||
Msg = "The 'init' function is called exclusively by the create contract transaction "
|
||||
"and cannot be called from the contract code.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({contract_treated_as_namespace, Ann, [Con, Fun] = QName}) ->
|
||||
Msg = io_lib:format("Invalid call to contract entrypoint `~s`.", [string:join(QName, ".")]),
|
||||
Cxt = io_lib:format("It must be called as `c.~s` for some `c : ~s`.", [Fun, Con]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({bad_top_level_decl, Decl}) ->
|
||||
What = case element(1, Decl) of
|
||||
letval -> "function or entrypoint";
|
||||
_ -> "contract or namespace"
|
||||
end,
|
||||
Id = element(3, Decl),
|
||||
Msg = io_lib:format("The definition of '~s' must appear inside a ~s.",
|
||||
[pp_expr(Id), What]),
|
||||
mk_t_err(pos(Decl), Msg);
|
||||
mk_error({unknown_byte_length, Type}) ->
|
||||
Msg = io_lib:format("Cannot resolve length of byte array.", []),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) ->
|
||||
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
"~s (at ~s)\n~s (at ~s)\nand result type\n~s (at ~s)",
|
||||
[pp_type(" - ", A), pp_loc(A), pp_type(" - ", B),
|
||||
pp_loc(B), pp_type(" - ", C), pp_loc(C)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) ->
|
||||
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
"~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)",
|
||||
[ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A),
|
||||
pp_type(" - ", B), pp_loc(B)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({failed_to_get_compiler_version, Err}) ->
|
||||
Msg = io_lib:format("Failed to get compiler version. Error: ~p", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg);
|
||||
mk_error({compiler_version_mismatch, Ann, Version, Op, Bound}) ->
|
||||
PrintV = fun(V) -> string:join([integer_to_list(N) || N <- V], ".") end,
|
||||
Msg = io_lib:format("Cannot compile with this version of the compiler, "
|
||||
"because it does not satisfy the constraint"
|
||||
" ~s ~s ~s", [PrintV(Version), Op, PrintV(Bound)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({empty_record_or_map_update, Expr}) ->
|
||||
Msg = io_lib:format("Empty record/map update `~s`", [pp_expr(Expr)]),
|
||||
mk_t_err(pos(Expr), Msg);
|
||||
mk_error({mixed_record_and_map, Expr}) ->
|
||||
Msg = io_lib:format("Mixed record fields and map keys in `~s`", [pp_expr(Expr)]),
|
||||
mk_t_err(pos(Expr), Msg);
|
||||
mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
|
||||
Msg = io_lib:format("Invalid `~s` argument `~s`. "
|
||||
"It must be either `true` or `false`.",
|
||||
[Name, pp_expr(instantiate(Arg))]),
|
||||
mk_t_err(pos(Arg), Msg);
|
||||
mk_error({conflicting_updates_for_field, Upd, Key}) ->
|
||||
Msg = io_lib:format("Conflicting updates for field '~s'", [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.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({multiple_main_contracts, Ann}) ->
|
||||
Msg = "Only one main contract can be defined.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unify_varargs, When}) ->
|
||||
Msg = "Cannot infer types for variable argument list.",
|
||||
{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.",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({contract_lacks_definition, Type, When}) ->
|
||||
Msg = io_lib:format(
|
||||
"~s is not implemented.",
|
||||
[pp_type(Type)]
|
||||
),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({ambiguous_name, Name, QIds}) ->
|
||||
Msg = io_lib:format("Ambiguous name `~s` could be one of~s",
|
||||
[pp(Name),
|
||||
[io_lib:format("\n - `~s` (at ~s)", [pp(QId), pp_loc(QId)]) || QId <- QIds]]),
|
||||
mk_t_err(pos(Name), 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({unknown_warning, Warning}) ->
|
||||
Msg = io_lib:format("Trying to report unknown warning: ~p", [Warning]),
|
||||
mk_t_err(pos(0, 0), Msg);
|
||||
mk_error({empty_record_definition, Ann, Name}) ->
|
||||
Msg = io_lib:format("Empty record definitions are not allowed. Cannot define the record `~s`", [Name]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
|
||||
Msg = io_lib:format("Unimplemented entrypoint `~s` from the interface `~s` in the contract `~s`", [FunName, InterfaceName, pp(ConId)]),
|
||||
mk_t_err(pos(ConId), Msg);
|
||||
mk_error({referencing_undefined_interface, InterfaceId}) ->
|
||||
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
|
||||
mk_t_err(pos(InterfaceId), Msg);
|
||||
mk_error({missing_definition, Id}) ->
|
||||
Msg = io_lib:format("Missing definition of function `~s`", [name(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({parameterized_state, Ann}) ->
|
||||
Msg = "The state type cannot be parameterized",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({parameterized_event, Ann}) ->
|
||||
Msg = "The event type cannot be parameterized",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing `init` function for the contract `~s`.", [name(Con)]),
|
||||
Cxt = "The `init` function can only be omitted if the state type is `unit`",
|
||||
mk_t_err(pos(Con), Msg, Cxt);
|
||||
mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
|
||||
What = "higher-order (contains function types)",
|
||||
ThingS = case Thing of
|
||||
{argument, X, T} -> io_lib:format("argument\n~s`\n", [pp_typed(" `", X, T)]);
|
||||
{result, T} -> io_lib:format("return type\n~s`\n", [pp_type(" `", T)])
|
||||
end,
|
||||
Bad = case Thing of
|
||||
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
|
||||
{result, _} -> io_lib:format("is ~s", [What])
|
||||
end,
|
||||
Msg = io_lib:format("The ~sof entrypoint `~s` ~s",
|
||||
[ThingS, Name, Bad]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({invalid_aens_resolve_type, Ann, T}) ->
|
||||
Msg = io_lib:format("Invalid return type of `AENS.resolve`:\n"
|
||||
"~s`\n"
|
||||
"It must be a `string` or a pubkey type (`address`, `oracle`, etc)",
|
||||
[pp_type(" `", T)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
WhyS = case Why of higher_order -> "higher-order (contain function types)";
|
||||
polymorphic -> "polymorphic (contain type variables)" end,
|
||||
Msg = io_lib:format("Invalid oracle type\n~s`", [pp_type(" `", Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({interface_implementation_conflict, Contract, I1, I2, Fun}) ->
|
||||
Msg = io_lib:format("Both interfaces `~s` and `~s` implemented by "
|
||||
"the contract `~s` have a function called `~s`",
|
||||
[name(I1), name(I2), name(Contract), name(Fun)]),
|
||||
mk_t_err(pos(Contract), Msg);
|
||||
mk_error({function_should_be_entrypoint, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` must be declared as an entrypoint instead of a function "
|
||||
"in order to implement the entrypoint `~s` from the interface `~s`",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({entrypoint_cannot_be_stateful, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` cannot be stateful because the entrypoint `~s` in the "
|
||||
"interface `~s` is not stateful",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({entrypoint_must_be_payable, Impl, Base, Interface}) ->
|
||||
Msg = io_lib:format("`~s` must be payable because the entrypoint `~s` in the "
|
||||
"interface `~s` is payable",
|
||||
[name(Impl), name(Base), name(Interface)]),
|
||||
mk_t_err(pos(Impl), Msg);
|
||||
mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
|
||||
KindStr = case Kind of
|
||||
contract -> "contract";
|
||||
contract_interface -> "interface"
|
||||
end,
|
||||
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
|
||||
[KindStr, name(ContractCon), name(InterfaceCon)]),
|
||||
mk_t_err(pos(ContractCon), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
mk_warning({unused_include, FileName, SrcFile}) ->
|
||||
Msg = io_lib:format("The file `~s` is included but not used.", [FileName]),
|
||||
aeso_warnings:new(aeso_errors:pos(SrcFile, 0, 0), Msg);
|
||||
mk_warning({unused_stateful, Ann, FunName}) ->
|
||||
Msg = io_lib:format("The function `~s` is unnecessarily marked as stateful.", [name(FunName)]),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
|
||||
Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({unused_typedef, Ann, QName, _Arity}) ->
|
||||
Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({unused_return_value, Ann}) ->
|
||||
Msg = io_lib:format("Unused return value.", []),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({unused_function, Ann, FunName}) ->
|
||||
Msg = io_lib:format("The function `~s` is defined but never used.", [FunName]),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({shadowing, Ann, VarName, AnnOld}) ->
|
||||
Msg = io_lib:format("The definition of `~s` shadows an older definition at ~s.", [VarName, pp_loc(AnnOld)]),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({division_by_zero, Ann}) ->
|
||||
Msg = io_lib:format("Division by zero.", []),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning({negative_spend, Ann}) ->
|
||||
Msg = io_lib:format("Negative spend.", []),
|
||||
aeso_warnings:new(pos(Ann), Msg);
|
||||
mk_warning(Warn) ->
|
||||
Msg = io_lib:format("Unknown warning: ~p", [Warn]),
|
||||
aeso_warnings:new(Msg).
|
||||
|
||||
mk_entrypoint(Decl) ->
|
||||
Ann = [entrypoint | lists:keydelete(public, 1,
|
||||
lists:keydelete(private, 1,
|
||||
aeso_syntax:get_ann(Decl))) -- [public, private]],
|
||||
aeso_syntax:set_ann(Ann, Decl).
|
||||
|
||||
pp_when({todo, What}) -> {pos(0, 0), io_lib:format("[TODO] ~p", [What])};
|
||||
pp_when({at, Ann}) -> {pos(Ann), io_lib:format("at ~s", [pp_loc(Ann)])};
|
||||
pp_when({check_typesig, Name, Inferred, Given}) ->
|
||||
{pos(Given),
|
||||
io_lib:format("when checking the definition of `~s`\n"
|
||||
" inferred type: `~s`\n"
|
||||
" given type: `~s`",
|
||||
[Name, pp(instantiate(Inferred)), pp(instantiate(Given))])};
|
||||
pp_when({infer_app, Fun, NamedArgs, Args, Inferred0, ArgTypes0}) ->
|
||||
Inferred = instantiate(Inferred0),
|
||||
ArgTypes = instantiate(ArgTypes0),
|
||||
{pos(Fun),
|
||||
io_lib:format("when checking the application of\n"
|
||||
" `~s`\n"
|
||||
"to arguments~s",
|
||||
[pp_typed("", Fun, Inferred),
|
||||
[ ["\n ", "`" ++ pp_expr(NamedArg) ++ "`"] || NamedArg <- NamedArgs ] ++
|
||||
[ ["\n ", "`" ++ pp_typed("", Arg, ArgT) ++ "`"]
|
||||
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
|
||||
pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
|
||||
FieldType = instantiate(FieldType0),
|
||||
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 `~s` to the old value `~s` and the new value `~s`",
|
||||
[pp_typed("", {lvalue, [], LV}, FieldType),
|
||||
pp(Id),
|
||||
pp_typed("", E, InferredType)]);
|
||||
{field, _Ann, LV, E} ->
|
||||
io_lib:format("when checking the assignment of the field `~s` to the value `~s`",
|
||||
[pp_typed("", {lvalue, [], LV}, FieldType),
|
||||
pp_typed("", E, InferredType)]);
|
||||
{proj, _Ann, _Rec, _Fld} ->
|
||||
io_lib:format("when checking the record projection `~s` against the expected type `~s`",
|
||||
[pp_typed(" ", Fld, FieldType),
|
||||
pp_type(" ", InferredType)])
|
||||
end};
|
||||
pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
|
||||
RecType = instantiate(RecType0),
|
||||
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",
|
||||
[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"
|
||||
"matches the expected type\n~s",
|
||||
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
|
||||
{field, _Ann, _LV, _E} ->
|
||||
{Pos,
|
||||
io_lib:format("when checking that the record type\n~s\n~s\n"
|
||||
"matches the expected type\n~s",
|
||||
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
|
||||
{proj, _Ann, Rec, _FldName} ->
|
||||
{pos(Rec),
|
||||
io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s",
|
||||
[pp_typed(" ", Rec, InferredType), pp_loc(Rec),
|
||||
pp_type(" ", RecType), WhyRec])}
|
||||
end;
|
||||
pp_when({if_branches, Then, ThenType0, Else, ElseType0}) ->
|
||||
{ThenType, ElseType} = instantiate({ThenType0, ElseType0}),
|
||||
Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ],
|
||||
{pos(element(1, hd(Branches))),
|
||||
io_lib:format("when comparing the types of the if-branches\n"
|
||||
"~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)])
|
||||
|| {B, BType} <- Branches ] ])};
|
||||
pp_when({case_pat, Pat, PatType0, ExprType0}) ->
|
||||
{PatType, ExprType} = instantiate({PatType0, ExprType0}),
|
||||
{pos(Pat),
|
||||
io_lib:format("when checking the type of the pattern `~s` against the expected type `~s`",
|
||||
[pp_typed("", Pat, PatType),
|
||||
pp_type(ExprType)])};
|
||||
pp_when({check_expr, Expr, Inferred0, Expected0}) ->
|
||||
{Inferred, Expected} = instantiate({Inferred0, Expected0}),
|
||||
{pos(Expr),
|
||||
io_lib:format("when checking the type of the expression `~s` against the expected type `~s`",
|
||||
[pp_typed("", Expr, Inferred), pp_type(Expected)])};
|
||||
pp_when({checking_init_type, Ann}) ->
|
||||
{pos(Ann),
|
||||
io_lib:format("when checking that `init` returns a value of type `state`", [])};
|
||||
pp_when({list_comp, BindExpr, Inferred0, Expected0}) ->
|
||||
{Inferred, Expected} = instantiate({Inferred0, Expected0}),
|
||||
{pos(BindExpr),
|
||||
io_lib:format("when checking rvalue of list comprehension binding `~s` against type `~s`",
|
||||
[pp_typed("", BindExpr, Inferred), pp_type(Expected)])};
|
||||
pp_when({check_named_arg_constraint, C}) ->
|
||||
{id, _, Name} = Arg = C#named_argument_constraint.name,
|
||||
[Type | _] = [ Type || {named_arg_t, _, {id, _, Name1}, Type, _} <- C#named_argument_constraint.args, Name1 == Name ],
|
||||
Err = io_lib:format("when checking named argument `~s` against inferred type `~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 `~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 `~s`", [pp_expr(Fun)])
|
||||
};
|
||||
pp_when(unknown) -> {pos(0,0), ""}.
|
||||
|
||||
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
|
||||
pp_why_record({var_args, Ann, Fun}) ->
|
||||
{pos(Ann),
|
||||
io_lib:format("arising from resolution of variadic function `~s`",
|
||||
[pp_expr(Fun)])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field `~s`",
|
||||
[pp_expr({lvalue, [], LV})])};
|
||||
pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
|
||||
{pos(Fld),
|
||||
io_lib:format("arising from an assignment of the field `~s`",
|
||||
[pp_expr({lvalue, [], LV})])};
|
||||
pp_why_record({proj, _Ann, Rec, FldName}) ->
|
||||
{pos(Rec),
|
||||
io_lib:format("arising from the projection of the field `~s`",
|
||||
[pp(FldName)])}.
|
||||
|
||||
|
||||
if_branches(If = {'if', Ann, _, Then, Else}) ->
|
||||
case proplists:get_value(format, Ann) of
|
||||
elif -> [Then | if_branches(Else)];
|
||||
_ -> [If]
|
||||
end;
|
||||
if_branches(E) -> [E].
|
||||
|
||||
pp_typed(Label, E, T = {type_sig, _, _, _, _, _}) -> pp_typed(Label, E, typesig_to_fun_t(T));
|
||||
pp_typed(Label, {typed, _, Expr, _}, Type) ->
|
||||
pp_typed(Label, 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])), 80, 80).
|
||||
|
||||
pp_type(Type) ->
|
||||
pp_type("", Type).
|
||||
pp_type(Label, Type) ->
|
||||
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated])), 80, 80).
|
||||
|
||||
src_file(T) -> aeso_syntax:get_ann(file, T, no_file).
|
||||
include_type(T) -> aeso_syntax:get_ann(include_type, T, none).
|
||||
line_number(T) -> aeso_syntax:get_ann(line, T, 0).
|
||||
column_number(T) -> aeso_syntax:get_ann(col, T, 0).
|
||||
|
||||
pos(T) -> aeso_errors:pos(src_file(T), line_number(T), column_number(T)).
|
||||
pos(L, C) -> aeso_errors:pos(L, C).
|
||||
|
||||
loc(T) ->
|
||||
{src_file(T), include_type(T), line_number(T), column_number(T)}.
|
||||
|
||||
pp_loc(T) ->
|
||||
{File, IncludeType, Line, Col} = loc(T),
|
||||
case {Line, Col} of
|
||||
{0, 0} -> "(builtin location)";
|
||||
_ -> case IncludeType of
|
||||
none -> io_lib:format("line ~p, column ~p", [Line, Col]);
|
||||
_ -> io_lib:format("line ~p, column ~p in ~s", [Line, Col, File])
|
||||
end
|
||||
end.
|
||||
|
||||
plural(No, _Yes, [_]) -> No;
|
||||
plural(_No, Yes, _) -> Yes.
|
||||
|
||||
pp(T = {type_sig, _, _, _, _, _}) ->
|
||||
pp(typesig_to_fun_t(T));
|
||||
pp([]) ->
|
||||
"";
|
||||
pp([T]) ->
|
||||
pp(T);
|
||||
pp([T|Ts]) ->
|
||||
[pp(T), ", "|pp(Ts)];
|
||||
pp({id, _, Name}) ->
|
||||
Name;
|
||||
pp({qid, _, Name}) ->
|
||||
string:join(Name, ".");
|
||||
pp({con, _, Name}) ->
|
||||
Name;
|
||||
pp({qcon, _, Name}) ->
|
||||
string:join(Name, ".");
|
||||
pp({uvar, _, Ref}) ->
|
||||
%% Show some unique representation
|
||||
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
||||
pp({tvar, _, Name}) ->
|
||||
Name;
|
||||
pp({if_t, _, Id, Then, Else}) ->
|
||||
["if(", pp([Id, Then, Else]), ")"];
|
||||
pp({tuple_t, _, []}) ->
|
||||
"unit";
|
||||
pp({tuple_t, _, Cpts}) ->
|
||||
["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"];
|
||||
pp({bytes_t, _, any}) -> "bytes(_)";
|
||||
pp({bytes_t, _, Len}) ->
|
||||
["bytes(", integer_to_list(Len), ")"];
|
||||
pp({app_t, _, T, []}) ->
|
||||
pp(T);
|
||||
pp({app_t, _, Type, Args}) ->
|
||||
[pp(Type), "(", pp(Args), ")"];
|
||||
pp({named_arg_t, _, Name, Type, _Default}) ->
|
||||
[pp(Name), " : ", pp(Type)];
|
||||
pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
|
||||
["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
|
||||
pp({fun_t, _, Named, As, B}) when is_list(Named) ->
|
||||
["(", pp(Named ++ As), ") => ", pp(B)];
|
||||
pp(Other) ->
|
||||
io_lib:format("~p", [Other]).
|
||||
|
||||
|
||||
plural(No, _Yes, [_]) -> No;
|
||||
plural(_No, Yes, _) -> Yes.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,344 @@
|
||||
-module(aeso_ast_types_solve).
|
||||
|
||||
|
||||
|
||||
-spec solve_constraints(env()) -> ok.
|
||||
solve_constraints(Env) ->
|
||||
%% First look for record fields that appear in only one type definition
|
||||
IsAmbiguous =
|
||||
fun(#field_constraint{
|
||||
record_t = RecordType,
|
||||
field = Field={id, _Attrs, FieldName},
|
||||
field_t = FieldType,
|
||||
kind = Kind,
|
||||
context = When }) ->
|
||||
Arity = fun_arity(dereference_deep(FieldType)),
|
||||
FieldInfos = case Arity of
|
||||
none -> lookup_record_field(Env, FieldName, Kind);
|
||||
_ -> lookup_record_field_arity(Env, FieldName, Arity, Kind)
|
||||
end,
|
||||
case FieldInfos of
|
||||
[] ->
|
||||
type_error({undefined_field, Field}),
|
||||
false;
|
||||
[#field_info{field_t = FldType, record_t = RecType}] ->
|
||||
create_freshen_tvars(),
|
||||
FreshFldType = freshen(FldType),
|
||||
FreshRecType = freshen(RecType),
|
||||
destroy_freshen_tvars(),
|
||||
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
|
||||
unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}),
|
||||
false;
|
||||
_ ->
|
||||
%% ambiguity--need cleverer strategy
|
||||
true
|
||||
end;
|
||||
(_) -> true
|
||||
end,
|
||||
AmbiguousConstraints = lists:filter(IsAmbiguous, get_constraints()),
|
||||
|
||||
% The two passes on AmbiguousConstraints are needed
|
||||
solve_ambiguous_constraints(Env, AmbiguousConstraints ++ AmbiguousConstraints).
|
||||
|
||||
-spec solve_ambiguous_constraints(env(), [constraint()]) -> ok.
|
||||
solve_ambiguous_constraints(Env, Constraints) ->
|
||||
Unknown = solve_known_record_types(Env, Constraints),
|
||||
if Unknown == [] -> ok;
|
||||
length(Unknown) < length(Constraints) ->
|
||||
%% progress! Keep trying.
|
||||
solve_ambiguous_constraints(Env, Unknown);
|
||||
true ->
|
||||
case solve_unknown_record_types(Env, Unknown) of
|
||||
true -> %% Progress!
|
||||
solve_ambiguous_constraints(Env, Unknown);
|
||||
_ -> ok %% No progress. Report errors later.
|
||||
end
|
||||
end.
|
||||
|
||||
solve_then_destroy_and_report_unsolved_constraints(Env) ->
|
||||
solve_constraints(Env),
|
||||
destroy_and_report_unsolved_constraints(Env).
|
||||
|
||||
destroy_and_report_unsolved_constraints(Env) ->
|
||||
{FieldCs, OtherCs} =
|
||||
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
||||
get_constraints()),
|
||||
{CreateCs, OtherCs1} =
|
||||
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
||||
OtherCs),
|
||||
{ContractCs, OtherCs2} =
|
||||
lists:partition(fun(#is_contract_constraint{}) -> true; (_) -> false end, OtherCs1),
|
||||
{NamedArgCs, OtherCs3} =
|
||||
lists:partition(fun(#dependent_type_constraint{}) -> true;
|
||||
(#named_argument_constraint{}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs2),
|
||||
{BytesCs, OtherCs4} =
|
||||
lists:partition(fun({is_bytes, _}) -> true;
|
||||
({add_bytes, _, _, _, _, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs3),
|
||||
{AensResolveCs, OtherCs5} =
|
||||
lists:partition(fun({aens_resolve_type, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs4),
|
||||
{OracleTypeCs, []} =
|
||||
lists:partition(fun({oracle_type, _, _}) -> true;
|
||||
(_) -> false
|
||||
end, OtherCs5),
|
||||
|
||||
Unsolved = [ S || S <- [ solve_constraint(Env, dereference_deep(C)) || C <- NamedArgCs ],
|
||||
S == unsolved ],
|
||||
[ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ],
|
||||
|
||||
Unknown = solve_known_record_types(Env, FieldCs),
|
||||
if Unknown == [] -> ok;
|
||||
true ->
|
||||
case solve_unknown_record_types(Env, Unknown) of
|
||||
true -> ok;
|
||||
Errors -> [ type_error(Err) || Err <- Errors ]
|
||||
end
|
||||
end,
|
||||
|
||||
check_record_create_constraints(Env, CreateCs),
|
||||
check_is_contract_constraints(Env, ContractCs),
|
||||
check_bytes_constraints(Env, BytesCs),
|
||||
check_aens_resolve_constraints(Env, AensResolveCs),
|
||||
check_oracle_type_constraints(Env, OracleTypeCs),
|
||||
|
||||
destroy_constraints().
|
||||
|
||||
%% 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;
|
||||
check_named_argument_constraint(Env,
|
||||
C = #named_argument_constraint{ args = Args,
|
||||
name = Id = {id, _, Name},
|
||||
type = Type }) ->
|
||||
case [ T || {named_arg_t, _, {id, _, Name1}, T, _} <- Args, Name1 == Name ] of
|
||||
[] ->
|
||||
type_error({bad_named_argument, Args, Id}),
|
||||
false;
|
||||
[T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true
|
||||
end;
|
||||
check_named_argument_constraint(Env,
|
||||
#dependent_type_constraint{ named_args_t = NamedArgsT0,
|
||||
named_args = NamedArgs,
|
||||
general_type = GenType,
|
||||
specialized_type = SpecType,
|
||||
context = {check_return, App} }) ->
|
||||
NamedArgsT = dereference(NamedArgsT0),
|
||||
case dereference(NamedArgsT0) of
|
||||
[_ | _] = NamedArgsT ->
|
||||
GetVal = fun(Name, Default) ->
|
||||
hd([ Val || {named_arg, _, {id, _, N}, Val} <- NamedArgs, N == Name] ++
|
||||
[ Default ])
|
||||
end,
|
||||
ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)}
|
||||
|| {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]),
|
||||
GenType1 = specialize_dependent_type(ArgEnv, GenType),
|
||||
unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}),
|
||||
true;
|
||||
_ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true
|
||||
end.
|
||||
|
||||
solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) ->
|
||||
not_solved;
|
||||
solve_constraint(Env, C = #field_constraint{record_t = RecType,
|
||||
field = FieldName,
|
||||
field_t = FieldType,
|
||||
context = When}) ->
|
||||
RecId = record_type_name(RecType),
|
||||
Attrs = aeso_syntax:get_ann(RecId),
|
||||
case lookup_type(Env, RecId) of
|
||||
{_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t ->
|
||||
FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields],
|
||||
{id, _, FieldString} = FieldName,
|
||||
case proplists:get_value(FieldString, FieldTypes) of
|
||||
undefined ->
|
||||
type_error({missing_field, FieldName, RecId}),
|
||||
not_solved;
|
||||
FldType ->
|
||||
create_freshen_tvars(),
|
||||
FreshFldType = freshen(FldType),
|
||||
FreshRecType = freshen(app_t(Attrs, RecId, Formals)),
|
||||
destroy_freshen_tvars(),
|
||||
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
|
||||
unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}),
|
||||
C
|
||||
end;
|
||||
_ ->
|
||||
type_error({not_a_record_type, instantiate(RecType), When}),
|
||||
not_solved
|
||||
end;
|
||||
solve_constraint(Env, C = #dependent_type_constraint{}) ->
|
||||
check_named_argument_constraint(Env, C);
|
||||
solve_constraint(Env, C = #named_argument_constraint{}) ->
|
||||
check_named_argument_constraint(Env, C);
|
||||
solve_constraint(_Env, {is_bytes, _}) -> ok;
|
||||
solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) ->
|
||||
A = unfold_types_in_type(Env, dereference(A0)),
|
||||
B = unfold_types_in_type(Env, dereference(B0)),
|
||||
C = unfold_types_in_type(Env, dereference(C0)),
|
||||
case {A, B, C} of
|
||||
{{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann});
|
||||
{{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann});
|
||||
{_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann});
|
||||
_ -> ok
|
||||
end;
|
||||
solve_constraint(_, _) -> ok.
|
||||
|
||||
check_bytes_constraints(Env, Constraints) ->
|
||||
InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints,
|
||||
T <- [A, B, C],
|
||||
element(1, T) /= bytes_t ],
|
||||
%% Skip is_bytes constraints for types that occur in add_bytes constraints
|
||||
%% (no need to generate error messages for both is_bytes and add_bytes).
|
||||
Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint);
|
||||
(_) -> false end,
|
||||
[ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ].
|
||||
|
||||
check_bytes_constraint(Env, {is_bytes, Type}) ->
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
case Type1 of
|
||||
{bytes_t, _, _} -> ok;
|
||||
_ ->
|
||||
type_error({unknown_byte_length, Type})
|
||||
end;
|
||||
check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
|
||||
A = unfold_types_in_type(Env, instantiate(A0)),
|
||||
B = unfold_types_in_type(Env, instantiate(B0)),
|
||||
C = unfold_types_in_type(Env, instantiate(C0)),
|
||||
case {A, B, C} of
|
||||
{{bytes_t, _, _M}, {bytes_t, _, _N}, {bytes_t, _, _R}} ->
|
||||
ok; %% If all are solved we checked M + N == R in solve_constraint.
|
||||
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
|
||||
end.
|
||||
|
||||
check_aens_resolve_constraints(_Env, []) ->
|
||||
ok;
|
||||
check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
{app_t, _, {id, _, "option"}, [Type2]} = Type1,
|
||||
case Type2 of
|
||||
{id, _, "string"} -> ok;
|
||||
{id, _, "address"} -> ok;
|
||||
{con, _, _} -> ok;
|
||||
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
|
||||
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
|
||||
_ -> type_error({invalid_aens_resolve_type, aeso_syntax:get_ann(Type), Type2})
|
||||
end,
|
||||
check_aens_resolve_constraints(Env, Rest).
|
||||
|
||||
check_oracle_type_constraints(_Env, []) ->
|
||||
ok;
|
||||
check_oracle_type_constraints(Env, [{oracle_type, Ann, OType} | Rest]) ->
|
||||
Type = unfold_types_in_type(Env, instantiate(OType)),
|
||||
{app_t, _, {id, _, "oracle"}, [QType, RType]} = Type,
|
||||
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
|
||||
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
|
||||
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
|
||||
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
|
||||
check_oracle_type_constraints(Env, Rest).
|
||||
|
||||
%% -- Field constraints --
|
||||
|
||||
check_record_create_constraints(_, []) -> ok;
|
||||
check_record_create_constraints(Env, [C | Cs]) ->
|
||||
#record_create_constraint{
|
||||
record_t = Type,
|
||||
fields = Fields,
|
||||
context = When } = C,
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
try lookup_type(Env, record_type_name(Type1)) of
|
||||
{_QId, {_Ann, {_Args, {record_t, RecFields}}}} ->
|
||||
ActualNames = [ Fld || {field_t, _, {id, _, Fld}, _} <- RecFields ],
|
||||
GivenNames = [ Fld || {id, _, Fld} <- Fields ],
|
||||
case ActualNames -- GivenNames of %% We know already that we don't have too many fields
|
||||
[] -> ok;
|
||||
Missing -> type_error({missing_fields, When, Type1, Missing})
|
||||
end;
|
||||
_ -> %% We can get here if there are other type errors.
|
||||
ok
|
||||
catch _:_ -> %% Might be unsolved, we get a different error in that case
|
||||
ok
|
||||
end,
|
||||
check_record_create_constraints(Env, Cs).
|
||||
|
||||
check_is_contract_constraints(_Env, []) -> ok;
|
||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||
#is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C,
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
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).
|
||||
|
||||
-spec solve_unknown_record_types(env(), [field_constraint()]) -> true | [tuple()].
|
||||
solve_unknown_record_types(Env, Unknown) ->
|
||||
UVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- Unknown]),
|
||||
Solutions = [solve_for_uvar(Env, UVar, [{Kind, When, Field}
|
||||
|| #field_constraint{record_t = U, field = Field, kind = Kind, context = When} <- Unknown,
|
||||
U == UVar])
|
||||
|| UVar <- UVars],
|
||||
case lists:member(true, Solutions) of
|
||||
true -> true;
|
||||
false -> Solutions
|
||||
end.
|
||||
|
||||
%% This will solve all kinds of constraints but will only return the
|
||||
%% unsolved field constraints
|
||||
-spec solve_known_record_types(env(), [constraint()]) -> [field_constraint()].
|
||||
solve_known_record_types(Env, Constraints) ->
|
||||
DerefConstraints = lists:map(fun(C = #field_constraint{record_t = RecordType}) ->
|
||||
C#field_constraint{record_t = dereference(RecordType)};
|
||||
(C) -> dereference_deep(C)
|
||||
end, Constraints),
|
||||
SolvedConstraints = lists:map(fun(C) -> solve_constraint(Env, dereference_deep(C)) end, DerefConstraints),
|
||||
Unsolved = DerefConstraints--SolvedConstraints,
|
||||
lists:filter(fun(#field_constraint{}) -> true; (_) -> false end, Unsolved).
|
||||
|
||||
solve_for_uvar(Env, UVar = {uvar, Attrs, _}, Fields0) ->
|
||||
Fields = [{Kind, Fld} || {Kind, _, Fld} <- Fields0],
|
||||
[{_, When, _} | _] = Fields0, %% Get the location from the first field
|
||||
%% If we have 'create' constraints they must be complete.
|
||||
Covering = lists:usort([ Name || {create, {id, _, Name}} <- Fields ]),
|
||||
%% Does this set of fields uniquely identify a record type?
|
||||
FieldNames = [ Name || {_Kind, {id, _, Name}} <- Fields ],
|
||||
UniqueFields = lists:usort(FieldNames),
|
||||
Candidates = [RecType || #field_info{record_t = RecType} <- lookup_record_field(Env, hd(FieldNames))],
|
||||
TypesAndFields = [case lookup_type(Env, record_type_name(RecType)) of
|
||||
{_, {_, {_, {record_t, RecFields}}}} ->
|
||||
{RecType, [Field || {field_t, _, {id, _, Field}, _} <- RecFields]};
|
||||
{_, {_, {_, {contract_t, ConFields}}}} ->
|
||||
%% TODO: is this right?
|
||||
{RecType, [Field || {field_t, _, {id, _, Field}, _} <- ConFields]};
|
||||
false -> %% impossible?
|
||||
error({no_definition_for, record_type_name(RecType), in, Env})
|
||||
end
|
||||
|| RecType <- Candidates],
|
||||
PartialSolutions =
|
||||
lists:sort([{RecType, if Covering == [] -> []; true -> RecFields -- Covering end}
|
||||
|| {RecType, RecFields} <- TypesAndFields,
|
||||
UniqueFields -- RecFields == []]),
|
||||
Solutions = [RecName || {RecName, []} <- PartialSolutions],
|
||||
|
||||
|
||||
apply_typesig_constraint(_Ann, none, _FunT) -> ok;
|
||||
apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
|
||||
add_constraint([#is_contract_constraint{ contract_t = Type,
|
||||
context = {address_to_contract, Ann}}]);
|
||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
||||
add_constraint({add_bytes, Ann, concat, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
||||
add_constraint({add_bytes, Ann, split, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) ->
|
||||
add_constraint([#is_contract_constraint{ contract_t = Con,
|
||||
context = {bytecode_hash, Ann} }]).
|
||||
@@ -1,684 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler builtin functions for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 20 Dec 2018
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_builtins).
|
||||
|
||||
-export([ builtin_function/1
|
||||
, bytes_to_raw_string/2
|
||||
, check_event_type/1
|
||||
, used_builtins/1 ]).
|
||||
|
||||
-import(aeso_ast_to_icode, [prim_call/5]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
|
||||
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
|
||||
used_builtins([H|T]) ->
|
||||
lists:umerge(used_builtins(H), used_builtins(T));
|
||||
used_builtins(T) when is_tuple(T) ->
|
||||
used_builtins(tuple_to_list(T));
|
||||
used_builtins(M) when is_map(M) ->
|
||||
used_builtins(maps:to_list(M));
|
||||
used_builtins(_) -> [].
|
||||
|
||||
builtin_deps(Builtin) ->
|
||||
lists:usort(builtin_deps1(Builtin)).
|
||||
|
||||
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1(map_member) -> [{map_lookup, word}];
|
||||
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
|
||||
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
|
||||
builtin_deps1(map_from_list) -> [map_put];
|
||||
builtin_deps1(str_equal) -> [str_equal_p];
|
||||
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
|
||||
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
|
||||
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
|
||||
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
|
||||
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
|
||||
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
|
||||
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
|
||||
builtin_deps1(string_reverse) -> [string_reverse_];
|
||||
builtin_deps1(require) -> [abort];
|
||||
builtin_deps1(_) -> [].
|
||||
|
||||
dep_closure(Deps) ->
|
||||
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
|
||||
[] -> Deps;
|
||||
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
|
||||
end.
|
||||
|
||||
%% Helper functions/macros
|
||||
v(X) when is_atom(X) -> v(atom_to_list(X));
|
||||
v(X) when is_list(X) -> #var_ref{name = X}.
|
||||
|
||||
option_none() -> {tuple, [{integer, 0}]}.
|
||||
option_some(X) -> {tuple, [{integer, 1}, X]}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
|
||||
-define(I(X), {integer, X}).
|
||||
-define(V(X), v(X)).
|
||||
-define(A(Op), aeb_opcodes:mnemonic(Op)).
|
||||
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
|
||||
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
|
||||
-define(NXT(Ptr), op('+', Ptr, 32)).
|
||||
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
|
||||
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
|
||||
|
||||
-define(EQ(A, B), op('==', A, B)).
|
||||
-define(LT(A, B), op('<', A, B)).
|
||||
-define(GT(A, B), op('>', A, B)).
|
||||
-define(ADD(A, B), op('+', A, B)).
|
||||
-define(SUB(A, B), op('-', A, B)).
|
||||
-define(MUL(A, B), op('*', A, B)).
|
||||
-define(DIV(A, B), op('div', A, B)).
|
||||
-define(MOD(A, B), op('mod', A, B)).
|
||||
-define(EXP(A, B), op('^', A, B)).
|
||||
-define(AND(A, B), op('&&', A, B)).
|
||||
|
||||
%% Bit shift operations takes their arguments backwards!?
|
||||
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
|
||||
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
|
||||
|
||||
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
|
||||
|
||||
%% We generate a lot of B * 8 for integer B from BSL and BSR.
|
||||
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
|
||||
{integer, A * B};
|
||||
simpl(Op) -> Op.
|
||||
|
||||
|
||||
operand(A) when is_atom(A) -> v(A);
|
||||
operand(I) when is_integer(I) -> {integer, I};
|
||||
operand(T) -> T.
|
||||
|
||||
check_event_type(Icode) ->
|
||||
case maps:get(event_type, Icode) of
|
||||
{variant_t, Cons} ->
|
||||
check_event_type(Cons, Icode);
|
||||
_ ->
|
||||
error({event_should_be_variant_type})
|
||||
end.
|
||||
|
||||
check_event_type(Evts, Icode) ->
|
||||
[ check_event_type(Name, Ix, T, Icode)
|
||||
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
|
||||
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
|
||||
|
||||
check_event_type(EvtName, Ix, Type, Icode) ->
|
||||
VMType =
|
||||
try
|
||||
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
||||
catch _:_ ->
|
||||
error({EvtName, could_not_resolve_type, Type})
|
||||
end,
|
||||
case {Ix, VMType, Type} of
|
||||
{indexed, word, _} -> ok;
|
||||
{notindexed, string, _} -> ok;
|
||||
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
|
||||
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
||||
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
|
||||
end.
|
||||
|
||||
bfun(B, {IArgs, IExpr, IRet}) ->
|
||||
{{builtin, B}, [private], IArgs, IExpr, IRet}.
|
||||
|
||||
builtin_function(BF) ->
|
||||
case BF of
|
||||
{event, EventT} -> bfun(BF, builtin_event(EventT));
|
||||
abort -> bfun(BF, builtin_abort());
|
||||
block_hash -> bfun(BF, builtin_block_hash());
|
||||
require -> bfun(BF, builtin_require());
|
||||
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
|
||||
map_put -> bfun(BF, builtin_map_put());
|
||||
map_delete -> bfun(BF, builtin_map_delete());
|
||||
map_size -> bfun(BF, builtin_map_size());
|
||||
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
|
||||
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
|
||||
map_member -> bfun(BF, builtin_map_member());
|
||||
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
|
||||
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
|
||||
map_from_list -> bfun(BF, builtin_map_from_list());
|
||||
list_concat -> bfun(BF, builtin_list_concat());
|
||||
string_length -> bfun(BF, builtin_string_length());
|
||||
string_concat -> bfun(BF, builtin_string_concat());
|
||||
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
|
||||
string_copy -> bfun(BF, builtin_string_copy());
|
||||
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
|
||||
str_equal_p -> bfun(BF, builtin_str_equal_p());
|
||||
str_equal -> bfun(BF, builtin_str_equal());
|
||||
popcount -> bfun(BF, builtin_popcount());
|
||||
int_to_str -> bfun(BF, builtin_int_to_str());
|
||||
addr_to_str -> bfun(BF, builtin_addr_to_str());
|
||||
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
|
||||
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
|
||||
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
|
||||
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
|
||||
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
|
||||
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
|
||||
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
|
||||
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
|
||||
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
|
||||
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
|
||||
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
|
||||
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
|
||||
string_reverse -> bfun(BF, builtin_string_reverse());
|
||||
string_reverse_ -> bfun(BF, builtin_string_reverse_())
|
||||
end.
|
||||
|
||||
%% Event primitive (dependent on Event type)
|
||||
%%
|
||||
%% We need to switch on the event and prepare the correct #event for icode_to_asm
|
||||
%% NOTE: we assume all errors are already checked!
|
||||
builtin_event(EventT) ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
||||
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
||||
Payload = %% Should put data ptr, length on stack.
|
||||
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
||||
([{{id, _, "string"}, V}]) ->
|
||||
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
||||
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
|
||||
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
|
||||
end,
|
||||
Ix =
|
||||
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
|
||||
(_, V) -> V end,
|
||||
Clause =
|
||||
fun(_Tag, {con, _, Con}, IxTypes) ->
|
||||
Types = [ T || {_Ix, T} <- IxTypes ],
|
||||
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
|
||||
EvtIndex = {integer, EvtIndexN},
|
||||
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
|
||||
end,
|
||||
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
|
||||
|
||||
{variant_t, Cons} = EventT,
|
||||
Tags = lists:seq(0, length(Cons) - 1),
|
||||
|
||||
{[{"e", event}],
|
||||
{switch, v(e),
|
||||
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|
||||
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Abort primitive.
|
||||
builtin_abort() ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
{[{"s", string}],
|
||||
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
|
||||
A(?REVERT)]}, %% Stack: 0,Ptr
|
||||
{tuple,[]}}.
|
||||
|
||||
builtin_block_hash() ->
|
||||
{[{"height", word}],
|
||||
?LET(hash, #prim_block_hash{ height = ?V(height)},
|
||||
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
|
||||
aeso_icode:option_typerep(word)}.
|
||||
|
||||
builtin_require() ->
|
||||
{[{"c", word}, {"msg", string}],
|
||||
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Map primitives
|
||||
builtin_map_lookup(Type) ->
|
||||
Ret = aeso_icode:option_typerep(Type),
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
|
||||
[#var_ref{name = "m"}, #var_ref{name = "k"}],
|
||||
[word, word], Ret),
|
||||
Ret}.
|
||||
|
||||
builtin_map_put() ->
|
||||
%% We don't need the types for put.
|
||||
{[{"m", word}, {"k", word}, {"v", word}],
|
||||
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
|
||||
[v(m), v(k), v(v)], [word, word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_delete() ->
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
|
||||
[v(m), v(k)], [word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_size() ->
|
||||
{[{"m", word}],
|
||||
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
|
||||
[v(m)], [word], word),
|
||||
word}.
|
||||
|
||||
%% Map builtins
|
||||
builtin_map_get(Type) ->
|
||||
%% function map_get(m, k) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_lookup_default(Type) ->
|
||||
%% function map_lookup_default(m, k, default) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% None => default
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}, {"default", Type}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
|
||||
[{option_none(), v(default)},
|
||||
{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_member() ->
|
||||
%% function map_member(m, k) : bool =
|
||||
%% switch(Map.lookup(m, k))
|
||||
%% None => false
|
||||
%% _ => true
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
|
||||
[{option_none(), {integer, 0}},
|
||||
{{var_ref, "_"}, {integer, 1}}]},
|
||||
word}.
|
||||
|
||||
builtin_map_upd(Type) ->
|
||||
%% function map_upd(map, key, fun) =
|
||||
%% map_put(map, key, fun(map_get(map, key)))
|
||||
{[{"map", word}, {"key", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_upd_default(Type) ->
|
||||
%% function map_upd(map, key, val, fun) =
|
||||
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
|
||||
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_from_list() ->
|
||||
%% function map_from_list(xs, acc) =
|
||||
%% switch(xs)
|
||||
%% [] => acc
|
||||
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
|
||||
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
|
||||
{switch, v(xs),
|
||||
[{{list, []}, v(acc)},
|
||||
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
|
||||
?call(map_from_list,
|
||||
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
|
||||
word}.
|
||||
|
||||
%% list_concat
|
||||
%%
|
||||
%% Concatenates two lists.
|
||||
builtin_list_concat() ->
|
||||
{[{"l1", {list, word}}, {"l2", {list, word}}],
|
||||
{switch, v(l1),
|
||||
[{{list, []}, v(l2)},
|
||||
{{binop, '::', v(hd), v(tl)},
|
||||
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
|
||||
]
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_string_length() ->
|
||||
%% function length(str) =
|
||||
%% switch(str)
|
||||
%% {n} -> n // (ab)use the representation
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s, ?V(n)),
|
||||
word}.
|
||||
|
||||
%% str_concat - concatenate two strings
|
||||
%%
|
||||
%% Unless the second string is the empty string, a new string is created at the
|
||||
%% top of the Heap and the address to it is returned. The tricky bit is when
|
||||
%% the words from the second string has to be shifted to fit next to the first
|
||||
%% string.
|
||||
builtin_string_concat() ->
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
{ifte, ?EQ(n1, 0),
|
||||
?V(s2), %% First string is empty return second string
|
||||
{ifte, ?EQ(n2, 0),
|
||||
?V(s1), %% Second string is empty return first string
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
|
||||
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
|
||||
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
|
||||
?V(ret) %% Put the actual return value
|
||||
]})}
|
||||
}
|
||||
)),
|
||||
word}.
|
||||
|
||||
builtin_string_concat_inner1() ->
|
||||
%% Copy all whole words from the first string, and set up for word fusion
|
||||
%% Special case when the length of the first string is divisible by 32.
|
||||
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
|
||||
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
|
||||
?LET(nx, ?MOD(n1, 32),
|
||||
{ifte, ?EQ(nx, 0),
|
||||
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
|
||||
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
|
||||
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
|
||||
})),
|
||||
word}.
|
||||
|
||||
builtin_string_copy() ->
|
||||
{[{"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{ifte, ?GT(n, 31),
|
||||
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
|
||||
?V(w)
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_string_shift_copy() ->
|
||||
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
{ifte, ?GT(n, ?SUB(32, off)),
|
||||
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
|
||||
{inline_asm, [?A(?MSIZE)]}}]
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_str_equal_p() ->
|
||||
%% function str_equal_p(n, p1, p2) =
|
||||
%% if(n =< 0) true
|
||||
%% else
|
||||
%% let w1 = *p1
|
||||
%% let w2 = *p2
|
||||
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
|
||||
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
|
||||
{ifte, ?LT(n, 1),
|
||||
?I(1),
|
||||
?DEREF(w1, p1,
|
||||
?DEREF(w2, p2,
|
||||
?AND(?EQ(w1, w2),
|
||||
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
|
||||
word}.
|
||||
|
||||
builtin_str_equal() ->
|
||||
%% function str_equal(s1, s2) =
|
||||
%% let n1 = length(s1)
|
||||
%% let n2 = length(s2)
|
||||
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
|
||||
)),
|
||||
word}.
|
||||
|
||||
%% Count the number of 1s in a bit field.
|
||||
builtin_popcount() ->
|
||||
%% function popcount(bits, acc) =
|
||||
%% if (bits == 0) acc
|
||||
%% else popcount(bits bsr 1, acc + bits band 1)
|
||||
{[{"bits", word}, {"acc", word}],
|
||||
{ifte, ?EQ(bits, 0),
|
||||
?V(acc),
|
||||
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
|
||||
}, word}.
|
||||
|
||||
builtin_int_to_str() ->
|
||||
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
|
||||
|
||||
builtin_baseX_tab(_X = 10) ->
|
||||
{[{"ix", word}], ?ADD($0, ix), word};
|
||||
builtin_baseX_tab(_X = 58) ->
|
||||
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
|
||||
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
|
||||
{[{"ix", word}],
|
||||
{ifte, ?LT(ix, 32),
|
||||
?BYTE(ix, Fst32),
|
||||
?BYTE(?SUB(ix, 32), Lst26)
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int(X) ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_pad(X = 10) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?LT(src, 0),
|
||||
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 16) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 58) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode(X) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
|
||||
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode_(X) ->
|
||||
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
|
||||
{ifte, ?EQ(fac, 0),
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
{ifte, ?EQ(ix, 32),
|
||||
%% We've filled a word, write it and start on new word
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
|
||||
?call({baseX_int_encode_, X},
|
||||
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
|
||||
?DIV(fac, X), ?ADD(ix, 1)])}
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_digits(X) ->
|
||||
{[{"x0", word}, {"dgts", word}],
|
||||
?LET(x1, ?DIV(x0, X),
|
||||
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_int(32) ->
|
||||
{[{"w", word}], ?V(w), word};
|
||||
builtin_bytes_to_int(N) when N < 32 ->
|
||||
{[{"w", word}], ?BSR(w, 32 - N), word};
|
||||
builtin_bytes_to_int(N) when N > 32 ->
|
||||
LastFullWord = N div 32 - 1,
|
||||
Body = case N rem 32 of
|
||||
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
|
||||
R ->
|
||||
?DEREF(hi, ?ADD(b, LastFullWord * 32),
|
||||
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
|
||||
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
|
||||
end,
|
||||
{[{"b", pointer}], Body, word}.
|
||||
|
||||
%% Two versions of this helper function, worker for sections not even 16 bytes long
|
||||
%% and worker_x for the full sized chunks.
|
||||
builtin_bytes_to_str_worker_x() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}],
|
||||
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_worker() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
|
||||
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_body(Var, N) when N < 16 ->
|
||||
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
|
||||
builtin_bytes_to_str_body(Var, 16) ->
|
||||
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N < 32 ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
|
||||
builtin_bytes_to_str_body(Var, 32) ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N > 32 ->
|
||||
WholeWords = ((N + 31) div 32) - 1,
|
||||
lists:append(
|
||||
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|
||||
|| I <- lists:seq(0, WholeWords - 1) ]) ++
|
||||
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
|
||||
|
||||
builtin_bytes_to_str(N) when N =< 32 ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(w, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string};
|
||||
builtin_bytes_to_str(N) when N > 32 ->
|
||||
{[{"p", pointer}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(p, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string}.
|
||||
|
||||
builtin_string_reverse() ->
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s,
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
|
||||
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
|
||||
word}.
|
||||
|
||||
builtin_string_reverse_() ->
|
||||
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
|
||||
{ifte, ?LT(i2, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
|
||||
?DEREF(w, p1,
|
||||
?LET(b, ?BYTE(?MOD(i2, 32), w),
|
||||
{ifte, ?LT(i1, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
|
||||
word}.
|
||||
|
||||
builtin_addr_to_str() ->
|
||||
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
|
||||
|
||||
%% At most one word
|
||||
%% | ..... | ========= | ........ |
|
||||
%% Offs ^ ^- Len -^ TotalLen ^
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
|
||||
%% Bytes are packed into a single word
|
||||
Masked =
|
||||
case Offs of
|
||||
0 -> Bytes;
|
||||
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
|
||||
end,
|
||||
Unpadded =
|
||||
case 32 - (Offs + Len) of
|
||||
0 -> Masked;
|
||||
N -> ?BSR(Masked, N)
|
||||
end,
|
||||
case Len of
|
||||
32 -> Unpadded;
|
||||
_ -> ?BSL(Unpadded, 32 - Len)
|
||||
end;
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
|
||||
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
|
||||
%% Might read one word more than necessary.
|
||||
Word = op('!', Offs, Bytes),
|
||||
case Len == 32 of
|
||||
true -> Word;
|
||||
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
|
||||
end.
|
||||
|
||||
builtin_bytes_concat(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Words = fun(N) -> (N + 31) div 32 end,
|
||||
WordsRes = Words(A + B),
|
||||
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
|
||||
(I) when 32 * I < A ->
|
||||
Len = A rem 32,
|
||||
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
|
||||
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
|
||||
?ADD(Hi, ?BSR(Lo, Len));
|
||||
(I) ->
|
||||
Offs = 32 * I - A,
|
||||
Len = min(32, B - Offs),
|
||||
bytes_slice(Offs, Len, B, ?V(b))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> ?V(b);
|
||||
{_, 0} -> ?V(a);
|
||||
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
|
||||
end,
|
||||
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
|
||||
|
||||
builtin_bytes_split(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Word = fun(I, Max) ->
|
||||
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> [?I(0), ?V(c)];
|
||||
{_, 0} -> [?V(c), ?I(0)];
|
||||
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
|
||||
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
|
||||
end,
|
||||
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
|
||||
|
||||
bytes_to_raw_string(N, Term) when N =< 32 ->
|
||||
{tuple, [?I(N), Term]};
|
||||
bytes_to_raw_string(N, Term) when N > 32 ->
|
||||
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
|
||||
end,
|
||||
Words = (N + 31) div 32,
|
||||
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Ulf Norell
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Formatting of code generation errors.
|
||||
%%% @end
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_code_errors).
|
||||
|
||||
-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]),
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
|
||||
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
|
||||
mk_err(pos(Con), Msg, Cxt);
|
||||
format({missing_definition, Id}) ->
|
||||
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
|
||||
mk_err(pos(Id), Msg);
|
||||
format({parameterized_state, Decl}) ->
|
||||
Msg = "The state type cannot be parameterized.\n",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({parameterized_event, Decl}) ->
|
||||
Msg = "The event type cannot be parameterized.\n",
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
|
||||
What = case Why of higher_order -> "higher-order (contains function types)";
|
||||
polymorphic -> "polymorphic (contains type variables)" end,
|
||||
ThingS = case Thing of
|
||||
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
|
||||
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
|
||||
end,
|
||||
Bad = case Thing of
|
||||
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
|
||||
{result, _} -> io_lib:format("is ~s", [What])
|
||||
end,
|
||||
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
|
||||
[ThingS, Name, Bad]),
|
||||
case Why of
|
||||
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
|
||||
higher_order -> mk_err(pos(Ann), Msg)
|
||||
end;
|
||||
format({cant_compare_type_aevm, Ann, Op, Type}) ->
|
||||
StringAndTuple = [ "- type string\n"
|
||||
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
|
||||
Msg = io_lib:format("Cannot compare values of type\n"
|
||||
"~s\n"
|
||||
"The AEVM only supports '~s' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"~s",
|
||||
[pp_type(2, Type), Op, StringAndTuple]),
|
||||
Cxt = "Use FATE if you need to compare arbitrary types.\n",
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({invalid_aens_resolve_type, Ann, T}) ->
|
||||
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
|
||||
"~s\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).\n",
|
||||
[pp_type(2, T)]),
|
||||
mk_err(pos(Ann), Msg);
|
||||
format({unapplied_contract_call, Contract}) ->
|
||||
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
|
||||
"~s\n", [pp_expr(2, Contract)]),
|
||||
Cxt = "Use FATE if you need this.\n",
|
||||
mk_err(pos(Contract), Msg, Cxt);
|
||||
format({unapplied_builtin, Id}) ->
|
||||
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
|
||||
Cxt = "Use FATE if you need this.\n",
|
||||
mk_err(pos(Id), Msg, Cxt);
|
||||
format({invalid_map_key_type, Why, Ann, Type}) ->
|
||||
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
|
||||
Cxt = case Why of
|
||||
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
|
||||
function -> "Map keys cannot be higher-order.\n"
|
||||
end,
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({invalid_oracle_type, Why, What, Ann, Type}) ->
|
||||
WhyS = case Why of higher_order -> "higher-order (contain function types)";
|
||||
polymorphic -> "polymorphic (contain type variables)" end,
|
||||
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
|
||||
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
|
||||
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
|
||||
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
|
||||
mk_err(pos(Ann), Msg, Cxt);
|
||||
|
||||
format(Err) ->
|
||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||
|
||||
pos(Ann) ->
|
||||
File = aeso_syntax:get_ann(file, Ann, no_file),
|
||||
Line = aeso_syntax:get_ann(line, Ann, 0),
|
||||
Col = aeso_syntax:get_ann(col, Ann, 0),
|
||||
aeso_errors:pos(File, Line, Col).
|
||||
|
||||
pp_typed(E, T) ->
|
||||
prettypr:format(prettypr:nest(2,
|
||||
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
|
||||
[aeso_pretty:expr(E), prettypr:text(" : "),
|
||||
aeso_pretty:type(T)]))).
|
||||
|
||||
pp_expr(E) ->
|
||||
pp_expr(0, E).
|
||||
|
||||
pp_expr(N, E) ->
|
||||
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
|
||||
|
||||
pp_type(N, T) ->
|
||||
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
|
||||
|
||||
mk_err(Pos, Msg) ->
|
||||
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
|
||||
|
||||
mk_err(Pos, Msg, Cxt) ->
|
||||
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).
|
||||
|
||||
+166
-366
@@ -2,7 +2,7 @@
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm.
|
||||
%%% Compiler from Aeterinty Sophia language to FATE.
|
||||
%%% @end
|
||||
%%% Created : 12 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
@@ -12,14 +12,13 @@
|
||||
, file/2
|
||||
, from_string/2
|
||||
, check_call/4
|
||||
, create_calldata/3 %% deprecated
|
||||
, create_calldata/3
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
, numeric_version/0
|
||||
, sophia_type_to_typerep/1
|
||||
, to_sophia_value/4 %% deprecated, need a backend
|
||||
, to_sophia_value/4
|
||||
, to_sophia_value/5
|
||||
, decode_calldata/3 %% deprecated
|
||||
, decode_calldata/3
|
||||
, decode_calldata/4
|
||||
, parse/2
|
||||
, add_include_path/2
|
||||
@@ -27,22 +26,20 @@
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code
|
||||
| pp_ast
|
||||
-type option() :: pp_ast
|
||||
| pp_types
|
||||
| pp_typed_ast
|
||||
| pp_icode
|
||||
| pp_assembler
|
||||
| pp_bytecode
|
||||
| pp_fate
|
||||
| no_code
|
||||
| keep_included
|
||||
| {backend, aevm | fate}
|
||||
| keep_included
|
||||
| debug_mode
|
||||
| {include, {file_system, [string()]} |
|
||||
{explicit_files, #{string() => binary()}}}
|
||||
| {src_file, string()}.
|
||||
| {src_file, string()}
|
||||
| {aci, aeso_aci:aci_type()}.
|
||||
-type options() :: [option()].
|
||||
|
||||
-export_type([ option/0
|
||||
@@ -103,72 +100,68 @@ add_include_path(File, Options) ->
|
||||
end.
|
||||
|
||||
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
|
||||
from_string(Contract, Options) ->
|
||||
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
|
||||
|
||||
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(Backend, binary_to_list(ContractBin), Options);
|
||||
from_string(Backend, ContractString, Options) ->
|
||||
from_string(ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(binary_to_list(ContractBin), Options);
|
||||
from_string(ContractString, Options) ->
|
||||
try
|
||||
from_string1(Backend, ContractString, Options)
|
||||
from_string1(ContractString, Options)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
from_string1(aevm, ContractString, Options) ->
|
||||
#{icode := Icode} = string_to_code(ContractString, Options),
|
||||
TypeInfo = extract_type_info(Icode),
|
||||
Assembler = assemble(Icode, Options),
|
||||
pp_assembler(aevm, Assembler, Options),
|
||||
ByteCodeList = to_bytecode(Assembler, Options),
|
||||
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
|
||||
pp_bytecode(ByteCode, Options),
|
||||
{ok, Version} = version(),
|
||||
{ok, #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => TypeInfo,
|
||||
abi_version => aeb_aevm_abi:abi_version(),
|
||||
payable => maps:get(payable, Icode)
|
||||
}};
|
||||
from_string1(fate, ContractString, Options) ->
|
||||
#{fcode := FCode} = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
|
||||
pp_assembler(fate, FateCode, Options),
|
||||
from_string1(ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := FCodeEnv
|
||||
, folded_typed_ast := FoldedTypedAst
|
||||
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||
#{ child_con_env := ChildContracts } = FCodeEnv,
|
||||
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
|
||||
{FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
|
||||
pp_fate(FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
{ok, #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => [],
|
||||
fate_code => FateCode,
|
||||
abi_version => aeb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode)
|
||||
}}.
|
||||
Res = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => [],
|
||||
fate_code => FateCode,
|
||||
abi_version => aeb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode),
|
||||
warnings => Warnings
|
||||
},
|
||||
ResDbg = Res#{variables_registers => VarsRegs},
|
||||
FinalRes =
|
||||
case proplists:get_value(debug_info, Options, false) of
|
||||
true -> ResDbg;
|
||||
false -> Res
|
||||
end,
|
||||
{ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}.
|
||||
|
||||
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
|
||||
case proplists:get_value(aci, Options) of
|
||||
undefined ->
|
||||
Result;
|
||||
Type ->
|
||||
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
|
||||
maps:put(aci, Aci, Result)
|
||||
end.
|
||||
|
||||
-spec string_to_code(string(), options()) -> map().
|
||||
string_to_code(ContractString, Options) ->
|
||||
Ast = parse(ContractString, Options),
|
||||
pp_sophia_code(Ast, Options),
|
||||
pp_ast(Ast, Options),
|
||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
pp_typed_ast(TypedAst, Options),
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = ast_to_icode(TypedAst, Options),
|
||||
pp_icode(Icode, Options),
|
||||
#{ icode => Icode,
|
||||
typed_ast => TypedAst,
|
||||
type_env => TypeEnv};
|
||||
fate ->
|
||||
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
|
||||
#{ fcode => Fcode,
|
||||
typed_ast => TypedAst,
|
||||
type_env => TypeEnv}
|
||||
end.
|
||||
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst, Warnings} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
pp_typed_ast(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
|
||||
, ast => Ast
|
||||
, warnings => Warnings }.
|
||||
|
||||
-define(CALL_NAME, "__call").
|
||||
-define(DECODE_NAME, "__decode").
|
||||
|
||||
%% Takes a string containing a contract with a declaration/prototype of a
|
||||
%% function (foo, say) and adds function __call() = foo(args) calling this
|
||||
@@ -176,10 +169,8 @@ string_to_code(ContractString, Options) ->
|
||||
%% terms for the arguments.
|
||||
%% NOTE: Special treatment for "init" since it might be implicit and has
|
||||
%% a special return type (typerep, T)
|
||||
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
|
||||
| {ok, string(), [term()]}
|
||||
| {error, [aeso_errors:error()]}
|
||||
when Type :: term().
|
||||
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), [term()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
check_call(Source, "init" = FunName, Args, Options) ->
|
||||
case check_call1(Source, FunName, Args, Options) of
|
||||
Err = {error, _} when Args == [] ->
|
||||
@@ -196,42 +187,20 @@ check_call(Source, FunName, Args, Options) ->
|
||||
|
||||
check_call1(ContractString0, FunName, Args, Options) ->
|
||||
try
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
%% First check the contract without the __call function
|
||||
#{} = string_to_code(ContractString0, Options),
|
||||
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options),
|
||||
#{typed_ast := TypedAst,
|
||||
icode := Icode} = string_to_code(ContractString, Options),
|
||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||
RetVMType = case RetType of
|
||||
{id, _, "_"} -> any;
|
||||
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
|
||||
end,
|
||||
#{ functions := Funs } = Icode,
|
||||
ArgIcode = get_arg_icode(Funs),
|
||||
ArgTerms = [ icode_to_term(T, Arg) ||
|
||||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
|
||||
RetVMType1 =
|
||||
case FunName of
|
||||
"init" -> {tuple, [typerep, RetVMType]};
|
||||
_ -> RetVMType
|
||||
end,
|
||||
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
|
||||
fate ->
|
||||
%% First check the contract without the __call function
|
||||
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
{ok, FunName, CallArgs}
|
||||
end
|
||||
%% First check the contract without the __call function
|
||||
#{fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast} = string_to_code(ContractString0, Options),
|
||||
{FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
|
||||
{ok, FunName, CallArgs}
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
@@ -253,9 +222,8 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
|
||||
end.
|
||||
|
||||
%% Add the __call function to a contract.
|
||||
-spec insert_call_function(string(), string(), string(), [string()], options()) -> string().
|
||||
insert_call_function(Code, Call, FunName, Args, Options) ->
|
||||
Ast = parse(Code, Options),
|
||||
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
|
||||
insert_call_function(Ast, Code, Call, FunName, Args) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
@@ -276,118 +244,77 @@ insert_init_function(Code, Options) ->
|
||||
|
||||
last_contract_indent(Decls) ->
|
||||
case lists:last(Decls) of
|
||||
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
{_, _, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
|
||||
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
||||
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
|
||||
to_sophia_value(_, _, error, Err, _Options) ->
|
||||
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||
to_sophia_value(_, _, revert, Data, Options) ->
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
case aeb_heap:from_binary(string, Data) of
|
||||
{ok, Err} ->
|
||||
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
||||
{error, _} ->
|
||||
Msg = "Could not interpret the revert message\n",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
try aeb_fate_encoding:deserialize(Data) of
|
||||
Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
|
||||
catch _:_ ->
|
||||
Msg = "Could not deserialize the revert message\n",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||
try aeso_vm_decode:from_fate({id, [], "string"}, aeb_fate_encoding:deserialize(Data)) of
|
||||
Err ->
|
||||
{ok, {app, [], {id, [], "abort"}, [Err]}}
|
||||
catch _:_ ->
|
||||
Msg = "Could not deserialize the revert message",
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
Code = string_to_code(ContractString, Options),
|
||||
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = maps:get(icode, Code),
|
||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||
case aeb_heap:from_binary(VmType, Data) of
|
||||
{ok, VmValue} ->
|
||||
try
|
||||
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||
[Data, VmType, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _Err} ->
|
||||
Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
|
||||
[aeb_fate_encoding:deserialize(Data), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
|
||||
[aeb_fate_encoding:deserialize(Data), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
|
||||
-spec create_calldata(string(), string(), [string()]) ->
|
||||
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
{ok, binary()} | {error, [aeso_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args) ->
|
||||
create_calldata(Code, Fun, Args, [{backend, aevm}]).
|
||||
|
||||
create_calldata(Code, Fun, Args, []).
|
||||
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
|
||||
{ok, binary()} | {error, [aeso_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||
{error, _} = Err -> Err
|
||||
end;
|
||||
fate ->
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, FateArgs} ->
|
||||
aeb_fate_abi:create_calldata(FunName, FateArgs);
|
||||
{error, _} = Err -> Err
|
||||
end
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, FateArgs} ->
|
||||
aeb_fate_abi:create_calldata(FunName, FateArgs);
|
||||
{error, _} = Err -> Err
|
||||
end.
|
||||
|
||||
-spec decode_calldata(string(), string(), binary()) ->
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata) ->
|
||||
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
|
||||
|
||||
decode_calldata(ContractString, FunName, Calldata, []).
|
||||
-spec decode_calldata(string(), string(), binary(), options()) ->
|
||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||
| {error, [aeso_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
Code = string_to_code(ContractString, Options),
|
||||
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
|
||||
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
||||
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
|
||||
@@ -395,75 +322,29 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
Type0 = {tuple_t, [], ArgTypes},
|
||||
%% user defined data types such as variants needed to match against
|
||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = maps:get(icode, Code),
|
||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
||||
{ok, {_, VmValue}} ->
|
||||
try
|
||||
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
|
||||
%% Values are Sophia expressions in AST format
|
||||
{ok, ArgTypes, Values}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||
[VmValue, VmType, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _Err} ->
|
||||
Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
|
||||
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||
{ok, FateArgs} ->
|
||||
try
|
||||
{tuple_t, [], ArgTypes1} = Type,
|
||||
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|
||||
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||
{ok, ArgTypes, AstArgs}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s",
|
||||
[FateArgs, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
fate ->
|
||||
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||
{ok, FateArgs} ->
|
||||
try
|
||||
{tuple_t, [], ArgTypes1} = Type,
|
||||
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|
||||
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||
{ok, ArgTypes, AstArgs}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
|
||||
[FateArgs, Type0Str]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _} ->
|
||||
Msg = io_lib:format("Failed to decode calldata binary\n", []),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
{error, _} ->
|
||||
Msg = io_lib:format("Failed to decode calldata binary", []),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
get_arg_icode(Funs) ->
|
||||
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||
[Args] -> Args;
|
||||
[] -> error_missing_call_function()
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, error_missing_call_function/0}).
|
||||
error_missing_call_function() ->
|
||||
Msg = "Internal error: missing '__call'-function",
|
||||
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
||||
|
||||
get_call_type([{contract, _, _, Defs}]) ->
|
||||
case [ {lists:last(QFunName), FunType}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
{typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
|
||||
[Call] -> {ok, Call};
|
||||
[] -> error_missing_call_function()
|
||||
end;
|
||||
get_call_type([_ | Contracts]) ->
|
||||
%% The __call should be in the final contract
|
||||
get_call_type(Contracts).
|
||||
|
||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
|
||||
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,
|
||||
@@ -473,8 +354,8 @@ get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
|
||||
case FunName of
|
||||
"init" -> {ok, [], {tuple_t, [], []}};
|
||||
_ ->
|
||||
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
|
||||
Pos = aeso_code_errors:pos(Ann),
|
||||
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
|
||||
Pos = aeso_errors:pos(Ann),
|
||||
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
|
||||
end
|
||||
end;
|
||||
@@ -482,91 +363,22 @@ get_decode_type(FunName, [_ | Contracts]) ->
|
||||
%% The __decode should be in the final contract
|
||||
get_decode_type(FunName, Contracts).
|
||||
|
||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||
%% consumed by aeb_heap:to_binary().
|
||||
icode_to_term(word, {integer, N}) -> N;
|
||||
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
|
||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||
Str;
|
||||
icode_to_term({list, T}, {list, Vs}) ->
|
||||
[ icode_to_term(T, V) || V <- Vs ];
|
||||
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
|
||||
list_to_tuple(icodes_to_terms(Ts, Vs));
|
||||
icode_to_term({variant, Cs}, {tuple, [{integer, Tag} | Args]}) ->
|
||||
Ts = lists:nth(Tag + 1, Cs),
|
||||
{variant, Tag, icodes_to_terms(Ts, Args)};
|
||||
icode_to_term(T = {map, KT, VT}, M) ->
|
||||
%% Maps are compiled to builtin and primop calls, so this gets a little hairy
|
||||
case M of
|
||||
{funcall, {var_ref, {builtin, map_put}}, [M1, K, V]} ->
|
||||
Map = icode_to_term(T, M1),
|
||||
Key = icode_to_term(KT, K),
|
||||
Val = icode_to_term(VT, V),
|
||||
Map#{ Key => Val };
|
||||
#prim_call_contract{ address = {integer, 0},
|
||||
arg = {tuple, [{integer, ?PRIM_CALL_MAP_EMPTY}, _, _]} } ->
|
||||
#{};
|
||||
_ -> throw({todo, M})
|
||||
end;
|
||||
icode_to_term(word, {unop, 'bnot', A}) ->
|
||||
bnot icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'bor', A, B}) ->
|
||||
icode_to_term(word, A) bor icode_to_term(word, B);
|
||||
icode_to_term(word, {binop, 'bsl', A, B}) ->
|
||||
icode_to_term(word, B) bsl icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'band', A, B}) ->
|
||||
icode_to_term(word, A) band icode_to_term(word, B);
|
||||
icode_to_term(typerep, _) ->
|
||||
throw({todo, typerep});
|
||||
icode_to_term(T, V) ->
|
||||
throw({not_a_value, T, V}).
|
||||
pp_ast(C, Opts) ->
|
||||
[ io:format("AST:\n~s\n",
|
||||
[prettypr:format(aeso_pretty:decls(Ast, []))])
|
||||
|| true <- proplists:get_value(pp_ast, Opts)
|
||||
].
|
||||
|
||||
icodes_to_terms(Ts, Vs) ->
|
||||
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
|
||||
pp_typed_ast(C, Opts) ->
|
||||
[ io:format("Typed AST:\n~s\n",
|
||||
[prettypr:format(aeso_pretty:decls(Ast, [show_generated]))])
|
||||
|| true <- proplists:get_value(pp_typed_ast, Opts)
|
||||
].
|
||||
|
||||
ast_to_icode(TypedAst, Options) ->
|
||||
aeso_ast_to_icode:convert_typed(TypedAst, Options).
|
||||
|
||||
assemble(Icode, Options) ->
|
||||
aeso_icode_to_asm:convert(Icode, Options).
|
||||
|
||||
|
||||
to_bytecode(['COMMENT',_|Rest],_Options) ->
|
||||
to_bytecode(Rest,_Options);
|
||||
to_bytecode([Op|Rest], Options) ->
|
||||
[aeb_opcodes:m_to_op(Op)|to_bytecode(Rest, Options)];
|
||||
to_bytecode([], _) -> [].
|
||||
|
||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
||||
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end,
|
||||
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
|
||||
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|
||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||
not is_tuple(Name),
|
||||
not lists:member(private, Attrs)
|
||||
],
|
||||
lists:sort(TypeInfo).
|
||||
|
||||
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
|
||||
io:format("~s\n", [prettypr:format(aeso_pretty:decls(Code))])
|
||||
end).
|
||||
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
|
||||
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
|
||||
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
|
||||
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
|
||||
|
||||
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
|
||||
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
|
||||
|
||||
pp(Code, Options, Option, PPFun) ->
|
||||
case proplists:lookup(Option, Options) of
|
||||
{Option, true} ->
|
||||
PPFun(Code);
|
||||
none ->
|
||||
ok
|
||||
end.
|
||||
pp_fate(C, Opts) ->
|
||||
[ io:format("FATE:\n~s\n", [aeb_fate_asm:pp(Asm)])
|
||||
|| true <- proplists:get_value(pp_fate, Opts)
|
||||
].
|
||||
|
||||
%% -- Byte code validation ---------------------------------------------------
|
||||
|
||||
@@ -575,31 +387,27 @@ pp(Code, Options, Option, PPFun) ->
|
||||
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
|
||||
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
|
||||
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
|
||||
fate ->
|
||||
try
|
||||
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||
{FCode2, SrcPayable} =
|
||||
?protect(compile,
|
||||
begin
|
||||
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||
from_string1(fate, Source, Options),
|
||||
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||
end),
|
||||
case compare_fate_code(FCode1, FCode2) of
|
||||
ok when SrcPayable /= Payable ->
|
||||
Not = fun(true) -> ""; (false) -> " not" end,
|
||||
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||
[Not(Payable), Not(SrcPayable)]));
|
||||
ok -> ok;
|
||||
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||
end
|
||||
catch
|
||||
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||
end
|
||||
try
|
||||
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||
{FCode2, SrcPayable} =
|
||||
?protect(compile,
|
||||
begin
|
||||
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||
from_string1(Source, Options),
|
||||
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||
end),
|
||||
case compare_fate_code(FCode1, FCode2) of
|
||||
ok when SrcPayable /= Payable ->
|
||||
Not = fun(true) -> ""; (false) -> " not" end,
|
||||
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||
[Not(Payable), Not(SrcPayable)]));
|
||||
ok -> ok;
|
||||
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||
end
|
||||
catch
|
||||
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||
end.
|
||||
|
||||
compare_fate_code(FCode1, FCode2) ->
|
||||
@@ -651,14 +459,6 @@ pp_fate_type(T) -> io_lib:format("~w", [T]).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}.
|
||||
sophia_type_to_typerep(String) ->
|
||||
Ast = aeso_parser:run_parser(aeso_parser:type(), String),
|
||||
try aeso_ast_to_icode:ast_typerep(Ast) of
|
||||
Type -> {ok, Type}
|
||||
catch _:_ -> {error, bad_type}
|
||||
end.
|
||||
|
||||
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
|
||||
parse(Text, Options) ->
|
||||
parse(Text, sets:new(), Options).
|
||||
|
||||
+21
-3
@@ -30,12 +30,16 @@
|
||||
|
||||
-export([ err_msg/1
|
||||
, msg/1
|
||||
, msg_oneline/1
|
||||
, new/2
|
||||
, new/3
|
||||
, new/4
|
||||
, pos/1
|
||||
, pos/2
|
||||
, pos/3
|
||||
, pp/1
|
||||
, pp_oneline/1
|
||||
, pp_pos/1
|
||||
, to_json/1
|
||||
, throw/1
|
||||
, type/1
|
||||
@@ -50,6 +54,12 @@ new(Type, Pos, Msg) ->
|
||||
new(Type, Pos, Msg, Ctxt) ->
|
||||
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
|
||||
|
||||
pos(Ann) ->
|
||||
File = aeso_syntax:get_ann(file, Ann, no_file),
|
||||
Line = aeso_syntax:get_ann(line, Ann, 0),
|
||||
Col = aeso_syntax:get_ann(col, Ann, 0),
|
||||
pos(File, Line, Col).
|
||||
|
||||
pos(Line, Col) ->
|
||||
#pos{ line = Line, col = Col }.
|
||||
|
||||
@@ -65,10 +75,13 @@ throw(#err{} = Err) ->
|
||||
erlang:throw({error, [Err]}).
|
||||
|
||||
msg(#err{ message = Msg, context = none }) -> Msg;
|
||||
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
|
||||
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ "\n" ++ Ctxt.
|
||||
|
||||
msg_oneline(#err{ message = Msg, context = none }) -> Msg;
|
||||
msg_oneline(#err{ message = Msg, context = Ctxt }) -> Msg ++ " - " ++ Ctxt.
|
||||
|
||||
err_msg(#err{ pos = Pos } = Err) ->
|
||||
lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])).
|
||||
lists:flatten(io_lib:format("~s~s\n", [str_pos(Pos), msg(Err)])).
|
||||
|
||||
str_pos(#pos{file = no_file, line = L, col = C}) ->
|
||||
io_lib:format("~p:~p:", [L, C]);
|
||||
@@ -78,7 +91,12 @@ str_pos(#pos{file = F, line = L, col = C}) ->
|
||||
type(#err{ type = Type }) -> Type.
|
||||
|
||||
pp(#err{ type = Kind, pos = Pos } = Err) ->
|
||||
lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
|
||||
lists:flatten(io_lib:format("~s~s:\n~s\n", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
|
||||
|
||||
pp_oneline(#err{ type = Kind, pos = Pos } = Err) ->
|
||||
Msg = msg_oneline(Err),
|
||||
OneLineMsg = re:replace(Msg, "[\s\\n]+", " ", [global]),
|
||||
lists:flatten(io_lib:format("~s~s: ~s", [pp_kind(Kind), pp_pos(Pos), OneLineMsg])).
|
||||
|
||||
pp_kind(type_error) -> "Type error";
|
||||
pp_kind(parse_error) -> "Parse error";
|
||||
|
||||
+320
-119
@@ -9,7 +9,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_fcode_to_fate).
|
||||
|
||||
-export([compile/2, term_to_fate/1]).
|
||||
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
@@ -45,7 +45,14 @@
|
||||
-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 = #{},
|
||||
saved_fresh_names = #{},
|
||||
options = [] }).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
|
||||
@@ -64,19 +71,34 @@ debug(Tag, Options, Fun) ->
|
||||
|
||||
-dialyzer({nowarn_function, [code_error/1]}).
|
||||
code_error(Err) ->
|
||||
aeso_errors:throw(aeso_code_errors:format(Err)).
|
||||
Pos = aeso_errors:pos(0, 0),
|
||||
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Err])),
|
||||
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
|
||||
|
||||
%% -- Main -------------------------------------------------------------------
|
||||
|
||||
%% @doc Main entry point.
|
||||
compile(FCode, Options) ->
|
||||
compile(FCode, SavedFreshNames, Options) ->
|
||||
compile(#{}, FCode, SavedFreshNames, Options).
|
||||
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
|
||||
try
|
||||
compile1(ChildContracts, FCode, SavedFreshNames, Options)
|
||||
after
|
||||
put(variables_registers, undefined)
|
||||
end.
|
||||
|
||||
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
|
||||
#{ contract_name := ContractName,
|
||||
functions := Functions } = FCode,
|
||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
FateCode = to_basic_blocks(SFuns1),
|
||||
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||
FateCode.
|
||||
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of
|
||||
false -> FateCode;
|
||||
true -> add_child_symbols(ChildContracts, FateCode)
|
||||
end,
|
||||
{FateCode1, get_variables_registers()}.
|
||||
|
||||
make_function_id(X) ->
|
||||
aeb_fate_code:symbol_identifier(make_function_name(X)).
|
||||
@@ -85,21 +107,48 @@ 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) ->
|
||||
add_child_symbols(ChildContracts, FateCode) ->
|
||||
Funs = lists:flatten([ maps:keys(ChildFuns) || {_, #{functions := ChildFuns}} <- maps:to_list(ChildContracts) ]),
|
||||
Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]),
|
||||
aeb_fate_code:update_symbols(FateCode, Symbols).
|
||||
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, 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, SavedFreshNames, 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, SavedFreshNames, 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, SavedFreshNames, Options),
|
||||
[ add_variables_register(Env, Arg, Register) ||
|
||||
proplists:get_value(debug_info, Options, false),
|
||||
{Arg, Register} <- Env#env.vars ],
|
||||
SCode = to_scode(Env, Body),
|
||||
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||
|
||||
get_variables_registers() ->
|
||||
case get(variables_registers) of
|
||||
undefined -> #{};
|
||||
Vs -> Vs
|
||||
end.
|
||||
|
||||
add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) ->
|
||||
Olds = get_variables_registers(),
|
||||
RealName = maps:get(Name, SavedFreshNames, Name),
|
||||
FunName =
|
||||
case Env#env.current_function of
|
||||
event -> "Chain.event";
|
||||
{entrypoint, BinName} -> binary_to_list(BinName);
|
||||
{local_fun, QualName} -> lists:last(QualName)
|
||||
end,
|
||||
New = {Env#env.contract, FunName, RealName},
|
||||
put(variables_registers, Olds#{New => Register}).
|
||||
|
||||
-define(tvars, '$tvars').
|
||||
|
||||
typesig_to_scode(Args, Res) ->
|
||||
@@ -133,7 +182,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,17 +193,21 @@ 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, SavedFreshNames, Options) ->
|
||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
tailpos = true }.
|
||||
options = Options,
|
||||
tailpos = true,
|
||||
saved_fresh_names = SavedFreshNames }.
|
||||
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
||||
|
||||
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
|
||||
proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var),
|
||||
Env#env{ vars = [{Name, Var} | Vars] }.
|
||||
|
||||
bind_local(Name, Env) ->
|
||||
@@ -169,7 +224,34 @@ lookup_var(#env{vars = Vars}, X) ->
|
||||
|
||||
%% -- The compiler --
|
||||
|
||||
lit_to_fate(L) ->
|
||||
serialize_contract_code(Env, C) ->
|
||||
Cache = case get(contract_code_cache) of
|
||||
undefined -> put(contract_code_cache, #{}), #{};
|
||||
Res -> Res
|
||||
end,
|
||||
case maps:get(C, Cache, none) of
|
||||
none ->
|
||||
Options = Env#env.options,
|
||||
SavedFreshNames = Env#env.saved_fresh_names,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, 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),
|
||||
put(contract_code_cache, maps:put(C, Serialized, Cache)),
|
||||
Serialized;
|
||||
Serialized -> Serialized
|
||||
end.
|
||||
|
||||
lit_to_fate(Env, L) ->
|
||||
case L of
|
||||
{int, N} -> aeb_fate_data:make_integer(N);
|
||||
{string, S} -> aeb_fate_data:make_string(S);
|
||||
@@ -179,63 +261,65 @@ 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} -> aeb_fate_data:make_contract_bytearray(serialize_contract_code(Env, C));
|
||||
{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)];
|
||||
@@ -302,18 +386,27 @@ to_scode1(Env, {funcall, Fun, Args}) ->
|
||||
to_scode1(Env, {builtin, B, Args}) ->
|
||||
builtin_to_scode(Env, B, Args);
|
||||
|
||||
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) ->
|
||||
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
|
||||
Lbl = make_function_id(Fun),
|
||||
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
|
||||
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
|
||||
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
|
||||
case Gas of
|
||||
{builtin, call_gas_left, _} ->
|
||||
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value | Args]);
|
||||
case Protected of
|
||||
{lit, {bool, false}} ->
|
||||
case Gas of
|
||||
{builtin, call_gas_left, _} ->
|
||||
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value | Args]);
|
||||
_ ->
|
||||
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
|
||||
end;
|
||||
{lit, {bool, true}} ->
|
||||
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args]);
|
||||
_ ->
|
||||
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
|
||||
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
|
||||
end;
|
||||
|
||||
to_scode1(_Env, {get_state, Reg}) ->
|
||||
@@ -463,6 +556,8 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
|
||||
builtin_to_scode(Env, abort, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
|
||||
builtin_to_scode(Env, exit, [_] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:exit(?a), Args);
|
||||
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
|
||||
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
|
||||
tuple(0)], Args);
|
||||
@@ -494,10 +589,14 @@ 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) ->
|
||||
call_to_scode(Env, aeb_fate_ops:oracle_register(?a, ?a, ?a, ?a, ?a, ?a, ?a), Args);
|
||||
builtin_to_scode(Env, oracle_expiry, [_Oracle] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:oracle_expiry(?a, ?a), Args);
|
||||
builtin_to_scode(Env, oracle_query_fee, [_Oracle] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:oracle_query_fee(?a, ?a), Args);
|
||||
builtin_to_scode(Env, oracle_query, [_Oracle, _Question, _QFee, _QTTL, _RTTL, _QType, _RType] = Args) ->
|
||||
@@ -536,8 +635,35 @@ builtin_to_scode(Env, aens_transfer, [_Sign, _From, _To, _Name] = Args) ->
|
||||
builtin_to_scode(Env, aens_revoke, [_Sign, _Account, _Name] = Args) ->
|
||||
call_to_scode(Env, [aeb_fate_ops:aens_revoke(?a, ?a, ?a),
|
||||
tuple(0)], Args);
|
||||
builtin_to_scode(Env, aens_update, [_Sign, _Account, _NameString, _TTL, _ClientTTL, _Pointers] = Args) ->
|
||||
call_to_scode(Env, [aeb_fate_ops:aens_update(?a, ?a, ?a, ?a, ?a, ?a),
|
||||
tuple(0)], Args);
|
||||
builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
|
||||
call_to_scode(Env, aeb_fate_ops:aens_lookup(?a, ?a), Args);
|
||||
builtin_to_scode(_Env, auth_tx_hash, []) ->
|
||||
[aeb_fate_ops:auth_tx_hash(?a)].
|
||||
[aeb_fate_ops:auth_tx_hash(?a)];
|
||||
builtin_to_scode(_Env, auth_tx, []) ->
|
||||
[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 --
|
||||
|
||||
@@ -564,8 +690,14 @@ op_to_scode(map_to_list) -> aeb_fate_ops:map_to_list(?a, ?a);
|
||||
op_to_scode(map_delete) -> aeb_fate_ops:map_delete(?a, ?a, ?a);
|
||||
op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a);
|
||||
op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a);
|
||||
op_to_scode(string_length) -> aeb_fate_ops:str_length(?a, ?a);
|
||||
op_to_scode(string_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a);
|
||||
op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a);
|
||||
op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a);
|
||||
op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a);
|
||||
op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a);
|
||||
op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a);
|
||||
op_to_scode(stringinternal_to_upper) -> aeb_fate_ops:str_to_upper(?a, ?a);
|
||||
op_to_scode(char_to_int) -> aeb_fate_ops:char_to_int(?a, ?a);
|
||||
op_to_scode(char_from_int) -> aeb_fate_ops:char_from_int(?a, ?a);
|
||||
op_to_scode(bits_set) -> aeb_fate_ops:bits_set(?a, ?a, ?a);
|
||||
op_to_scode(bits_clear) -> aeb_fate_ops:bits_clear(?a, ?a, ?a);
|
||||
op_to_scode(bits_test) -> aeb_fate_ops:bits_test(?a, ?a, ?a);
|
||||
@@ -584,9 +716,33 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a,
|
||||
op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a);
|
||||
op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a);
|
||||
op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a);
|
||||
op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a);
|
||||
op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a);
|
||||
op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a).
|
||||
op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a);
|
||||
op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a);
|
||||
op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_neg) -> aeb_fate_ops:bls12_381_g1_neg(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_norm) -> aeb_fate_ops:bls12_381_g1_norm(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_valid) -> aeb_fate_ops:bls12_381_g1_valid(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_is_zero) -> aeb_fate_ops:bls12_381_g1_is_zero(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_add) -> aeb_fate_ops:bls12_381_g1_add(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g1_mul) -> aeb_fate_ops:bls12_381_g1_mul(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_neg) -> aeb_fate_ops:bls12_381_g2_neg(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_norm) -> aeb_fate_ops:bls12_381_g2_norm(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_valid) -> aeb_fate_ops:bls12_381_g2_valid(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_is_zero) -> aeb_fate_ops:bls12_381_g2_is_zero(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_add) -> aeb_fate_ops:bls12_381_g2_add(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_g2_mul) -> aeb_fate_ops:bls12_381_g2_mul(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_gt_inv) -> aeb_fate_ops:bls12_381_gt_inv(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_gt_add) -> aeb_fate_ops:bls12_381_gt_add(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_gt_mul) -> aeb_fate_ops:bls12_381_gt_mul(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_gt_pow) -> aeb_fate_ops:bls12_381_gt_pow(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_gt_is_one) -> aeb_fate_ops:bls12_381_gt_is_one(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_pairing) -> aeb_fate_ops:bls12_381_pairing(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_miller_loop) -> aeb_fate_ops:bls12_381_miller_loop(?a, ?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_final_exp) -> aeb_fate_ops:bls12_381_final_exp(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_int_to_fr) -> aeb_fate_ops:bls12_381_int_to_fr(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_int_to_fp) -> aeb_fate_ops:bls12_381_int_to_fp(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_fr_to_int) -> aeb_fate_ops:bls12_381_fr_to_int(?a, ?a);
|
||||
op_to_scode(mcl_bls12_381_fp_to_int) -> aeb_fate_ops:bls12_381_fp_to_int(?a, ?a).
|
||||
|
||||
%% PUSH and STORE ?a are the same, so we use STORE to make optimizations
|
||||
%% easier, and specialize to PUSH (which is cheaper) at the end.
|
||||
@@ -600,7 +756,7 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
|
||||
|
||||
optimize_scode(Funs, Options) ->
|
||||
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
|
||||
Funs).
|
||||
Funs).
|
||||
|
||||
flatten(missing) -> missing;
|
||||
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
||||
@@ -734,6 +890,7 @@ attributes(I) ->
|
||||
{'CALL', A} -> Impure(?a, [A]);
|
||||
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
||||
{'CALL_PGR', A, _, B, C, D, E, F} -> Impure(?a, [A, B, C, D, E, F]);
|
||||
{'CALL_T', A} -> Impure(pc, [A]);
|
||||
{'CALL_VALUE', A} -> Pure(A, []);
|
||||
{'JUMP', _} -> Impure(pc, []);
|
||||
@@ -815,6 +972,7 @@ attributes(I) ->
|
||||
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
|
||||
{'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]);
|
||||
{'AUTH_TX_HASH', A} -> Pure(A, []);
|
||||
{'AUTH_TX', A} -> Pure(A, []);
|
||||
{'BYTES_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
|
||||
@@ -831,6 +989,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, []);
|
||||
@@ -853,12 +1012,48 @@ attributes(I) ->
|
||||
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
|
||||
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]);
|
||||
{'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]);
|
||||
{'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||
{'ORACLE_EXPIRY', A, B} -> Impure(A, [B]);
|
||||
{'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]);
|
||||
{'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]);
|
||||
{'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
|
||||
'AENS_UPDATE' -> Impure(none, []);%% TODO
|
||||
{'AENS_UPDATE', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
|
||||
{'AENS_TRANSFER', A, B, C, D} -> Impure(none, [A, B, C, D]);
|
||||
{'AENS_REVOKE', A, B, C} -> Impure(none, [A, B, C]);
|
||||
{'AENS_LOOKUP', A, B} -> Impure(A, [B]);
|
||||
{'BLS12_381_G1_NEG', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G1_NORM', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G1_VALID', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G1_IS_ZERO', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G1_ADD', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_G1_MUL', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_G2_NEG', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G2_NORM', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G2_VALID', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G2_IS_ZERO', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_G2_ADD', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_G2_MUL', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_GT_INV', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_GT_ADD', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_GT_MUL', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_GT_POW', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_GT_IS_ONE', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_PAIRING', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_MILLER_LOOP', A, B, C} -> Pure(A, [B, C]);
|
||||
{'BLS12_381_FINAL_EXP', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_INT_TO_FR', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_INT_TO_FP', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_FR_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'BLS12_381_FP_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'STR_TO_LIST', A, B} -> Pure(A, [B]);
|
||||
{'STR_FROM_LIST', A, B} -> Pure(A, [B]);
|
||||
{'STR_TO_UPPER', A, B} -> Pure(A, [B]);
|
||||
{'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, [])
|
||||
@@ -944,7 +1139,8 @@ simpl_top(I, Code, Options) ->
|
||||
simpl_top(0, I, Code, _Options) ->
|
||||
code_error({optimizer_out_of_fuel, I, Code});
|
||||
simpl_top(Fuel, I, Code, Options) ->
|
||||
apply_rules(Fuel, rules(), I, Code, Options).
|
||||
Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
|
||||
apply_rules(Fuel, Rules, I, Code, Options).
|
||||
|
||||
apply_rules(Fuel, Rules, I, Code, Options) ->
|
||||
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
|
||||
@@ -971,29 +1167,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
|
||||
-define(RULE(Name), {Name, fun Name/2}).
|
||||
|
||||
merge_rules() ->
|
||||
[?RULE(r_push_consume),
|
||||
?RULE(r_one_shot_var),
|
||||
?RULE(r_write_to_dead_var),
|
||||
?RULE(r_inline_switch_target)
|
||||
[?RULE(optimize_push_consume),
|
||||
?RULE(optimize_one_shot_var),
|
||||
?RULE(optimize_write_to_dead_var),
|
||||
?RULE(optimize_inline_switch_target)
|
||||
].
|
||||
|
||||
rules() ->
|
||||
merge_rules() ++
|
||||
[?RULE(r_swap_push),
|
||||
?RULE(r_swap_pop),
|
||||
?RULE(r_swap_write),
|
||||
?RULE(r_constant_propagation),
|
||||
?RULE(r_prune_impossible_branches),
|
||||
?RULE(r_single_successful_branch),
|
||||
?RULE(r_inline_store),
|
||||
?RULE(r_float_switch_body)
|
||||
[?RULE(optimize_swap_push),
|
||||
?RULE(optimize_swap_pop),
|
||||
?RULE(optimize_swap_write),
|
||||
?RULE(optimize_constant_propagation),
|
||||
?RULE(optimize_prune_impossible_branches),
|
||||
?RULE(optimize_single_successful_branch),
|
||||
?RULE(optimize_inline_store),
|
||||
?RULE(optimize_float_switch_body)
|
||||
].
|
||||
|
||||
%% Removing pushes that are immediately consumed.
|
||||
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
inline_push(Ann1, A, 0, Code, []);
|
||||
%% Writing directly to memory instead of going through the accumulator.
|
||||
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
IsPush =
|
||||
case op_view(I) of
|
||||
{_, ?a, _} -> true;
|
||||
@@ -1005,7 +1201,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
end,
|
||||
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
|
||||
true -> false end;
|
||||
r_push_consume(_, _) -> false.
|
||||
optimize_push_consume(_, _) -> false.
|
||||
|
||||
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
|
||||
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
|
||||
@@ -1038,7 +1234,7 @@ split_stack_arg(N, [A | As], Acc) ->
|
||||
split_stack_arg(N1, As, [A | Acc]).
|
||||
|
||||
%% Move PUSHes past non-stack instructions.
|
||||
r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
case op_view(PushI) of
|
||||
{_, ?a, _} ->
|
||||
case independent(Push, I) of
|
||||
@@ -1049,10 +1245,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_push(_, _) -> false.
|
||||
optimize_swap_push(_, _) -> false.
|
||||
|
||||
%% Move non-stack instruction past POPs.
|
||||
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
case independent(IA, JA) of
|
||||
true ->
|
||||
case {op_view(I), op_view(J)} of
|
||||
@@ -1060,7 +1256,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
{_, false} -> false;
|
||||
{{_, IR, IAs}, {_, RJ, JAs}} ->
|
||||
NonStackI = not lists:member(?a, [IR | IAs]),
|
||||
%% RJ /= ?a to not conflict with r_swap_push
|
||||
%% RJ /= ?a to not conflict with optimize_swap_push
|
||||
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
|
||||
case NonStackI andalso PopJ of
|
||||
false -> false;
|
||||
@@ -1071,22 +1267,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
end;
|
||||
false -> false
|
||||
end;
|
||||
r_swap_pop(_, _) -> false.
|
||||
optimize_swap_pop(_, _) -> false.
|
||||
|
||||
%% Match up writes to variables with instructions further down.
|
||||
r_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
optimize_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
case {var_writes(I), independent(I, J)} of
|
||||
{[_], true} ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1], I1, Code);
|
||||
optimize_swap_write([J1], I1, Code);
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_write(_, _) -> false.
|
||||
optimize_swap_write(_, _) -> false.
|
||||
|
||||
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code);
|
||||
r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
optimize_swap_write([J1 | Pre], I1, Code);
|
||||
optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
case apply_rules_once(merge_rules(), I, Code0) of
|
||||
{_Rule, New, Rest} ->
|
||||
{lists:reverse(Pre) ++ New, Rest};
|
||||
@@ -1095,27 +1291,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
false -> false;
|
||||
true ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code)
|
||||
optimize_swap_write([J1 | Pre], I1, Code)
|
||||
end
|
||||
end;
|
||||
r_swap_write(_, _, _) -> false.
|
||||
optimize_swap_write(_, _, _) -> false.
|
||||
|
||||
%% Precompute instructions with known values
|
||||
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(false)}},
|
||||
Cons1 = case R of
|
||||
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
|
||||
_ -> Cons
|
||||
end,
|
||||
{[Cons1, Store], Code};
|
||||
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(true)}},
|
||||
Nil1 = case R of
|
||||
?a -> {i, Ann1, {'NIL', ?void}};
|
||||
_ -> Nil
|
||||
end,
|
||||
{[Nil1, Store], Code};
|
||||
r_constant_propagation({i, Ann, I}, Code) ->
|
||||
optimize_constant_propagation({i, Ann, I}, Code) ->
|
||||
case op_view(I) of
|
||||
false -> false;
|
||||
{Op, R, As} ->
|
||||
@@ -1129,7 +1325,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
|
||||
end
|
||||
end
|
||||
end;
|
||||
r_constant_propagation(_, _) -> false.
|
||||
optimize_constant_propagation(_, _) -> false.
|
||||
|
||||
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
|
||||
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
|
||||
@@ -1148,12 +1344,12 @@ eval_op('NOT', [false]) -> true;
|
||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
||||
|
||||
%% Prune impossible branches from switches
|
||||
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
case pick_branch(Type, V, Alts) of
|
||||
false -> false;
|
||||
Alt -> {Alt, Code}
|
||||
end;
|
||||
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
Alts1 = [if V -> missing; true -> False end,
|
||||
if V -> True; true -> missing end],
|
||||
case Alts == Alts1 of
|
||||
@@ -1164,7 +1360,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
|
||||
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
|
||||
case {R, lists:nth(Tag + 1, Alts)} of
|
||||
{_, missing} ->
|
||||
@@ -1180,7 +1376,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
|
||||
false -> {Alt, Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(_, _) -> false.
|
||||
optimize_prune_impossible_branches(_, _) -> false.
|
||||
|
||||
pick_branch(boolean, V, [False, True]) when V == true; V == false ->
|
||||
Alt = if V -> True; true -> False end,
|
||||
@@ -1193,7 +1389,7 @@ pick_branch(_Type, _V, _Alts) ->
|
||||
|
||||
%% If there's a single branch that doesn't abort we can push the code for that
|
||||
%% out of the switch.
|
||||
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
case push_code_out_of_switch([Def | Alts]) of
|
||||
{_, none} -> false;
|
||||
{_, many} -> false;
|
||||
@@ -1201,7 +1397,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
{[Def1 | Alts1], PushedOut} ->
|
||||
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
|
||||
end;
|
||||
r_single_successful_branch(_, _) -> false.
|
||||
optimize_single_successful_branch(_, _) -> false.
|
||||
|
||||
push_code_out_of_switch([]) -> {[], none};
|
||||
push_code_out_of_switch([Alt | Alts]) ->
|
||||
@@ -1237,7 +1433,7 @@ does_abort({switch, _, _, Alts, Def}) ->
|
||||
does_abort(_) -> false.
|
||||
|
||||
%% STORE R A, SWITCH R --> SWITCH A
|
||||
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||
optimize_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||
Ann1 =
|
||||
case is_reg(A) of
|
||||
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
|
||||
@@ -1256,18 +1452,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
|
||||
end;
|
||||
_ -> false %% impossible
|
||||
end;
|
||||
r_inline_switch_target(_, _) -> false.
|
||||
optimize_inline_switch_target(_, _) -> false.
|
||||
|
||||
%% Float switch-body to closest switch
|
||||
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
{[], [J1, I1 | Code]};
|
||||
r_float_switch_body(_, _) -> false.
|
||||
optimize_float_switch_body(_, _) -> false.
|
||||
|
||||
%% Inline stores
|
||||
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
{[], Code};
|
||||
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
%% Not when A is var unless updating the annotations properly.
|
||||
Inline = case A of
|
||||
{arg, _} -> true;
|
||||
@@ -1275,13 +1471,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
{store, _} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if Inline -> r_inline_store([I], false, R, A, Code);
|
||||
if Inline -> optimize_inline_store([I], false, R, A, Code);
|
||||
true -> false end;
|
||||
r_inline_store(_, _) -> false.
|
||||
optimize_inline_store(_, _) -> false.
|
||||
|
||||
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
r_inline_store([I | Acc], Progress, R, A, Code);
|
||||
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
optimize_inline_store([I | Acc], Progress, R, A, Code);
|
||||
optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
#{ write := W } = attributes(I),
|
||||
Inl = fun(X) when X == R -> A; (X) -> X end,
|
||||
case live_in(R, Ann) of
|
||||
@@ -1301,14 +1497,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
case lists:member(W, [R, A]) of
|
||||
true when Progress1 -> {lists:reverse(Acc1), Code};
|
||||
true -> false;
|
||||
false -> r_inline_store(Acc1, Progress1, R, A, Code)
|
||||
false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
|
||||
end
|
||||
end;
|
||||
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
r_inline_store(_, false, _, _, _) -> false.
|
||||
optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
optimize_inline_store(_, false, _, _, _) -> false.
|
||||
|
||||
%% Shortcut write followed by final read
|
||||
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
case op_view(I) of
|
||||
{Op, R = {var, _}, As} ->
|
||||
Copy = case J of
|
||||
@@ -1322,11 +1518,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_one_shot_var(_, _) -> false.
|
||||
optimize_one_shot_var(_, _) -> false.
|
||||
|
||||
%% Remove writes to dead variables
|
||||
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
optimize_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
#{ pure := Pure } = attributes(I),
|
||||
case op_view(I) of
|
||||
{_Op, R, As} when R /= ?a, Pure ->
|
||||
@@ -1339,9 +1535,10 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_write_to_dead_var(_, _) -> false.
|
||||
optimize_write_to_dead_var(_, _) -> false.
|
||||
|
||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
|
||||
op_view(T) when is_tuple(T) ->
|
||||
[Op, R | As] = tuple_to_list(T),
|
||||
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
||||
@@ -1611,6 +1808,10 @@ split_calls(Ref, [], Acc, Blocks) ->
|
||||
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) ->
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Intermediate Code for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode).
|
||||
|
||||
-export([new/1,
|
||||
pp/1,
|
||||
set_name/2,
|
||||
set_namespace/2,
|
||||
set_payable/2,
|
||||
enter_namespace/2,
|
||||
get_namespace/1,
|
||||
in_main_contract/1,
|
||||
qualify/2,
|
||||
set_functions/2,
|
||||
map_typerep/2,
|
||||
option_typerep/1,
|
||||
get_constructor_tag/2]).
|
||||
|
||||
-export_type([icode/0]).
|
||||
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
|
||||
|
||||
-type bindings() :: any().
|
||||
-type fun_dec() :: { string()
|
||||
, [modifier()]
|
||||
, arg_list()
|
||||
, expr()
|
||||
, aeb_aevm_data:type()}.
|
||||
|
||||
-type modifier() :: private | stateful.
|
||||
|
||||
-type type_name() :: string() | [string()].
|
||||
|
||||
-type icode() :: #{ contract_name => string()
|
||||
, functions => [fun_dec()]
|
||||
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||
, env => [bindings()]
|
||||
, state_type => aeb_aevm_data:type()
|
||||
, event_type => aeb_aevm_data:type()
|
||||
, types => #{ type_name() => type_def() }
|
||||
, type_vars => #{ string() => aeb_aevm_data:type() }
|
||||
, constructors => #{ [string()] => integer() } %% name to tag
|
||||
, options => [any()]
|
||||
, payable => boolean()
|
||||
}.
|
||||
|
||||
pp(Icode) ->
|
||||
%% TODO: Actually do *Pretty* printing.
|
||||
io:format("~p~n", [Icode]).
|
||||
|
||||
-spec new([any()]) -> icode().
|
||||
new(Options) ->
|
||||
#{ contract_name => ""
|
||||
, functions => []
|
||||
, env => new_env()
|
||||
%% Default to unit type for state and event
|
||||
, state_type => {tuple, []}
|
||||
, event_type => {tuple, []}
|
||||
, types => builtin_types()
|
||||
, type_vars => #{}
|
||||
, constructors => builtin_constructors()
|
||||
, options => Options
|
||||
, payable => false }.
|
||||
|
||||
builtin_types() ->
|
||||
Word = fun([]) -> word end,
|
||||
#{ "bool" => Word
|
||||
, "int" => Word
|
||||
, "char" => Word
|
||||
, "bits" => Word
|
||||
, "string" => fun([]) -> string end
|
||||
, "address" => Word
|
||||
, "hash" => Word
|
||||
, "unit" => fun([]) -> {tuple, []} end
|
||||
, "signature" => fun([]) -> {tuple, [word, word]} end
|
||||
, "oracle" => fun([_, _]) -> word end
|
||||
, "oracle_query" => fun([_, _]) -> word end
|
||||
, "list" => fun([A]) -> {list, A} end
|
||||
, "option" => fun([A]) -> {variant, [[], [A]]} end
|
||||
, "map" => fun([K, V]) -> map_typerep(K, V) end
|
||||
, ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end
|
||||
}.
|
||||
|
||||
builtin_constructors() ->
|
||||
#{ ["RelativeTTL"] => 0
|
||||
, ["FixedTTL"] => 1
|
||||
, ["None"] => 0
|
||||
, ["Some"] => 1 }.
|
||||
|
||||
map_typerep(K, V) ->
|
||||
{map, K, V}.
|
||||
|
||||
option_typerep(A) ->
|
||||
{variant, [[], [A]]}.
|
||||
|
||||
new_env() ->
|
||||
[].
|
||||
|
||||
-spec set_name(string(), icode()) -> icode().
|
||||
set_name(Name, Icode) ->
|
||||
maps:put(contract_name, Name, Icode).
|
||||
|
||||
-spec set_payable(boolean(), icode()) -> icode().
|
||||
set_payable(Payable, Icode) ->
|
||||
maps:put(payable, Payable, Icode).
|
||||
|
||||
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
|
||||
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
||||
|
||||
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
|
||||
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
|
||||
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
|
||||
enter_namespace(NS, Icode) ->
|
||||
Icode#{ namespace => NS }.
|
||||
|
||||
-spec in_main_contract(icode()) -> boolean().
|
||||
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
|
||||
in_main_contract(_Icode) -> false.
|
||||
|
||||
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
get_namespace(Icode) -> maps:get(namespace, Icode, false).
|
||||
|
||||
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
qualify(X, Icode) ->
|
||||
case get_namespace(Icode) of
|
||||
false -> X;
|
||||
NS -> aeso_syntax:qualify(NS, X)
|
||||
end.
|
||||
|
||||
-spec set_functions([fun_dec()], icode()) -> icode().
|
||||
set_functions(NewFuns, Icode) ->
|
||||
maps:put(functions, NewFuns, Icode).
|
||||
|
||||
-spec get_constructor_tag([string()], icode()) -> integer().
|
||||
get_constructor_tag(Name, #{constructors := Constructors}) ->
|
||||
case maps:get(Name, Constructors, undefined) of
|
||||
undefined -> error({undefined_constructor, Name});
|
||||
Tag -> Tag
|
||||
end.
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
|
||||
|
||||
-record(arg, {name::string(), type::?Type()}).
|
||||
|
||||
-type expr() :: term().
|
||||
-type arg() :: #arg{name::string(), type::?Type()}.
|
||||
-type arg_list() :: [arg()].
|
||||
|
||||
-record(fun_dec, { name :: string()
|
||||
, args :: arg_list()
|
||||
, body :: expr()}).
|
||||
|
||||
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
|
||||
|
||||
-record(prim_call_contract,
|
||||
{ gas :: expr()
|
||||
, address :: expr()
|
||||
, value :: expr()
|
||||
, arg :: expr()
|
||||
, type_hash:: expr()
|
||||
}).
|
||||
|
||||
-record(prim_balance, { address :: expr() }).
|
||||
-record(prim_block_hash, { height :: expr() }).
|
||||
-record(prim_put, { state :: expr() }).
|
||||
|
||||
-record(integer, {value :: integer()}).
|
||||
|
||||
-record(tuple, {cpts :: [expr()]}).
|
||||
|
||||
-record(list, {elems :: [expr()]}).
|
||||
|
||||
-record(unop, { op :: term()
|
||||
, rand :: expr()}).
|
||||
|
||||
-record(binop, { op :: term()
|
||||
, left :: expr()
|
||||
, right :: expr()}).
|
||||
|
||||
-record(ifte, { decision :: expr()
|
||||
, then :: expr()
|
||||
, else :: expr()}).
|
||||
|
||||
-record(switch, { expr :: expr()
|
||||
, cases :: [{expr(),expr()}]}).
|
||||
|
||||
-record(funcall, { function :: expr()
|
||||
, args :: [expr()]}).
|
||||
|
||||
-record(lambda, { args :: arg_list(),
|
||||
body :: expr()}).
|
||||
|
||||
-record(missing_field, { format :: string()
|
||||
, args :: [term()]}).
|
||||
|
||||
-record(seq, {exprs :: [expr()]}).
|
||||
|
||||
-record(event, {topics :: [expr()], payload :: expr()}).
|
||||
@@ -1,983 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Translator from Aesophia Icode to Aevm Assebly
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode_to_asm).
|
||||
|
||||
-export([convert/2]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
i(Code) -> aeb_opcodes:mnemonic(Code).
|
||||
|
||||
%% We don't track purity or statefulness in the type checker yet.
|
||||
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
|
||||
|
||||
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
|
||||
|
||||
convert(#{ contract_name := _ContractName
|
||||
, state_type := StateType
|
||||
, functions := Functions
|
||||
},
|
||||
_Options) ->
|
||||
%% Create a function dispatcher
|
||||
DispatchFun = {"%main", [], [{"arg", "_"}],
|
||||
{switch, {var_ref, "arg"},
|
||||
[{{tuple, [fun_hash(Fun),
|
||||
{tuple, make_args(Args)}]},
|
||||
icode_seq([ hack_return_address(Fun, length(Args) + 1) ] ++
|
||||
[ {funcall, {var_ref, FName}, make_args(Args)}]
|
||||
)}
|
||||
|| Fun={FName, _, Args, _,_TypeRep} <- Functions, is_public(Fun) ]},
|
||||
word},
|
||||
NewFunctions = Functions ++ [DispatchFun],
|
||||
%% Create a function environment
|
||||
Funs = [{Name, length(Args), make_ref()}
|
||||
|| {Name, _Attrs, Args, _Body, _Type} <- NewFunctions],
|
||||
%% Create dummy code to call the main function with one argument
|
||||
%% taken from the stack
|
||||
StopLabel = make_ref(),
|
||||
StatefulStopLabel = make_ref(),
|
||||
MainFunction = lookup_fun(Funs, "%main"),
|
||||
|
||||
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
|
||||
|
||||
DispatchCode = [%% push two return addresses to stop, one for stateful
|
||||
%% functions and one for non-stateful functions.
|
||||
push_label(StatefulStopLabel),
|
||||
push_label(StopLabel),
|
||||
%% The calldata is already on the stack when we start. Put
|
||||
%% it on top (also reorders StatefulStop and Stop).
|
||||
swap(2),
|
||||
|
||||
jump(MainFunction),
|
||||
jumpdest(StatefulStopLabel),
|
||||
|
||||
%% We need to encode the state type and put it
|
||||
%% underneath the return value.
|
||||
assemble_expr(Funs, [], nontail, StateTypeValue), %% StateT Ret
|
||||
swap(1), %% Ret StateT
|
||||
|
||||
%% We should also change the state value at address 0 to a
|
||||
%% pointer to the state value (to allow 0 to represent an
|
||||
%% unchanged state).
|
||||
i(?MSIZE), %% Ptr
|
||||
push(0), i(?MLOAD), %% Val Ptr
|
||||
i(?MSIZE), i(?MSTORE), %% Ptr Mem[Ptr] := Val
|
||||
push(0), i(?MSTORE), %% Mem[0] := Ptr
|
||||
|
||||
%% The pointer to the return value is on top of
|
||||
%% the stack, but the return instruction takes two
|
||||
%% stack arguments.
|
||||
push(0),
|
||||
i(?RETURN),
|
||||
jumpdest(StopLabel),
|
||||
%% Set state pointer to 0 to indicate that we didn't change state
|
||||
push(0), dup(1), i(?MSTORE),
|
||||
%% Same as StatefulStopLabel above
|
||||
push(0),
|
||||
i(?RETURN)
|
||||
],
|
||||
%% Code is a deep list of instructions, containing labels and
|
||||
%% references to them. Labels take the form {'JUMPDEST', Ref}, and
|
||||
%% references take the form {push_label, Ref}, which is translated
|
||||
%% into a PUSH instruction.
|
||||
Code = [assemble_function(Funs, Name, Args, Body)
|
||||
|| {Name, _, Args, Body, _Type} <- NewFunctions],
|
||||
resolve_references(
|
||||
[%% i(?COMMENT), "CONTRACT: " ++ ContractName,
|
||||
DispatchCode,
|
||||
Code]).
|
||||
|
||||
%% Generate error on correct format.
|
||||
|
||||
gen_error(Error) ->
|
||||
error({code_errors, [Error]}).
|
||||
|
||||
make_args(Args) ->
|
||||
[{var_ref, [I-1 + $a]} || I <- lists:seq(1, length(Args))].
|
||||
|
||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||
{integer, Hash}.
|
||||
|
||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||
%% one for stateful functions and the bottom one for non-stateful.
|
||||
hack_return_address(Fun, N) ->
|
||||
case is_stateful(Fun) of
|
||||
true -> {inline_asm, [i(?MSIZE)]};
|
||||
false ->
|
||||
{inline_asm, %% X1 .. XN State NoState
|
||||
[ dup(N + 2) %% NoState X1 .. XN State NoState
|
||||
, swap(N + 1) %% State X1 .. XN NoState NoState
|
||||
]} %% Top of the stack will be discarded.
|
||||
end.
|
||||
|
||||
assemble_function(Funs, Name, Args, Body) ->
|
||||
[jumpdest(lookup_fun(Funs, Name)),
|
||||
assemble_expr(Funs, lists:reverse(Args), tail, Body),
|
||||
%% swap return value and first argument
|
||||
pop_args(length(Args)),
|
||||
swap(1),
|
||||
i(?JUMP)].
|
||||
|
||||
%% {seq, Es} - should be "one" operation in terms of stack content
|
||||
%% i.e. after the `seq` there should be one new element on the stack.
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E]}) ->
|
||||
assemble_expr(Funs, Stack, Tail, E);
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E | Es]}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_expr(Funs, Stack, Tail, {seq, Es})];
|
||||
assemble_expr(_Funs, _Stack, _Tail, {inline_asm, Code}) ->
|
||||
Code; %% Unsafe! Code should take care to respect the stack!
|
||||
assemble_expr(Funs, Stack, _TailPosition, {var_ref, Id}) ->
|
||||
case lists:keymember(Id, 1, Stack) of
|
||||
true ->
|
||||
dup(lookup_var(Id, Stack));
|
||||
false ->
|
||||
%% Build a closure
|
||||
%% When a top-level fun is called directly, we do not
|
||||
%% reach this case.
|
||||
Eta = make_ref(),
|
||||
Continue = make_ref(),
|
||||
[i(?MSIZE),
|
||||
push_label(Eta),
|
||||
dup(2),
|
||||
i(?MSTORE),
|
||||
jump(Continue),
|
||||
%% the code of the closure
|
||||
jumpdest(Eta),
|
||||
%% pop the pointer to the function
|
||||
pop(1),
|
||||
jump(lookup_fun(Funs, Id)),
|
||||
jumpdest(Continue)]
|
||||
end;
|
||||
assemble_expr(_, _, _, {missing_field, Format, Args}) ->
|
||||
io:format(Format, Args),
|
||||
gen_error(missing_field);
|
||||
assemble_expr(_Funs, _Stack, _, {integer, N}) ->
|
||||
push(N);
|
||||
assemble_expr(Funs, Stack, _, {tuple, Cpts}) ->
|
||||
%% We build tuples right-to-left, so that the first write to the
|
||||
%% tuple extends the memory size. Because we use ?MSIZE as the
|
||||
%% heap pointer, we must allocate the tuple AFTER computing the
|
||||
%% first element.
|
||||
%% We store elements into the tuple as soon as possible, to avoid
|
||||
%% keeping them for a long time on the stack.
|
||||
case lists:reverse(Cpts) of
|
||||
[] ->
|
||||
i(?MSIZE);
|
||||
[Last|Rest] ->
|
||||
[assemble_expr(Funs, Stack, nontail, Last),
|
||||
%% allocate the tuple memory
|
||||
i(?MSIZE),
|
||||
%% compute address of last word
|
||||
push(32 * (length(Cpts) - 1)), i(?ADD),
|
||||
%% Stack: <last-value> <pointer>
|
||||
%% Write value to memory (allocates the tuple)
|
||||
swap(1), dup(2), i(?MSTORE),
|
||||
%% Stack: pointer to last word written
|
||||
[[%% Update pointer to next word to be written
|
||||
push(32), swap(1), i(?SUB),
|
||||
%% Compute element
|
||||
assemble_expr(Funs, [pointer|Stack], nontail, A),
|
||||
%% Write element to memory
|
||||
dup(2), i(?MSTORE)]
|
||||
%% And we leave a pointer to the last word written on
|
||||
%% the stack
|
||||
|| A <- Rest]]
|
||||
%% The pointer to the entire tuple is on the stack
|
||||
end;
|
||||
assemble_expr(_Funs, _Stack, _, {list, []}) ->
|
||||
%% Use Erik's value of -1 for []
|
||||
[push(0), i(?NOT)];
|
||||
assemble_expr(Funs, Stack, _, {list, [A|B]}) ->
|
||||
assemble_expr(Funs, Stack, nontail, {tuple, [A, {list, B}]});
|
||||
assemble_expr(Funs, Stack, _, {unop, '!', A}) ->
|
||||
case A of
|
||||
{binop, Logical, _, _} when Logical=='&&'; Logical=='||' ->
|
||||
assemble_expr(Funs, Stack, nontail, {ifte, A, {integer, 0}, {integer, 1}});
|
||||
_ ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
i(?ISZERO)
|
||||
]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, _, {event, Topics, Payload}) ->
|
||||
[assemble_exprs(Funs, Stack, Topics ++ [Payload]),
|
||||
case length(Topics) of
|
||||
0 -> i(?LOG0);
|
||||
1 -> i(?LOG1);
|
||||
2 -> i(?LOG2);
|
||||
3 -> i(?LOG3);
|
||||
4 -> i(?LOG4)
|
||||
end, i(?MSIZE)];
|
||||
assemble_expr(Funs, Stack, _, {unop, Op, A}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_prefix(Op)];
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '&&', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, B, {integer, 0}});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '||', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, {integer, 1}, B});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '::', A, B}) ->
|
||||
%% Take advantage of optimizations in tuple construction.
|
||||
assemble_expr(Funs, Stack, Tail, {tuple, [A, B]});
|
||||
assemble_expr(Funs, Stack, _, {binop, Op, A, B}) ->
|
||||
%% EEVM binary instructions take their first argument from the top
|
||||
%% of the stack, so to get operands on the stack in the right
|
||||
%% order, we evaluate from right to left.
|
||||
[assemble_expr(Funs, Stack, nontail, B),
|
||||
assemble_expr(Funs, [dummy|Stack], nontail, A),
|
||||
assemble_infix(Op)];
|
||||
assemble_expr(Funs, Stack, _, {lambda, Args, Body}) ->
|
||||
Function = make_ref(),
|
||||
FunBody = make_ref(),
|
||||
Continue = make_ref(),
|
||||
NoMatch = make_ref(),
|
||||
FreeVars = free_vars({lambda, Args, Body}),
|
||||
{NewVars, MatchingCode} = assemble_pattern(FunBody, NoMatch, {tuple, [{var_ref, "_"}|FreeVars]}),
|
||||
BodyCode = assemble_expr(Funs, NewVars ++ lists:reverse([ {Arg#arg.name, Arg#arg.type} || Arg <- Args ]), tail, Body),
|
||||
[assemble_expr(Funs, Stack, nontail, {tuple, [{label, Function}|FreeVars]}),
|
||||
jump(Continue), %% will be optimized away
|
||||
jumpdest(Function),
|
||||
%% A pointer to the closure is on the stack
|
||||
MatchingCode,
|
||||
jumpdest(FunBody),
|
||||
BodyCode,
|
||||
pop_args(length(Args)+length(NewVars)),
|
||||
swap(1),
|
||||
i(?JUMP),
|
||||
jumpdest(NoMatch), %% dead code--raise an exception just in case
|
||||
push(0),
|
||||
i(?NOT),
|
||||
i(?MLOAD),
|
||||
i(?STOP),
|
||||
jumpdest(Continue)];
|
||||
assemble_expr(_, _, _, {label, Label}) ->
|
||||
push_label(Label);
|
||||
assemble_expr(Funs, Stack, nontail, {funcall, Fun, Args}) ->
|
||||
Return = make_ref(),
|
||||
%% This is the obvious code:
|
||||
%% [{push_label, Return},
|
||||
%% assemble_exprs(Funs, [return_address|Stack], Args++[Fun]),
|
||||
%% 'JUMP',
|
||||
%% {'JUMPDEST', Return}];
|
||||
%% Its problem is that it stores the return address on the stack
|
||||
%% while the arguments are computed, which is unnecessary. To
|
||||
%% avoid that, we compute the last argument FIRST, and replace it
|
||||
%% with the return address using a SWAP.
|
||||
%%
|
||||
%% assemble_function leaves the code pointer of the function to
|
||||
%% call on top of the stack, and--if the function is not a
|
||||
%% top-level name--a pointer to its tuple of free variables. In
|
||||
%% either case a JUMP is the right way to call it.
|
||||
case Args of
|
||||
[] ->
|
||||
[push_label(Return),
|
||||
assemble_function(Funs, [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)];
|
||||
_ ->
|
||||
{Init, [Last]} = lists:split(length(Args) - 1, Args),
|
||||
[assemble_exprs(Funs, Stack, [Last|Init]),
|
||||
%% Put the return address in the right place, which also
|
||||
%% reorders the args correctly.
|
||||
push_label(Return),
|
||||
swap(length(Args)),
|
||||
assemble_function(Funs, [dummy || _ <- Args] ++ [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, tail, {funcall, Fun, Args}) ->
|
||||
IsTopLevel = is_top_level_fun(Stack, Fun),
|
||||
%% If the fun is not top-level, then it may refer to local
|
||||
%% variables and must be computed before stack shuffling.
|
||||
ArgsAndFun = Args++[Fun || not IsTopLevel],
|
||||
ComputeArgsAndFun = assemble_exprs(Funs, Stack, ArgsAndFun),
|
||||
%% Copy arguments back down the stack to the start of the frame
|
||||
ShuffleSpec = lists:seq(length(ArgsAndFun), 1, -1) ++ [discard || _ <- Stack],
|
||||
Shuffle = shuffle_stack(ShuffleSpec),
|
||||
[ComputeArgsAndFun, Shuffle,
|
||||
if IsTopLevel ->
|
||||
%% still need to compute function
|
||||
assemble_function(Funs, [], Fun);
|
||||
true ->
|
||||
%% need to unpack a closure
|
||||
[dup(1), i(?MLOAD)]
|
||||
end,
|
||||
i(?JUMP)];
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, Decision, Then, Else}) ->
|
||||
%% This compilation scheme introduces a lot of labels and
|
||||
%% jumps. Unnecessary ones are removed later in
|
||||
%% resolve_references.
|
||||
Close = make_ref(),
|
||||
ThenL = make_ref(),
|
||||
ElseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, Decision, ThenL, ElseL),
|
||||
jumpdest(ElseL),
|
||||
assemble_expr(Funs, Stack, Tail, Else),
|
||||
jump(Close),
|
||||
jumpdest(ThenL),
|
||||
assemble_expr(Funs, Stack, Tail, Then),
|
||||
jumpdest(Close)
|
||||
];
|
||||
assemble_expr(Funs, Stack, Tail, {switch, A, Cases}) ->
|
||||
Close = make_ref(),
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases),
|
||||
{'JUMPDEST', Close}];
|
||||
%% State primitives
|
||||
%% (A pointer to) the contract state is stored at address 0.
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_state) ->
|
||||
[push(0), i(?MLOAD)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, State),
|
||||
push(0), i(?MSTORE), %% We need something for the unit value on the stack,
|
||||
i(?MSIZE)]; %% MSIZE is the cheapest instruction.
|
||||
%% Environment primitives
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
||||
[i(?ADDRESS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
|
||||
[i(?CREATOR)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
||||
[i(?ORIGIN)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
||||
[i(?CALLER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_value) ->
|
||||
[i(?CALLVALUE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_price) ->
|
||||
[i(?GASPRICE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_left) ->
|
||||
[i(?GAS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_coinbase) ->
|
||||
[i(?COINBASE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_timestamp) ->
|
||||
[i(?TIMESTAMP)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_block_height) ->
|
||||
[i(?NUMBER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_difficulty) ->
|
||||
[i(?DIFFICULTY)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_limit) ->
|
||||
[i(?GASLIMIT)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_balance{ address = Addr }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Addr),
|
||||
i(?BALANCE)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_block_hash{ height = Height }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Height),
|
||||
i(?BLOCKHASH)];
|
||||
assemble_expr(Funs, Stack, _Tail,
|
||||
#prim_call_contract{ gas = Gas
|
||||
, address = To
|
||||
, value = Value
|
||||
, arg = Arg
|
||||
, type_hash= TypeHash
|
||||
}) ->
|
||||
%% ?CALL takes (from the top)
|
||||
%% Gas, To, Value, Arg, TypeHash, _OOffset,_OSize
|
||||
%% So assemble these in reverse order.
|
||||
[ assemble_exprs(Funs, Stack, [ {integer, 0}, {integer, 0}, TypeHash
|
||||
, Arg, Value, To, Gas ])
|
||||
, i(?CALL)
|
||||
].
|
||||
|
||||
|
||||
assemble_exprs(_Funs, _Stack, []) ->
|
||||
[];
|
||||
assemble_exprs(Funs, Stack, [E|Es]) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_exprs(Funs, [dummy|Stack], Es)].
|
||||
|
||||
assemble_decision(Funs, Stack, {binop, '&&', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Label, Else),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {binop, '||', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Then, Label),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {unop, '!', A}, Then, Else) ->
|
||||
assemble_decision(Funs, Stack, A, Else, Then);
|
||||
assemble_decision(Funs, Stack, {ifte, A, B, C}, Then, Else) ->
|
||||
TrueL = make_ref(),
|
||||
FalseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, TrueL, FalseL),
|
||||
jumpdest(TrueL), assemble_decision(Funs, Stack, B, Then, Else),
|
||||
jumpdest(FalseL), assemble_decision(Funs, Stack, C, Then, Else)];
|
||||
assemble_decision(Funs, Stack, Decision, Then, Else) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Decision),
|
||||
jump_if(Then), jump(Else)].
|
||||
|
||||
%% Entered with value to switch on on top of the stack
|
||||
%% Evaluate selected case, then jump to Close with result on the
|
||||
%% stack.
|
||||
assemble_cases(_Funs, _Stack, _Tail, _Close, []) ->
|
||||
%% No match! What should be do? There's no real way to raise an
|
||||
%% exception, except consuming all the gas.
|
||||
%% There should not be enough gas to do this:
|
||||
[push(1), i(?NOT),
|
||||
i(?MLOAD),
|
||||
%% now stop, so that jump optimizer realizes we will not fall
|
||||
%% through this code.
|
||||
i(?STOP)];
|
||||
assemble_cases(Funs, Stack, Tail, Close, [{Pattern, Body}|Cases]) ->
|
||||
Succeed = make_ref(),
|
||||
Fail = make_ref(),
|
||||
{NewVars, MatchingCode} =
|
||||
assemble_pattern(Succeed, Fail, Pattern),
|
||||
%% In the code that follows, if this is NOT the last case, then we
|
||||
%% save the value being switched on, and discard it on
|
||||
%% success. The code is simpler if this IS the last case.
|
||||
[[dup(1) || Cases /= []], %% save value for next case, if there is one
|
||||
MatchingCode,
|
||||
jumpdest(Succeed),
|
||||
%% Discard saved value, if we saved one
|
||||
[case NewVars of
|
||||
[] ->
|
||||
pop(1);
|
||||
[_] ->
|
||||
%% Special case for peep-hole optimization
|
||||
pop_args(1);
|
||||
_ ->
|
||||
[swap(length(NewVars)), pop(1)]
|
||||
end
|
||||
|| Cases/=[]],
|
||||
assemble_expr(Funs,
|
||||
case Cases of
|
||||
[] -> NewVars;
|
||||
_ -> reorder_vars(NewVars)
|
||||
end
|
||||
++Stack, Tail, Body),
|
||||
%% If the Body makes a tail call, then we will not return
|
||||
%% here--but it doesn't matter, because
|
||||
%% (a) the NewVars will be popped before the tailcall
|
||||
%% (b) the code below will be deleted since it is dead
|
||||
pop_args(length(NewVars)),
|
||||
jump(Close),
|
||||
jumpdest(Fail),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases)].
|
||||
|
||||
%% Entered with value to match on top of the stack.
|
||||
%% Generated code removes value, and
|
||||
%% - jumps to Fail if no match, or
|
||||
%% - binds variables, leaves them on the stack, and jumps to Succeed
|
||||
%% Result is a list of variables to add to the stack, and the matching
|
||||
%% code.
|
||||
assemble_pattern(Succeed, Fail, {integer, N}) ->
|
||||
{[], [push(N),
|
||||
i(?EQ),
|
||||
jump_if(Succeed),
|
||||
jump(Fail)]};
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, "_"}) ->
|
||||
{[], [i(?POP), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {missing_field, _, _}) ->
|
||||
%% Missing record fields are quite ok in patterns.
|
||||
assemble_pattern(Succeed, Fail, {var_ref, "_"});
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, Id}) ->
|
||||
{[{Id, "_"}], jump(Succeed)};
|
||||
assemble_pattern(Succeed, _Fail, {tuple, []}) ->
|
||||
{[], [pop(1), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A]}) ->
|
||||
%% Treat this case specially, because we don't need to save the
|
||||
%% pointer to the tuple.
|
||||
{AVars, ACode} = assemble_pattern(Succeed, Fail, A),
|
||||
{AVars, [i(?MLOAD),
|
||||
ACode]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A|B]}) ->
|
||||
%% Entered with the address of the tuple on the top of the
|
||||
%% stack. We will duplicate the address before matching on A.
|
||||
Continue = make_ref(), %% the label for matching B
|
||||
Pop1Fail = make_ref(), %% pop 1 word and goto Fail
|
||||
PopNFail = make_ref(), %% pop length(AVars) words and goto Fail
|
||||
{AVars, ACode} =
|
||||
assemble_pattern(Continue, Pop1Fail, A),
|
||||
{BVars, BCode} =
|
||||
assemble_pattern(Succeed, PopNFail, {tuple, B}),
|
||||
{BVars ++ reorder_vars(AVars),
|
||||
[%% duplicate the pointer so we don't lose it when we match on A
|
||||
dup(1),
|
||||
i(?MLOAD),
|
||||
ACode,
|
||||
jumpdest(Continue),
|
||||
%% Bring the pointer to the top of the stack--this reorders AVars!
|
||||
swap(length(AVars)),
|
||||
push(32),
|
||||
i(?ADD),
|
||||
BCode,
|
||||
case AVars of
|
||||
[] ->
|
||||
[jumpdest(Pop1Fail), pop(1),
|
||||
jumpdest(PopNFail),
|
||||
jump(Fail)];
|
||||
_ ->
|
||||
[{'JUMPDEST', PopNFail}, pop(length(AVars)-1),
|
||||
{'JUMPDEST', Pop1Fail}, pop(1),
|
||||
{push_label, Fail}, 'JUMP']
|
||||
end]};
|
||||
assemble_pattern(Succeed, Fail, {list, []}) ->
|
||||
%% [] is represented by -1.
|
||||
{[], [push(1),
|
||||
i(?ADD),
|
||||
jump_if(Fail),
|
||||
jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {list, [A|B]}) ->
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, {list, B}});
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, B}) ->
|
||||
%% Make sure it's not [], then match as tuple.
|
||||
NotNil = make_ref(),
|
||||
{Vars, Code} = assemble_pattern(Succeed, Fail, {tuple, [A, B]}),
|
||||
{Vars, [dup(1), push(1), i(?ADD), %% Check for [] without consuming the value
|
||||
jump_if(NotNil), %% so it's still there when matching the tuple.
|
||||
pop(1), %% It was [] so discard the saved value.
|
||||
jump(Fail),
|
||||
jumpdest(NotNil),
|
||||
Code]}.
|
||||
|
||||
%% When Vars are on the stack, with a value we want to discard
|
||||
%% below them, then we swap the top variable with that value and pop.
|
||||
%% This reorders the variables on the stack, as follows:
|
||||
reorder_vars([]) ->
|
||||
[];
|
||||
reorder_vars([V|Vs]) ->
|
||||
Vs ++ [V].
|
||||
|
||||
assemble_prefix('sha3') -> [i(?DUP1), i(?MLOAD), %% length, ptr
|
||||
i(?SWAP1), push(32), i(?ADD), %% ptr+32, length
|
||||
i(?SHA3)];
|
||||
assemble_prefix('-') -> [push(0), i(?SUB)];
|
||||
assemble_prefix('bnot') -> i(?NOT).
|
||||
|
||||
assemble_infix('+') -> i(?ADD);
|
||||
assemble_infix('-') -> i(?SUB);
|
||||
assemble_infix('*') -> i(?MUL);
|
||||
assemble_infix('/') -> i(?SDIV);
|
||||
assemble_infix('div') -> i(?DIV);
|
||||
assemble_infix('mod') -> i(?MOD);
|
||||
assemble_infix('^') -> i(?EXP);
|
||||
assemble_infix('bor') -> i(?OR);
|
||||
assemble_infix('band') -> i(?AND);
|
||||
assemble_infix('bxor') -> i(?XOR);
|
||||
assemble_infix('bsl') -> i(?SHL);
|
||||
assemble_infix('bsr') -> i(?SHR);
|
||||
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
|
||||
assemble_infix('>') -> i(?SGT);
|
||||
assemble_infix('==') -> i(?EQ);
|
||||
assemble_infix('<=') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('=<') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('>=') -> [i(?SLT), i(?ISZERO)];
|
||||
assemble_infix('!=') -> [i(?EQ), i(?ISZERO)];
|
||||
assemble_infix('!') -> [i(?ADD), i(?MLOAD)];
|
||||
assemble_infix('byte') -> i(?BYTE).
|
||||
%% assemble_infix('::') -> [i(?MSIZE), write_word(0), write_word(1)].
|
||||
|
||||
%% a function may either refer to a top-level function, in which case
|
||||
%% we fetch the code label from Funs, or it may be a lambda-expression
|
||||
%% (including a top-level function passed as a parameter). In the
|
||||
%% latter case, the function value is a pointer to a tuple of the code
|
||||
%% pointer and the free variables: we keep the pointer and push the
|
||||
%% code pointer onto the stack. In either case, we are ready to enter
|
||||
%% the function with JUMP.
|
||||
assemble_function(Funs, Stack, Fun) ->
|
||||
case is_top_level_fun(Stack, Fun) of
|
||||
true ->
|
||||
{var_ref, Name} = Fun,
|
||||
{push_label, lookup_fun(Funs, Name)};
|
||||
false ->
|
||||
[assemble_expr(Funs, Stack, nontail, Fun),
|
||||
dup(1),
|
||||
i(?MLOAD)]
|
||||
end.
|
||||
|
||||
free_vars(V={var_ref, _}) ->
|
||||
[V];
|
||||
free_vars({switch, E, Cases}) ->
|
||||
lists:umerge(free_vars(E),
|
||||
lists:umerge([free_vars(Body)--free_vars(Pattern)
|
||||
|| {Pattern, Body} <- Cases]));
|
||||
free_vars({lambda, Args, Body}) ->
|
||||
free_vars(Body) -- [{var_ref, Arg#arg.name} || Arg <- Args];
|
||||
free_vars(T) when is_tuple(T) ->
|
||||
free_vars(tuple_to_list(T));
|
||||
free_vars([H|T]) ->
|
||||
lists:umerge(free_vars(H), free_vars(T));
|
||||
free_vars(_) ->
|
||||
[].
|
||||
|
||||
|
||||
|
||||
%% shuffle_stack reorders the stack, for example before a tailcall. It is called
|
||||
%% with a description of the current stack, and how the final stack
|
||||
%% should appear. The argument is a list containing
|
||||
%% a NUMBER for each element that should be kept, the number being
|
||||
%% the position this element should occupy in the final stack
|
||||
%% discard, for elements that can be discarded.
|
||||
%% The positions start at 1, referring to the variable to be placed at
|
||||
%% the bottom of the stack, and ranging up to the size of the final stack.
|
||||
shuffle_stack([]) ->
|
||||
[];
|
||||
shuffle_stack([discard|Stack]) ->
|
||||
[i(?POP) | shuffle_stack(Stack)];
|
||||
shuffle_stack([N|Stack]) ->
|
||||
case length(Stack) + 1 - N of
|
||||
0 ->
|
||||
%% the job should be finished
|
||||
CorrectStack = lists:seq(N - 1, 1, -1),
|
||||
CorrectStack = Stack,
|
||||
[];
|
||||
MoveBy ->
|
||||
{Pref, [_|Suff]} = lists:split(MoveBy - 1, Stack),
|
||||
[swap(MoveBy) | shuffle_stack([lists:nth(MoveBy, Stack) | Pref ++ [N|Suff]])]
|
||||
end.
|
||||
|
||||
|
||||
|
||||
lookup_fun(Funs, Name) ->
|
||||
case [Ref || {Name1, _, Ref} <- Funs,
|
||||
Name == Name1] of
|
||||
[Ref] -> Ref;
|
||||
[] -> gen_error({undefined_function, Name})
|
||||
end.
|
||||
|
||||
is_top_level_fun(Stack, {var_ref, Id}) ->
|
||||
not lists:keymember(Id, 1, Stack);
|
||||
is_top_level_fun(_, _) ->
|
||||
false.
|
||||
|
||||
lookup_var(Id, Stack) ->
|
||||
lookup_var(1, Id, Stack).
|
||||
|
||||
lookup_var(N, Id, [{Id, _Type}|_]) ->
|
||||
N;
|
||||
lookup_var(N, Id, [_|Stack]) ->
|
||||
lookup_var(N + 1, Id, Stack);
|
||||
lookup_var(_, Id, []) ->
|
||||
gen_error({var_not_in_scope, Id}).
|
||||
|
||||
%% Smart instruction generation
|
||||
|
||||
%% TODO: handle references to the stack beyond depth 16. Perhaps the
|
||||
%% best way is to repush variables that will be needed in
|
||||
%% subexpressions before evaluating he subexpression... i.e. fix the
|
||||
%% problem in assemble_expr, rather than here. A fix here would have
|
||||
%% to save the top elements of the stack in memory, duplicate the
|
||||
%% targetted element, and then repush the values from memory.
|
||||
dup(N) when 1 =< N, N =< 16 ->
|
||||
i(?DUP1 + N - 1).
|
||||
|
||||
push(N) ->
|
||||
Bytes = binary:encode_unsigned(N),
|
||||
true = size(Bytes) =< 32,
|
||||
[i(?PUSH1 + size(Bytes) - 1) |
|
||||
binary_to_list(Bytes)].
|
||||
|
||||
%% Pop N values from UNDER the top element of the stack.
|
||||
%% This is a pseudo-instruction so peephole optimization can
|
||||
%% combine pop_args(M), pop_args(N) to pop_args(M+N)
|
||||
pop_args(0) ->
|
||||
[];
|
||||
pop_args(N) ->
|
||||
{pop_args, N}.
|
||||
%% [swap(N), pop(N)].
|
||||
|
||||
pop(N) ->
|
||||
[i(?POP) || _ <- lists:seq(1, N)].
|
||||
|
||||
swap(0) ->
|
||||
%% Doesn't exist, but is logically a no-op.
|
||||
[];
|
||||
swap(N) when 1 =< N, N =< 16 ->
|
||||
i(?SWAP1 + N - 1).
|
||||
|
||||
jumpdest(Label) -> {i(?JUMPDEST), Label}.
|
||||
push_label(Label) -> {push_label, Label}.
|
||||
|
||||
jump(Label) -> [push_label(Label), i(?JUMP)].
|
||||
jump_if(Label) -> [push_label(Label), i(?JUMPI)].
|
||||
|
||||
%% ICode utilities (TODO: move to separate module)
|
||||
|
||||
icode_noname() -> #var_ref{name = "_"}.
|
||||
|
||||
icode_seq([A]) -> A;
|
||||
icode_seq([A | As]) ->
|
||||
icode_seq(A, icode_seq(As)).
|
||||
|
||||
icode_seq(A, B) ->
|
||||
#switch{ expr = A, cases = [{icode_noname(), B}] }.
|
||||
|
||||
%% Stack: <N elements> ADDR
|
||||
%% Write elements at addresses ADDR, ADDR+32, ADDR+64...
|
||||
%% Stack afterwards: ADDR
|
||||
% write_words(N) ->
|
||||
% [write_word(I) || I <- lists:seq(N-1, 0, -1)].
|
||||
|
||||
%% Unused at the moment. Comment out to please dialyzer.
|
||||
%% write_word(I) ->
|
||||
%% [%% Stack: elements e ADDR
|
||||
%% swap(1),
|
||||
%% dup(2),
|
||||
%% %% Stack: elements ADDR e ADDR
|
||||
%% push(32*I),
|
||||
%% i(?ADD),
|
||||
%% %% Stack: elements ADDR e ADDR+32I
|
||||
%% i(?MSTORE)].
|
||||
|
||||
%% Resolve references, and convert code from deep list to flat list.
|
||||
%% List elements are:
|
||||
%% Opcodes
|
||||
%% Byte values
|
||||
%% {'JUMPDEST', Ref} -- assembles to ?JUMPDEST and sets Ref
|
||||
%% {push_label, Ref} -- assembles to ?PUSHN address bytes
|
||||
|
||||
%% For now, we assemble all code addresses as three bytes.
|
||||
|
||||
resolve_references(Code) ->
|
||||
Peephole = peep_hole(lists:flatten(Code)),
|
||||
%% WARNING: Optimizing jumps reorders the code and deletes
|
||||
%% instructions. When debugging the assemble_ functions, it can be
|
||||
%% useful to replace the next line by:
|
||||
%% Instrs = lists:flatten(Code),
|
||||
%% thus disabling the optimization.
|
||||
OptimizedJumps = optimize_jumps(Peephole),
|
||||
Instrs = lists:reverse(peep_hole_backwards(lists:reverse(OptimizedJumps))),
|
||||
Labels = define_labels(0, Instrs),
|
||||
lists:flatten([use_labels(Labels, I) || I <- Instrs]).
|
||||
|
||||
define_labels(Addr, [{'JUMPDEST', Lab}|More]) ->
|
||||
[{Lab, Addr}|define_labels(Addr + 1, More)];
|
||||
define_labels(Addr, [{push_label, _}|More]) ->
|
||||
define_labels(Addr + 4, More);
|
||||
define_labels(Addr, [{pop_args, N}|More]) ->
|
||||
define_labels(Addr + N + 1, More);
|
||||
define_labels(Addr, [_|More]) ->
|
||||
define_labels(Addr + 1, More);
|
||||
define_labels(_, []) ->
|
||||
[].
|
||||
|
||||
use_labels(_, {'JUMPDEST', _}) ->
|
||||
'JUMPDEST';
|
||||
use_labels(Labels, {push_label, Ref}) ->
|
||||
case proplists:get_value(Ref, Labels) of
|
||||
undefined ->
|
||||
gen_error({undefined_label, Ref});
|
||||
Addr when is_integer(Addr) ->
|
||||
[i(?PUSH3),
|
||||
Addr div 65536, (Addr div 256) rem 256, Addr rem 256]
|
||||
end;
|
||||
use_labels(_, {pop_args, N}) ->
|
||||
[swap(N), pop(N)];
|
||||
use_labels(_, I) ->
|
||||
I.
|
||||
|
||||
%% Peep-hole optimization.
|
||||
%% The compilation of conditionals can introduce jumps depending on
|
||||
%% constants 1 and 0. These are removed by peep-hole optimization.
|
||||
|
||||
peep_hole(['PUSH1', 0, {push_label, _}, 'JUMPI'|More]) ->
|
||||
peep_hole(More);
|
||||
peep_hole(['PUSH1', 1, {push_label, Lab}, 'JUMPI'|More]) ->
|
||||
[{push_label, Lab}, 'JUMP'|peep_hole(More)];
|
||||
peep_hole([{pop_args, M}, {pop_args, N}|More]) when M + N =< 16 ->
|
||||
peep_hole([{pop_args, M + N}|More]);
|
||||
peep_hole([I|More]) ->
|
||||
[I|peep_hole(More)];
|
||||
peep_hole([]) ->
|
||||
[].
|
||||
|
||||
%% Peep-hole optimization on reversed instructions lists.
|
||||
|
||||
peep_hole_backwards(Code) ->
|
||||
NewCode = peep_hole_backwards1(Code),
|
||||
if Code == NewCode -> Code;
|
||||
true -> peep_hole_backwards(NewCode)
|
||||
end.
|
||||
|
||||
peep_hole_backwards1(['ADD', 0, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1(['POP', UnOp|Code]) when UnOp=='MLOAD';UnOp=='ISZERO';UnOp=='NOT' ->
|
||||
peep_hole_backwards1(['POP'|Code]);
|
||||
peep_hole_backwards1(['POP', BinOp|Code]) when
|
||||
%% TODO: more binary operators
|
||||
BinOp=='ADD';BinOp=='SUB';BinOp=='MUL';BinOp=='SDIV' ->
|
||||
peep_hole_backwards1(['POP', 'POP'|Code]);
|
||||
peep_hole_backwards1(['POP', _, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1([I|Code]) ->
|
||||
[I|peep_hole_backwards1(Code)];
|
||||
peep_hole_backwards1([]) ->
|
||||
[].
|
||||
|
||||
%% Jump optimization:
|
||||
%% Replaces a jump to a jump with a jump to the final destination
|
||||
%% Moves basic blocks to eliminate an unconditional jump to them.
|
||||
|
||||
%% The compilation of conditionals generates a lot of labels and
|
||||
%% jumps, some of them unnecessary. This optimization phase reorders
|
||||
%% code so that as many jumps as possible can be eliminated, and
|
||||
%% replaced by just falling through to the destination label. This
|
||||
%% both optimizes the code generated by conditionals, and converts one
|
||||
%% call of a function into falling through into its code--so it
|
||||
%% reorders code quite aggressively. Function returns are indirect
|
||||
%% jumps, however, and are never optimized away.
|
||||
|
||||
%% IMPORTANT: since execution begins at address zero, then the first
|
||||
%% block of code must never be moved elsewhere. The code below has
|
||||
%% this property, because it processes blocks from left to right, and
|
||||
%% because the first block does not begin with a label, and so can
|
||||
%% never be jumped to--hence no code can be inserted before it.
|
||||
|
||||
%% The optimization works by taking one block of code at a time, and
|
||||
%% then prepending blocks that jump directly to it, and appending
|
||||
%% blocks that it jumps directly to, resulting in a jump-free sequence
|
||||
%% that is as long as possible. To do so, we store blocks in the form
|
||||
%% {OptionalLabel, Body, OptionalJump} which represents the code block
|
||||
%% OptionalLabel++Body++OptionalJump; the optional parts are the empty
|
||||
%% list of instructions if not present. Two blocks can be merged if
|
||||
%% the first ends in an OptionalJump to the OptionalLabel beginning
|
||||
%% the second; the OptionalJump can then be removed (and the
|
||||
%% OptionalLabel if there are no other references to it--this happens
|
||||
%% during dead code elimination.
|
||||
|
||||
%% TODO: the present implementation is QUADRATIC, because we search
|
||||
%% repeatedly for matching blocks to merge with the first one, storing
|
||||
%% the blocks in a list. A near linear time implementation could use
|
||||
%% two ets tables, one keyed on the labels, and the other keyed on the
|
||||
%% final jumps.
|
||||
|
||||
optimize_jumps(Code) ->
|
||||
JJs = jumps_to_jumps(Code),
|
||||
ShortCircuited = [short_circuit_jumps(JJs, Instr) || Instr <- Code],
|
||||
NoDeadCode = eliminate_dead_code(ShortCircuited),
|
||||
MovedCode = merge_blocks(moveable_blocks(NoDeadCode)),
|
||||
%% Moving code may have made some labels superfluous.
|
||||
eliminate_dead_code(MovedCode).
|
||||
|
||||
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {push_label, Target}, 'JUMP'|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps(More)];
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {'JUMPDEST', Target}|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps([{'JUMPDEST', Target}|More])];
|
||||
jumps_to_jumps([_|More]) ->
|
||||
jumps_to_jumps(More);
|
||||
jumps_to_jumps([]) ->
|
||||
[].
|
||||
|
||||
short_circuit_jumps(JJs, {push_label, Lab}) ->
|
||||
case proplists:get_value(Lab, JJs) of
|
||||
undefined ->
|
||||
{push_label, Lab};
|
||||
Target ->
|
||||
%% I wonder if this will ever loop infinitely?
|
||||
short_circuit_jumps(JJs, {push_label, Target})
|
||||
end;
|
||||
short_circuit_jumps(_JJs, Instr) ->
|
||||
Instr.
|
||||
|
||||
eliminate_dead_code(Code) ->
|
||||
Jumps = lists:usort([Lab || {push_label, Lab} <- Code]),
|
||||
NewCode = live_code(Jumps, Code),
|
||||
if Code==NewCode ->
|
||||
Code;
|
||||
true ->
|
||||
eliminate_dead_code(NewCode)
|
||||
end.
|
||||
|
||||
live_code(Jumps, ['JUMP'|More]) ->
|
||||
['JUMP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, ['STOP'|More]) ->
|
||||
['STOP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
live_code(Jumps, More)
|
||||
end;
|
||||
live_code(Jumps, [I|More]) ->
|
||||
[I|live_code(Jumps, More)];
|
||||
live_code(_, []) ->
|
||||
[].
|
||||
|
||||
dead_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
dead_code(Jumps, More)
|
||||
end;
|
||||
dead_code(Jumps, [_I|More]) ->
|
||||
dead_code(Jumps, More);
|
||||
dead_code(_, []) ->
|
||||
[].
|
||||
|
||||
%% Split the code into "moveable blocks" that control flow only
|
||||
%% reaches via jumps.
|
||||
moveable_blocks([]) ->
|
||||
[];
|
||||
moveable_blocks([I]) ->
|
||||
[[I]];
|
||||
moveable_blocks([Jump|More]) when Jump=='JUMP'; Jump=='STOP' ->
|
||||
[[Jump]|moveable_blocks(More)];
|
||||
moveable_blocks([I|More]) ->
|
||||
[Block|MoreBlocks] = moveable_blocks(More),
|
||||
[[I|Block]|MoreBlocks].
|
||||
|
||||
%% Merge blocks to eliminate jumps where possible.
|
||||
merge_blocks(Blocks) ->
|
||||
BlocksAndTargets = [label_and_jump(B) || B <- Blocks],
|
||||
[I || {Pref, Body, Suff} <- merge_after(BlocksAndTargets),
|
||||
I <- Pref++Body++Suff].
|
||||
|
||||
%% Merge the first block with other blocks that come after it
|
||||
merge_after(All=[{Label, Body, [{push_label, Target}, 'JUMP']}|BlocksAndTargets]) ->
|
||||
case [{B, J} || {[{'JUMPDEST', L}], B, J} <- BlocksAndTargets,
|
||||
L == Target] of
|
||||
[{B, J}|_] ->
|
||||
merge_after([{Label, Body ++ [{'JUMPDEST', Target}] ++ B, J}|
|
||||
lists:delete({[{'JUMPDEST', Target}], B, J},
|
||||
BlocksAndTargets)]);
|
||||
[] ->
|
||||
merge_before(All)
|
||||
end;
|
||||
merge_after(All) ->
|
||||
merge_before(All).
|
||||
|
||||
%% The first block cannot be merged with any blocks that it jumps
|
||||
%% to... but maybe it can be merged with a block that jumps to it!
|
||||
merge_before([Block={[{'JUMPDEST', Label}], Body, Jump}|BlocksAndTargets]) ->
|
||||
case [{L, B, T} || {L, B, [{push_label, T}, 'JUMP']} <- BlocksAndTargets,
|
||||
T == Label] of
|
||||
[{L, B, T}|_] ->
|
||||
merge_before([{L, B ++ [{'JUMPDEST', Label}] ++ Body, Jump}
|
||||
|lists:delete({L, B, [{push_label, T}, 'JUMP']}, BlocksAndTargets)]);
|
||||
_ ->
|
||||
[Block | merge_after(BlocksAndTargets)]
|
||||
end;
|
||||
merge_before([Block|BlocksAndTargets]) ->
|
||||
[Block | merge_after(BlocksAndTargets)];
|
||||
merge_before([]) ->
|
||||
[].
|
||||
|
||||
%% Convert each block to a PREFIX, which is a label or empty, a
|
||||
%% middle, and a SUFFIX which is a JUMP to a label, or empty.
|
||||
label_and_jump(B) ->
|
||||
{Label, B1} = case B of
|
||||
[{'JUMPDEST', L}|More1] ->
|
||||
{[{'JUMPDEST', L}], More1};
|
||||
_ ->
|
||||
{[], B}
|
||||
end,
|
||||
{Target, B2} = case lists:reverse(B1) of
|
||||
['JUMP', {push_label, T}|More2] ->
|
||||
{[{push_label, T}, 'JUMP'], lists:reverse(More2)};
|
||||
_ ->
|
||||
{[], B1}
|
||||
end,
|
||||
{Label, B2, Target}.
|
||||
+24
-8
@@ -15,7 +15,8 @@
|
||||
many/1, many1/1, sep/2, sep1/2,
|
||||
infixl/2, infixr/2]).
|
||||
|
||||
-export([current_file/0, set_current_file/1]).
|
||||
-export([current_file/0, set_current_file/1,
|
||||
current_include_type/0, set_current_include_type/1]).
|
||||
|
||||
%% -- Types ------------------------------------------------------------------
|
||||
|
||||
@@ -74,25 +75,31 @@
|
||||
%% first argument. I.e. no backtracking to the second argument if the first
|
||||
%% fails.
|
||||
|
||||
trampoline({bounce, Cont}) when is_function(Cont, 0) ->
|
||||
trampoline(Cont());
|
||||
trampoline(Res) ->
|
||||
Res.
|
||||
-define(BOUNCE(X), {bounce, fun() -> X end}).
|
||||
|
||||
%% Apply a parser to its continuation. This compiles a parser to its low-level representation.
|
||||
-spec apply_p(parser(A), fun((A) -> parser1(B))) -> parser1(B).
|
||||
apply_p(?lazy(F), K) -> apply_p(F(), K);
|
||||
apply_p(?fail(Err), _) -> {fail, Err};
|
||||
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(apply_p(Q, K), R) end,
|
||||
apply_p(P, K), Ps);
|
||||
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(trampoline(apply_p(Q, K)), R) end,
|
||||
trampoline(apply_p(P, K)), Ps);
|
||||
apply_p(?bind(P, F), K) -> apply_p(P, fun(X) -> apply_p(F(X), K) end);
|
||||
apply_p(?right(P, Q), K) -> apply_p(P, fun(_) -> apply_p(Q, K) end);
|
||||
apply_p(?left(P, Q), K) -> apply_p(P, fun(X) -> apply_p(Q, fun(_) -> K(X) end) end);
|
||||
apply_p(?map(F, P), K) -> apply_p(P, fun(X) -> K(F(X)) end);
|
||||
apply_p(?layout, K) -> {layout, K, {fail, {expected, layout_block}}};
|
||||
apply_p(?tok(Atom), K) -> {tok_bind, #{Atom => K}};
|
||||
apply_p(?return(X), K) -> K(X);
|
||||
apply_p(?return(X), K) -> ?BOUNCE(K(X));
|
||||
apply_p([P | Q], K) -> apply_p(P, fun(H) -> apply_p(Q, fun(T) -> K([H | T]) end) end);
|
||||
apply_p(T, K) when is_tuple(T) -> apply_p(tuple_to_list(T), fun(Xs) -> K(list_to_tuple(Xs)) end);
|
||||
apply_p(M, K) when is_map(M) ->
|
||||
{Keys, Ps} = lists:unzip(maps:to_list(M)),
|
||||
apply_p(Ps, fun(Vals) -> K(maps:from_list(lists:zip(Keys, Vals))) end);
|
||||
apply_p(X, K) -> K(X).
|
||||
apply_p(X, K) -> ?BOUNCE(K(X)).
|
||||
|
||||
%% -- Primitive combinators --------------------------------------------------
|
||||
|
||||
@@ -160,7 +167,7 @@ layout() -> ?layout.
|
||||
%% @doc Parse a sequence of tokens using a parser. Fails if the parse is ambiguous.
|
||||
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
|
||||
parse(P, S) ->
|
||||
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
|
||||
case parse1(trampoline(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end)), S) of
|
||||
{[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
|
||||
{[A], _} -> {ok, A};
|
||||
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
|
||||
@@ -241,7 +248,7 @@ col(T) when is_tuple(T) -> element(2, pos(T)).
|
||||
|
||||
%% If both parsers want the next token we grab it and merge the continuations.
|
||||
choice1({tok_bind, Map1}, {tok_bind, Map2}) ->
|
||||
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(F(T), G(T)) end end, Map1, Map2)};
|
||||
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(trampoline(F(T)), trampoline(G(T))) end end, Map1, Map2)};
|
||||
|
||||
%% If both parsers fail we combine the error messages. If only one fails we discard it.
|
||||
choice1({fail, E1}, {fail, E2}) -> {fail, add_error(E1, E2)};
|
||||
@@ -255,7 +262,7 @@ choice1(P, {return_plus, X, Q}) -> {return_plus, X, choice1(P, Q)};
|
||||
%% If both sides want a layout block we combine them. If only one side wants a layout block we
|
||||
%% will commit to a layout block is there is one.
|
||||
choice1({layout, F, P}, {layout, G, Q}) ->
|
||||
{layout, fun(N) -> choice1(F(N), G(N)) end, choice1(P, Q)};
|
||||
{layout, fun(N) -> choice1(trampoline(F(N)), trampoline(G(N))) end, choice1(P, Q)};
|
||||
choice1({layout, F, P}, Q) -> {layout, F, choice1(P, Q)};
|
||||
choice1(P, {layout, G, Q}) -> {layout, G, choice1(P, Q)}.
|
||||
|
||||
@@ -278,6 +285,8 @@ parse1(P, S) ->
|
||||
%% The main work horse. Returns a list of possible parses and an error message in case parsing
|
||||
%% fails.
|
||||
-spec parse1(parser1(A), #ts{}, [A], term()) -> {[A], error()}.
|
||||
parse1({bounce, F}, Ts, Acc, Err) ->
|
||||
parse1(F(), Ts, Acc, Err);
|
||||
parse1({tok_bind, Map}, Ts, Acc, Err) ->
|
||||
case next_token(Ts) of
|
||||
{T, Ts1} ->
|
||||
@@ -457,6 +466,13 @@ merge_with(Fun, Map1, Map2) ->
|
||||
end, Map2, maps:to_list(Map1))
|
||||
end.
|
||||
|
||||
%% Current include type
|
||||
current_include_type() ->
|
||||
get('$current_include_type').
|
||||
|
||||
set_current_include_type(IncludeType) ->
|
||||
put('$current_include_type', IncludeType).
|
||||
|
||||
%% Current source file
|
||||
current_file() ->
|
||||
get('$current_file').
|
||||
|
||||
@@ -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,
|
||||
|
||||
+113
-132
@@ -8,21 +8,17 @@
|
||||
-export([string/1,
|
||||
string/2,
|
||||
string/3,
|
||||
auto_imports/1,
|
||||
hash_include/2,
|
||||
decl/0,
|
||||
type/0,
|
||||
body/0,
|
||||
maybe_block/1,
|
||||
run_parser/2,
|
||||
run_parser/3]).
|
||||
run_parser/2]).
|
||||
|
||||
-include("aeso_parse_lib.hrl").
|
||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
|
||||
current_include_type/0, set_current_include_type/1]).
|
||||
|
||||
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
|
||||
|
||||
-type include_hash() :: {string(), binary()}.
|
||||
-type parse_result() :: aeso_syntax:ast() | none().
|
||||
|
||||
|
||||
escape_errors({ok, Ok}) ->
|
||||
@@ -30,33 +26,23 @@ escape_errors({ok, Ok}) ->
|
||||
escape_errors({error, Err}) ->
|
||||
parse_error(Err).
|
||||
|
||||
-spec string(string()) -> parse_result().
|
||||
string(String) ->
|
||||
string(String, sets:new(), []).
|
||||
-spec module(string()) -> parse_result().
|
||||
module(String) ->
|
||||
module(String, []).
|
||||
|
||||
-spec string(string(), aeso_compiler:options()) -> parse_result().
|
||||
string(String, Opts) ->
|
||||
case lists:keyfind(src_file, 1, Opts) of
|
||||
{src_file, File} -> string(String, sets:add_element(File, sets:new()), Opts);
|
||||
false -> string(String, sets:new(), Opts)
|
||||
end.
|
||||
|
||||
-spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result().
|
||||
string(String, Included, Opts) ->
|
||||
-spec module(string(), aeso_compiler:options()) -> parse_result().
|
||||
module(String, Opts) ->
|
||||
AST = run_parser(file(), String, Opts),
|
||||
case expand_includes(AST, Included, Opts) of
|
||||
{ok, AST1} -> AST1;
|
||||
{error, Err} -> parse_error(Err)
|
||||
end.
|
||||
|
||||
add_auto_imports(AST).
|
||||
|
||||
run_parser(P, Inp) ->
|
||||
escape_errors(parse_and_scan(P, Inp, [])).
|
||||
escape_errors(scan_and_parse(P, Inp, [])).
|
||||
run_parser(P, Inp, Opts) ->
|
||||
escape_errors(parse_and_scan(P, Inp, Opts)).
|
||||
escape_errors(scan_and_parse(P, Inp, Opts)).
|
||||
|
||||
parse_and_scan(P, S, Opts) ->
|
||||
scan_and_parse(P, S, Opts) ->
|
||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||
set_current_include_type(proplists:get_value(include_type, Opts, none)),
|
||||
case aeso_scan:scan(S) of
|
||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||
{error, {{Input, Pos}, _}} ->
|
||||
@@ -93,10 +79,35 @@ 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(token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
|
||||
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||
, using()
|
||||
, pragma()
|
||||
|
||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||
@@ -123,6 +134,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 +209,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 +228,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 +277,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 +287,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)).
|
||||
@@ -262,17 +304,18 @@ expr() -> expr100().
|
||||
|
||||
expr100() ->
|
||||
Expr100 = ?LAZY_P(expr100()),
|
||||
Expr200 = ?LAZY_P(expr200()),
|
||||
Expr150 = ?LAZY_P(expr150()),
|
||||
choice(
|
||||
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
|
||||
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
|
||||
, ?RULE(Expr200, optional(right(tok(':'), type())),
|
||||
, {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)}
|
||||
, ?RULE(Expr150, optional(right(tok(':'), type())),
|
||||
case _2 of
|
||||
none -> _1;
|
||||
{ok, Type} -> {typed, get_ann(_1), _1, Type}
|
||||
end)
|
||||
]).
|
||||
|
||||
expr150() -> infixl(expr200(), binop('|>')).
|
||||
expr200() -> infixr(expr300(), binop('||')).
|
||||
expr300() -> infixr(expr400(), binop('&&')).
|
||||
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
|
||||
@@ -288,7 +331,7 @@ exprAtom() ->
|
||||
?LAZY_P(begin
|
||||
Expr = ?LAZY_P(expr()),
|
||||
choice(
|
||||
[ id_or_addr(), con(), token(qid), token(qcon)
|
||||
[ id_or_addr(), con(), token(qid), token(qcon), binop_as_lam()
|
||||
, token(bytes), token(string), token(char)
|
||||
, token(int)
|
||||
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
||||
@@ -299,9 +342,13 @@ 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()
|
||||
, hole()
|
||||
])
|
||||
end).
|
||||
|
||||
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
|
||||
|
||||
comprehension_exp() ->
|
||||
?LAZY_P(choice(
|
||||
[ comprehension_bind()
|
||||
@@ -424,6 +471,19 @@ id() -> token(id).
|
||||
tvar() -> token(tvar).
|
||||
str() -> token(string).
|
||||
|
||||
binop_as_lam() ->
|
||||
BinOps = ['&&', '||',
|
||||
'+', '-', '*', '/', '^', 'mod',
|
||||
'==', '!=', '<', '>', '<=', '=<', '>=',
|
||||
'::', '++', '|>'],
|
||||
OpToLam = fun(Op = {_, Ann}) ->
|
||||
IdL = {id, Ann, "l"},
|
||||
IdR = {id, Ann, "r"},
|
||||
Arg = fun(Id) -> {arg, Ann, Id, type_wildcard(Ann)} end,
|
||||
{lam, Ann, [Arg(IdL), Arg(IdR)], infix(IdL, Op, IdR)}
|
||||
end,
|
||||
?RULE(parens(choice(lists:map(fun token/1, BinOps))), OpToLam(_1)).
|
||||
|
||||
token(Tag) ->
|
||||
?RULE(tok(Tag),
|
||||
case _1 of
|
||||
@@ -478,7 +538,14 @@ bracket_list(P) -> brackets(comma_sep(P)).
|
||||
-type ann_col() :: aeso_syntax:ann_col().
|
||||
|
||||
-spec pos_ann(ann_line(), ann_col()) -> ann().
|
||||
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
|
||||
pos_ann(Line, Col) ->
|
||||
[ {file, current_file()}
|
||||
, {include_type, current_include_type()}
|
||||
, {line, Line}
|
||||
, {col, Col} ].
|
||||
|
||||
top_ann() ->
|
||||
pos_ann(0, 0).
|
||||
|
||||
ann_pos(Ann) ->
|
||||
{proplists:get_value(file, Ann),
|
||||
@@ -559,6 +626,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}]}) ->
|
||||
@@ -599,101 +668,13 @@ bad_expr_err(Reason, E) ->
|
||||
|
||||
%% -- Helper functions -------------------------------------------------------
|
||||
|
||||
expand_includes(AST, Included, Opts) ->
|
||||
add_auto_imports(AST) ->
|
||||
Ann = [{origin, system}],
|
||||
AST1 = [ {include, Ann, {string, Ann, File}}
|
||||
|| File <- lists:usort(auto_imports(AST)) ] ++ AST,
|
||||
expand_includes(AST1, Included, [], Opts).
|
||||
[ {using, Ann, {con, Ann, Import}}
|
||||
|| Import <- lists:usort(auto_imports(AST)) ] ++ AST.
|
||||
|
||||
expand_includes([], Included, Acc, Opts) ->
|
||||
case lists:member(keep_included, Opts) of
|
||||
false ->
|
||||
{ok, lists:reverse(Acc)};
|
||||
true ->
|
||||
{ok, {lists:reverse(Acc), Included}}
|
||||
end;
|
||||
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
|
||||
case get_include_code(File, Ann, Opts) of
|
||||
{ok, Code} ->
|
||||
Hashed = hash_include(File, Code),
|
||||
case sets:is_element(Hashed, Included) of
|
||||
false ->
|
||||
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
||||
Included1 = sets:add_element(Hashed, Included),
|
||||
case parse_and_scan(file(), Code, Opts1) of
|
||||
{ok, AST1} ->
|
||||
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end;
|
||||
true ->
|
||||
expand_includes(AST, Included, Acc, Opts)
|
||||
end;
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end;
|
||||
expand_includes([E | AST], Included, Acc, Opts) ->
|
||||
expand_includes(AST, Included, [E | Acc], Opts).
|
||||
|
||||
read_file(File, Opts) ->
|
||||
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
|
||||
{file_system, Paths} ->
|
||||
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
|
||||
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
|
||||
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
|
||||
{explicit_files, Files} ->
|
||||
case maps:get(binary_to_list(File), Files, not_found) of
|
||||
not_found -> {error, not_found};
|
||||
Src -> {ok, Src}
|
||||
end;
|
||||
escript ->
|
||||
try
|
||||
Escript = escript:script_name(),
|
||||
{ok, Sections} = escript:extract(Escript, []),
|
||||
Archive = proplists:get_value(archive, Sections),
|
||||
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
|
||||
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
|
||||
{ok, [{_, Src}]} -> {ok, Src};
|
||||
_ -> {error, not_found}
|
||||
end
|
||||
catch _:_ ->
|
||||
{error, not_found}
|
||||
end
|
||||
end.
|
||||
|
||||
stdlib_options() ->
|
||||
StdLibDir = aeso_stdlib:stdlib_include_path(),
|
||||
case filelib:is_dir(StdLibDir) of
|
||||
true -> [{include, {file_system, [StdLibDir]}}];
|
||||
false -> [{include, escript}]
|
||||
end.
|
||||
|
||||
get_include_code(File, Ann, Opts) ->
|
||||
case {read_file(File, Opts), read_file(File, stdlib_options())} of
|
||||
{{ok, Bin}, {ok, _}} ->
|
||||
case filename:basename(File) == File of
|
||||
true -> { error
|
||||
, fail( ann_pos(Ann)
|
||||
, "Illegal redefinition of standard library " ++ binary_to_list(File))};
|
||||
%% If a path is provided then the stdlib takes lower priority
|
||||
false -> {ok, binary_to_list(Bin)}
|
||||
end;
|
||||
{_, {ok, Bin}} ->
|
||||
{ok, binary_to_list(Bin)};
|
||||
{{ok, Bin}, _} ->
|
||||
{ok, binary_to_list(Bin)};
|
||||
{_, _} ->
|
||||
{error, {ann_pos(Ann), include_error, File}}
|
||||
end.
|
||||
|
||||
-spec hash_include(string() | binary(), string()) -> include_hash().
|
||||
hash_include(File, Code) when is_binary(File) ->
|
||||
hash_include(binary_to_list(File), Code);
|
||||
hash_include(File, Code) when is_list(File) ->
|
||||
{filename:basename(File), crypto:hash(sha256, Code)}.
|
||||
|
||||
auto_imports({comprehension_bind, _, _}) -> [<<"ListInternal.aes">>];
|
||||
auto_imports({'..', _}) -> [<<"ListInternal.aes">>];
|
||||
auto_imports({comprehension_bind, _, _}) -> ["ListInternal"];
|
||||
auto_imports({'..', _}) -> ["ListInternal"];
|
||||
auto_imports(L) when is_list(L) ->
|
||||
lists:flatmap(fun auto_imports/1, L);
|
||||
auto_imports(T) when is_tuple(T) ->
|
||||
|
||||
+50
-18
@@ -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,12 +151,16 @@ 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, Is, 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")])
|
||||
, hsep(name(C), text("="))), decls(Ds));
|
||||
ImplsList = case Is of
|
||||
[] -> [empty()];
|
||||
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
|
||||
end,
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({pragma, _, Pragma}) -> pragma(Pragma);
|
||||
@@ -206,8 +216,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) ->
|
||||
@@ -249,6 +261,8 @@ type(Type, Options) ->
|
||||
with_options(Options, fun() -> type(Type) end).
|
||||
|
||||
-spec type(aeso_syntax:type()) -> doc().
|
||||
type(F = {fun_t, _, _, var_args, _}) ->
|
||||
type(setelement(4, F, [var_args]));
|
||||
type({fun_t, _, Named, Args, Ret}) ->
|
||||
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
|
||||
type({type_sig, _, Named, Args, Ret}) ->
|
||||
@@ -264,6 +278,8 @@ type({args_t, _, Args}) ->
|
||||
type({bytes_t, _, any}) -> text("bytes(_)");
|
||||
type({bytes_t, _, Len}) ->
|
||||
text(lists:concat(["bytes(", Len, ")"]));
|
||||
type({if_t, _, Id, Then, Else}) ->
|
||||
beside(text("if"), args_type([Id, Then, Else]));
|
||||
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||
%% Drop the default value
|
||||
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||
@@ -274,7 +290,9 @@ type(T = {id, _, _}) -> name(T);
|
||||
type(T = {qid, _, _}) -> name(T);
|
||||
type(T = {con, _, _}) -> name(T);
|
||||
type(T = {qcon, _, _}) -> name(T);
|
||||
type(T = {tvar, _, _}) -> name(T).
|
||||
type(T = {tvar, _, _}) -> name(T);
|
||||
|
||||
type(var_args) -> text("var_args").
|
||||
|
||||
-spec args_type([aeso_syntax:type()]) -> doc().
|
||||
args_type(Args) ->
|
||||
@@ -290,12 +308,11 @@ tuple_type(Factors) ->
|
||||
, text(")")
|
||||
]).
|
||||
|
||||
-spec arg_expr(aeso_syntax:arg_expr()) -> doc().
|
||||
arg_expr({named_arg, _, Name, E}) ->
|
||||
follow(hsep(expr(Name), text("=")), expr(E));
|
||||
arg_expr(E) -> expr(E).
|
||||
|
||||
-spec expr_p(integer(), aeso_syntax:expr()) -> doc().
|
||||
-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}) ->
|
||||
paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E)));
|
||||
expr_p(P, If = {'if', Ann, Cond, Then, Else}) ->
|
||||
@@ -377,8 +394,13 @@ expr_p(_, {char, _, C}) ->
|
||||
case C of
|
||||
$' -> text("'\\''");
|
||||
$" -> text("'\"'");
|
||||
_ -> S = lists:flatten(io_lib:format("~p", [[C]])),
|
||||
text("'" ++ tl(lists:droplast(S)) ++ "'")
|
||||
_ when C < 16#80 ->
|
||||
S = lists:flatten(io_lib:format("~p", [[C]])),
|
||||
text("'" ++ tl(lists:droplast(S)) ++ "'");
|
||||
_ ->
|
||||
S = lists:flatten(
|
||||
io_lib:format("'~ts'", [list_to_binary(aeso_scan:utf8_encode([C]))])),
|
||||
text(S)
|
||||
end;
|
||||
%% -- Names
|
||||
expr_p(_, E = {id, _, _}) -> name(E);
|
||||
@@ -412,6 +434,7 @@ lc_bind(Let) ->
|
||||
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
|
||||
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
|
||||
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
|
||||
bin_prec('|>') -> {150, 150, 200};
|
||||
bin_prec('||') -> {200, 300, 200};
|
||||
bin_prec('&&') -> {300, 400, 300};
|
||||
bin_prec('<') -> {400, 500, 500};
|
||||
@@ -450,7 +473,7 @@ prefix(P, Op, A) ->
|
||||
app(P, F, Args) ->
|
||||
paren(P > 900,
|
||||
beside(expr_p(900, F),
|
||||
tuple(lists:map(fun arg_expr/1, Args)))).
|
||||
tuple(lists:map(fun expr/1, Args)))).
|
||||
|
||||
field({field, _, LV, E}) ->
|
||||
follow(hsep(lvalue(LV), text("=")), expr(E));
|
||||
@@ -470,8 +493,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));
|
||||
@@ -493,4 +526,3 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) ->
|
||||
_ -> {lists:reverse(Elifs), If}
|
||||
end;
|
||||
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
|
||||
|
||||
|
||||
+35
-27
@@ -7,7 +7,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_scan).
|
||||
|
||||
-export([scan/1]).
|
||||
-export([scan/1, utf8_encode/1]).
|
||||
|
||||
-import(aeso_scan_lib, [token/1, token/2, symbol/0, skip/0,
|
||||
override/2, push/2, pop/1]).
|
||||
@@ -28,7 +28,13 @@ lexer() ->
|
||||
QID = ["(", CON, "\\.)+", ID],
|
||||
QCON = ["(", CON, "\\.)+", CON],
|
||||
OP = "[=!<>+\\-*/:&|?~@^]+",
|
||||
CHAR = "'([^'\\\\]|(\\\\.))'",
|
||||
%% Five cases for a character
|
||||
%% * 1 7-bit ascii, not \ or '
|
||||
%% * 2-4 8-bit values (UTF8)
|
||||
%% * \ followed by a known modifier [aernrtv]
|
||||
%% * \xhh
|
||||
%% * \x{hhh...}
|
||||
CHAR = "'(([\\x00-\\x26\\x28-\\x5b\\x5d-\\x7f])|([\\x00-\\xff][\\x80-\\xff]{1,3})|(\\\\[befnrtv'\\\\])|(\\\\x[0-9a-fA-F]{2,2})|(\\\\x\\{[0-9a-fA-F]*\\}))'",
|
||||
STRING = "\"([^\"\\\\]|(\\\\.))*\"",
|
||||
|
||||
CommentStart = {"/\\*", push(comment, skip())},
|
||||
@@ -38,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 =
|
||||
@@ -77,34 +85,34 @@ scan(String) ->
|
||||
%% -- Helpers ----------------------------------------------------------------
|
||||
|
||||
parse_string([$" | Chars]) ->
|
||||
unescape(Chars).
|
||||
unicode:characters_to_nfc_binary(unescape(Chars)).
|
||||
|
||||
parse_char([$', $\\, Code, $']) ->
|
||||
case Code of
|
||||
$' -> $';
|
||||
$\\ -> $\\;
|
||||
$b -> $\b;
|
||||
$e -> $\e;
|
||||
$f -> $\f;
|
||||
$n -> $\n;
|
||||
$r -> $\r;
|
||||
$t -> $\t;
|
||||
$v -> $\v;
|
||||
_ -> {error, "Bad control sequence: \\" ++ [Code]}
|
||||
end;
|
||||
parse_char([$', C, $']) -> C.
|
||||
parse_char([$' | Chars]) ->
|
||||
case unicode:characters_to_nfc_list(unescape($', Chars, [])) of
|
||||
[Char] -> Char;
|
||||
_Bad -> {error, "Bad character literal: '" ++ Chars}
|
||||
end.
|
||||
|
||||
unescape(Str) -> unescape(Str, []).
|
||||
utf8_encode(Cs) ->
|
||||
binary_to_list(unicode:characters_to_binary(Cs)).
|
||||
|
||||
unescape([$"], Acc) ->
|
||||
unescape(Str) -> unescape($", Str, []).
|
||||
|
||||
unescape(Delim, [Delim], Acc) ->
|
||||
list_to_binary(lists:reverse(Acc));
|
||||
unescape([$\\, $x, D1, D2 | Chars ], Acc) ->
|
||||
unescape(Delim, [$\\, $x, ${ | Chars ], Acc) ->
|
||||
{Ds, [_ | Cs]} = lists:splitwith(fun($}) -> false ; (_) -> true end, Chars),
|
||||
C = list_to_integer(Ds, 16),
|
||||
Utf8Cs = binary_to_list(unicode:characters_to_binary([C])),
|
||||
unescape(Delim, Cs, [Utf8Cs | Acc]);
|
||||
unescape(Delim, [$\\, $x, D1, D2 | Chars ], Acc) ->
|
||||
C = list_to_integer([D1, D2], 16),
|
||||
unescape(Chars, [C | Acc]);
|
||||
unescape([$\\, Code | Chars], Acc) ->
|
||||
Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
|
||||
Utf8Cs = binary_to_list(unicode:characters_to_binary([C])),
|
||||
unescape(Delim, Chars, [Utf8Cs | Acc]);
|
||||
unescape(Delim, [$\\, Code | Chars], Acc) ->
|
||||
Ok = fun(C) -> unescape(Delim, Chars, [C | Acc]) end,
|
||||
case Code of
|
||||
$" -> Ok($");
|
||||
Delim -> Ok(Delim);
|
||||
$\\ -> Ok($\\);
|
||||
$b -> Ok($\b);
|
||||
$e -> Ok($\e);
|
||||
@@ -115,8 +123,8 @@ unescape([$\\, Code | Chars], Acc) ->
|
||||
$v -> Ok($\v);
|
||||
_ -> error("Bad control sequence: \\" ++ [Code]) %% TODO
|
||||
end;
|
||||
unescape([C | Chars], Acc) ->
|
||||
unescape(Chars, [C | Acc]).
|
||||
unescape(Delim, [C | Chars], Acc) ->
|
||||
unescape(Delim, Chars, [C | Acc]).
|
||||
|
||||
strip_underscores(S) ->
|
||||
lists:filter(fun(C) -> C /= $_ end, S).
|
||||
|
||||
+45
-18
@@ -13,19 +13,21 @@
|
||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
||||
-export_type([bin_op/0, un_op/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
||||
-export_type([top_decl/0, decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
|
||||
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||
-export_type([ast/0]).
|
||||
|
||||
-type ast() :: [decl()].
|
||||
-type ast() :: [top_decl()].
|
||||
|
||||
-type ann_line() :: integer().
|
||||
-type ann_col() :: integer().
|
||||
-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()} | {file, ann_file()}
|
||||
| {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface | entrypoint].
|
||||
|
||||
-type name() :: string().
|
||||
-type id() :: {id, ann(), name()}.
|
||||
@@ -34,24 +36,47 @@
|
||||
-type qcon() :: {qcon, ann(), [name()]}.
|
||||
-type tvar() :: {tvar, ann(), name()}.
|
||||
|
||||
-type decl() :: {contract, 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_decl, ann(), id(), type()}
|
||||
| {fun_clauses, ann(), id(), type(), [letbind()]}
|
||||
| {block, ann(), [decl()]}
|
||||
| letfun()
|
||||
| letval(). % Only for error msgs
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
% Can't be toplevel
|
||||
-type scoped_decl()
|
||||
:: {contract_decl, ann(), con()}
|
||||
| {namespace_decl, ann(), con()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
|
||||
| {block, ann(), [scoped_decl()]}
|
||||
| fundecl()
|
||||
| letfun()
|
||||
| letval() % Only for error msgs
|
||||
| decl().
|
||||
|
||||
% Toplevel, can be nested
|
||||
-type decl()
|
||||
:: {contract_main, ann(), con(), [con()], [scoped_decl()]}
|
||||
| {contract_child, ann(), con(), [con()], [scoped_decl()]}
|
||||
| {contract_interface, ann(), con(), [con()], [scoped_decl()]}
|
||||
| {namespace, ann(), con(), [scoped_decl()]}
|
||||
| {using, ann(), con(), namespace_alias(), namespace_parts()}.
|
||||
|
||||
% Toplevel only
|
||||
-type top_decl()
|
||||
:: {pragma, ann(), pragma()}
|
||||
| decl().
|
||||
|
||||
-type compiler_version() :: [non_neg_integer()].
|
||||
|
||||
-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(), [guarded_expr(),...]}.
|
||||
-type letpat() :: {letpat, ann(), id(), pat()}.
|
||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||
|
||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||
-type letbind()
|
||||
:: letfun()
|
||||
| letval().
|
||||
@@ -93,7 +118,7 @@
|
||||
|
||||
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
|
||||
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '||' | '&&' | '..'.
|
||||
| '||' | '&&' | '..' | '|>'.
|
||||
-type un_op() :: '-' | '!'.
|
||||
|
||||
-type expr()
|
||||
@@ -114,7 +139,8 @@
|
||||
| {block, ann(), [stmt()]}
|
||||
| {op(), ann()}
|
||||
| id() | qid() | con() | qcon()
|
||||
| constant().
|
||||
| constant()
|
||||
| letpat().
|
||||
|
||||
-type record_or_map() :: record | map | record_or_map_error.
|
||||
|
||||
@@ -135,7 +161,7 @@
|
||||
-type stmt() :: letbind()
|
||||
| expr().
|
||||
|
||||
-type alt() :: {'case', ann(), pat(), expr()}.
|
||||
-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.
|
||||
|
||||
-type lvalue() :: nonempty_list(elim()).
|
||||
|
||||
@@ -148,6 +174,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
|
||||
)
|
||||
).
|
||||
+99
-62
@@ -1,75 +1,15 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Decoding aevm and fate data to AST
|
||||
%%%
|
||||
%%% @doc Decoding fate data to AST
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_vm_decode).
|
||||
|
||||
-export([ from_aevm/3, from_fate/2 ]).
|
||||
-export([ from_fate/2 ]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_fate_data.hrl").
|
||||
|
||||
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||
|
||||
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
||||
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
|
||||
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
||||
from_aevm(word, {id, _, "int"}, N0) ->
|
||||
<<N:256/signed>> = <<N0:256>>,
|
||||
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
|
||||
true -> {int, [], N} end;
|
||||
from_aevm(word, {id, _, "bits"}, N0) ->
|
||||
<<N:256/signed>> = <<N0:256>>,
|
||||
make_bits(N);
|
||||
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
|
||||
{bytes, [], <<Bytes:Len/unit:8>>};
|
||||
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
|
||||
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
|
||||
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
|
||||
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
||||
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
|
||||
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||
case Val of
|
||||
{variant, 0, []} -> {con, [], "None"};
|
||||
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
|
||||
end;
|
||||
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
|
||||
when length(VmTypes) == length(Types),
|
||||
length(VmTypes) == tuple_size(Val) ->
|
||||
{tuple, [], [from_aevm(VmType, Type, X)
|
||||
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
|
||||
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
|
||||
when length(VmTypes) == length(Fields),
|
||||
length(VmTypes) == tuple_size(Val) ->
|
||||
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|
||||
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
|
||||
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||
when is_map(Map) ->
|
||||
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
|
||||
from_aevm(VmValType, ValType, Val)}
|
||||
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
|
||||
when length(VmCons) == length(Cons),
|
||||
length(VmCons) > Tag ->
|
||||
VmTypes = lists:nth(Tag + 1, VmCons),
|
||||
ConType = lists:nth(Tag + 1, Cons),
|
||||
from_aevm(VmTypes, ConType, Args);
|
||||
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
|
||||
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
|
||||
when length(VmTypes) == length(Types),
|
||||
length(VmTypes) == length(Args) ->
|
||||
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|
||||
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
|
||||
from_aevm(_VmType, _Type, _Data) ->
|
||||
throw(cannot_translate_to_sophia).
|
||||
|
||||
|
||||
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
|
||||
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
|
||||
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
|
||||
@@ -95,6 +35,8 @@ from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
|
||||
when length(Types) == tuple_size(Val) ->
|
||||
{tuple, [], [from_fate(Type, X)
|
||||
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
|
||||
from_fate({record_t, [{field_t, _, FName, FType}]}, Val) ->
|
||||
{record, [], [{field, [], [{proj, [], FName}], from_fate(FType, Val)}]};
|
||||
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
|
||||
when length(Fields) == tuple_size(Val) ->
|
||||
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|
||||
@@ -118,10 +60,105 @@ 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},
|
||||
I32 = {bytes_t, [], 32},
|
||||
I48 = {bytes_t, [], 48},
|
||||
Qid = fun(Name) -> {qid, [], Name} end,
|
||||
Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end,
|
||||
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
|
||||
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"], []);
|
||||
|
||||
{["MCL_BLS12_381", "fp"], X} ->
|
||||
App(["MCL_BLS12_381", "fp"], [Chk(I32, X)]);
|
||||
{["MCL_BLS12_381", "fr"], X} ->
|
||||
App(["MCL_BLS12_381", "fr"], [Chk(I48, X)]);
|
||||
|
||||
_ ->
|
||||
throw(cannot_translate_to_sophia)
|
||||
end.
|
||||
|
||||
make_bits(N) ->
|
||||
Id = fun(F) -> {qid, [], ["Bits", F]} end,
|
||||
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
-module(aeso_warnings).
|
||||
|
||||
-record(warn, { pos :: aeso_errors:pos()
|
||||
, message :: iolist()
|
||||
}).
|
||||
|
||||
-opaque warning() :: #warn{}.
|
||||
|
||||
-export_type([warning/0]).
|
||||
|
||||
-export([ new/1
|
||||
, new/2
|
||||
, warn_to_err/2
|
||||
, sort_warnings/1
|
||||
, pp/1
|
||||
]).
|
||||
|
||||
new(Msg) ->
|
||||
new(aeso_errors:pos(0, 0), Msg).
|
||||
|
||||
new(Pos, Msg) ->
|
||||
#warn{ pos = Pos, message = Msg }.
|
||||
|
||||
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
|
||||
aeso_errors:new(Kind, Pos, lists:flatten(Msg)).
|
||||
|
||||
sort_warnings(Warnings) ->
|
||||
lists:sort(fun(W1, W2) -> W1#warn.pos =< W2#warn.pos end, Warnings).
|
||||
|
||||
pp(#warn{ pos = Pos, message = Msg }) ->
|
||||
lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])).
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Contract Language for aeternity"},
|
||||
{vsn, "4.3.0"},
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "7.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
+79
-91
@@ -5,7 +5,6 @@
|
||||
|
||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
||||
-define(DUMMY_HASH_WORD, 16#123).
|
||||
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
|
||||
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
|
||||
|
||||
sandbox(Code) ->
|
||||
@@ -20,12 +19,6 @@ sandbox(Code) ->
|
||||
{error, loop}
|
||||
end.
|
||||
|
||||
malicious_from_binary_test() ->
|
||||
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
||||
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
|
||||
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
||||
ok.
|
||||
|
||||
from_words(Ws) ->
|
||||
<< <<(from_word(W))/binary>> || W <- Ws >>.
|
||||
|
||||
@@ -37,23 +30,14 @@ from_word(S) when is_list(S) ->
|
||||
<<Len:256, Bin/binary>>.
|
||||
|
||||
encode_decode_test() ->
|
||||
encode_decode(word, 42),
|
||||
42 = encode_decode(word, 42),
|
||||
-1 = encode_decode(signed_word, -1),
|
||||
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
|
||||
{} = encode_decode({tuple, []}, {}),
|
||||
{42} = encode_decode({tuple, [word]}, {42}),
|
||||
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
|
||||
[] = encode_decode({list, word}, []),
|
||||
[32] = encode_decode({list, word}, [32]),
|
||||
none = encode_decode({option, word}, none),
|
||||
{some, 1} = encode_decode({option, word}, {some, 1}),
|
||||
string = encode_decode(typerep, string),
|
||||
word = encode_decode(typerep, word),
|
||||
{list, word} = encode_decode(typerep, {list, word}),
|
||||
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
|
||||
1 = encode_decode(word, 1),
|
||||
0 = encode_decode(word, 0),
|
||||
Tests =
|
||||
[42, 1, 0 -1, <<"Hello">>,
|
||||
{tuple, {}}, {tuple, {42}}, {tuple, {21, 37}},
|
||||
[], [42], [21, 37],
|
||||
{variant, [0, 1], 0, {}}, {variant, [0, 1], 1, {42}}, {variant, [2], 0, {21, 37}},
|
||||
{typerep, string}, {typerep, integer}, {typerep, {list, integer}}, {typerep, {tuple, [integer]}}
|
||||
],
|
||||
[?assertEqual(Test, encode_decode(Test)) || Test <- Tests],
|
||||
ok.
|
||||
|
||||
encode_decode_sophia_test() ->
|
||||
@@ -72,55 +56,67 @@ encode_decode_sophia_test() ->
|
||||
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
|
||||
ok.
|
||||
|
||||
to_sophia_value_mcl_bls12_381_test() ->
|
||||
Code = "include \"BLS12_381.aes\"\n"
|
||||
"contract C =\n"
|
||||
" entrypoint test_bls12_381_fp(x : int) = BLS12_381.int_to_fp(x)\n"
|
||||
" entrypoint test_bls12_381_fr(x : int) = BLS12_381.int_to_fr(x)\n"
|
||||
" entrypoint test_bls12_381_g1(x : int) = BLS12_381.mk_g1(x, x, x)\n",
|
||||
|
||||
Opts = [{backend, fate}],
|
||||
|
||||
CallValue32 = aeb_fate_encoding:serialize({bytes, <<20:256>>}),
|
||||
CallValue48 = aeb_fate_encoding:serialize({bytes, <<55:384>>}),
|
||||
CallValueTp = aeb_fate_encoding:serialize({tuple, {{bytes, <<15:256>>}, {bytes, <<160:256>>}, {bytes, <<1234:256>>}}}),
|
||||
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue32, Opts),
|
||||
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue48, Opts),
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue48, Opts),
|
||||
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue32, Opts),
|
||||
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_g1", ok, CallValueTp, Opts),
|
||||
|
||||
ok.
|
||||
|
||||
to_sophia_value_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
|
||||
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
|
||||
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
|
||||
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
|
||||
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "f", ok, encode(12)),
|
||||
?assertEqual("Data error:\nCannot translate FATE value 12\n of Sophia type string\n", aeso_errors:pp(Err1)),
|
||||
|
||||
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
|
||||
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
|
||||
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
|
||||
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
|
||||
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "f", revert, encode(12)),
|
||||
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err2)),
|
||||
ok.
|
||||
|
||||
encode_calldata_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
|
||||
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
|
||||
"when checking the application at line 5, column 34 of\n"
|
||||
" x : (int) => string\nto arguments\n true : bool\n",
|
||||
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
|
||||
ExpErr1 = "Type error at line 5, col 34:\nCannot unify `int` and `bool`\n"
|
||||
"when checking the application of\n"
|
||||
" `f : (int) => string`\n"
|
||||
"to arguments\n"
|
||||
" `true : bool`\n",
|
||||
{error, [Err1]} = aeso_compiler:create_calldata(Code, "f", ["true"]),
|
||||
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
|
||||
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
|
||||
|
||||
ok.
|
||||
|
||||
decode_calldata_neg_test() ->
|
||||
Code1 = [ "contract Foo =\n"
|
||||
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
||||
" entrypoint f(x : int) : string = \"hello\"\n" ],
|
||||
Code2 = [ "contract Foo =\n"
|
||||
" entrypoint x(y : string) : int = 42\n" ],
|
||||
" entrypoint f(x : string) : int = 42\n" ],
|
||||
|
||||
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
|
||||
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
|
||||
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "f", ["42"]),
|
||||
|
||||
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
|
||||
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
|
||||
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
|
||||
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
|
||||
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
|
||||
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "f", <<1,2,3>>),
|
||||
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err1)),
|
||||
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "f", CallDataFATE),
|
||||
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err2)),
|
||||
|
||||
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
|
||||
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
|
||||
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE),
|
||||
?assertEqual("Data error at line 1, col 1:\nFunction 'x' is missing in contract\n", aeso_errors:pp(Err3)),
|
||||
ok.
|
||||
|
||||
|
||||
@@ -133,8 +129,7 @@ encode_decode_sophia_string(SophiaType, String) ->
|
||||
, " datatype variant = Red | Blue(map(string, int))\n"
|
||||
, " entrypoint foo : arg_type => arg_type\n" ],
|
||||
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
|
||||
{ok, _, {[Type], _}, [Arg]} ->
|
||||
io:format("Type ~p~n", [Type]),
|
||||
{ok, _, [Arg]} ->
|
||||
Data = encode(Arg),
|
||||
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
|
||||
{ok, Sophia} ->
|
||||
@@ -150,30 +145,32 @@ encode_decode_sophia_string(SophiaType, String) ->
|
||||
|
||||
calldata_test() ->
|
||||
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
|
||||
Map = #{ <<"a">> => 4 },
|
||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||
[{variant, [0,1], 1, {#{ <<"a">> := 4 }}}, {tuple, {{tuple, {<<"b">>, 5}}, {variant, [0,1], 0, {}}}}] =
|
||||
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
||||
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
|
||||
[{bytes, <<291:256>>}, {address, <<1110:256>>}] =
|
||||
encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||
[{bytes, <<291:256>>}, {bytes, <<291:256>>}] =
|
||||
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
|
||||
|
||||
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||
[119, {bytes, <<0:64/unit:8>>}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||
|
||||
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
[{contract, <<1110:256>>}] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_init_test() ->
|
||||
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
|
||||
encode_decode_calldata("init", ["int"], ["42"]),
|
||||
|
||||
Code = parameterized_contract("foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
|
||||
encode_decode_calldata_(Code, "init", []),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_indent_test() ->
|
||||
Test = fun(Extra) ->
|
||||
Code = parameterized_contract(Extra, "foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "foo", ["42"], word)
|
||||
encode_decode_calldata_(Code, "foo", ["42"])
|
||||
end,
|
||||
Test(" stateful entrypoint bla() = ()"),
|
||||
Test(" type x = int"),
|
||||
@@ -190,7 +187,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"
|
||||
@@ -202,9 +199,9 @@ oracle_test() ->
|
||||
"contract OracleTest =\n"
|
||||
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n"
|
||||
" Oracle.get_question(o, q)\n",
|
||||
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
||||
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
|
||||
?assertEqual({ok, "question", [{oracle, <<291:256>>}, {oracle_query, <<1110:256>>}]},
|
||||
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||
"oq_1111111111111111111111111111113AFEFpt5"], [no_code])),
|
||||
|
||||
ok.
|
||||
|
||||
@@ -220,35 +217,26 @@ permissive_literals_fail_test() ->
|
||||
ok.
|
||||
|
||||
encode_decode_calldata(FunName, Types, Args) ->
|
||||
encode_decode_calldata(FunName, Types, Args, word).
|
||||
|
||||
encode_decode_calldata(FunName, Types, Args, RetType) ->
|
||||
Code = parameterized_contract(FunName, Types),
|
||||
encode_decode_calldata_(Code, FunName, Args, RetType).
|
||||
encode_decode_calldata_(Code, FunName, Args).
|
||||
|
||||
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
||||
encode_decode_calldata_(Code, FunName, Args) ->
|
||||
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
|
||||
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
|
||||
?assertEqual(RetType, RetVMType),
|
||||
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
|
||||
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
||||
{ok, _, _} = aeso_compiler:check_call(Code, FunName, Args, [no_code]),
|
||||
case FunName of
|
||||
"init" ->
|
||||
ok;
|
||||
[];
|
||||
_ ->
|
||||
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
|
||||
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
|
||||
?assertMatch({X, X}, {Args, Values})
|
||||
end,
|
||||
tuple_to_list(ArgTuple).
|
||||
{ok, FateArgs} = aeb_fate_abi:decode_calldata(FunName, Calldata),
|
||||
FateArgs
|
||||
end.
|
||||
|
||||
encode_decode(T, D) ->
|
||||
?assertEqual(D, decode(T, encode(D))),
|
||||
encode_decode(D) ->
|
||||
?assertEqual(D, decode(encode(D))),
|
||||
D.
|
||||
|
||||
encode(D) ->
|
||||
aeb_heap:to_binary(D).
|
||||
aeb_fate_encoding:serialize(D).
|
||||
|
||||
decode(T,B) ->
|
||||
{ok, D} = aeb_heap:from_binary(T, B),
|
||||
D.
|
||||
decode(B) ->
|
||||
aeb_fate_encoding:deserialize(B).
|
||||
|
||||
+68
-52
@@ -11,15 +11,19 @@ test_contract(N) ->
|
||||
{Contract,MapACI,DecACI} = test_cases(N),
|
||||
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
|
||||
?assertEqual([MapACI], JSON),
|
||||
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
|
||||
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)),
|
||||
%% Check if the compiler provides correct aci
|
||||
{ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]),
|
||||
?assertEqual(JSON, JSON2).
|
||||
|
||||
test_cases(1) ->
|
||||
Contract = <<"payable contract C =\n"
|
||||
" payable stateful entrypoint a(i : int) = i+1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>,
|
||||
type_defs => [],
|
||||
payable => true,
|
||||
typedefs => [],
|
||||
payable => true,
|
||||
kind => contract_main,
|
||||
functions =>
|
||||
[#{name => <<"a">>,
|
||||
arguments =>
|
||||
@@ -28,63 +32,64 @@ 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,
|
||||
typedefs =>
|
||||
[#{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">>,
|
||||
type_defs =>
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
||||
vars => [#{name => <<"'a">>}]}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
[#{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">>,
|
||||
typedefs =>
|
||||
[#{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"
|
||||
" entrypoint a : (C.bert(string)) => int\n">>,
|
||||
" datatype bert('a) = Bin('a)\n"
|
||||
" entrypoint a : (C.bert(string)) => int\n">>,
|
||||
{Contract,MapACI,DecACI}.
|
||||
|
||||
%% Rounttrip
|
||||
%% Roundtrip
|
||||
aci_test_() ->
|
||||
[{"Testing ACI generation for " ++ ContractName,
|
||||
fun() -> aci_test_contract(ContractName) end}
|
||||
@@ -94,23 +99,35 @@ all_contracts() -> aeso_compiler_tests:compilable_contracts().
|
||||
|
||||
aci_test_contract(Name) ->
|
||||
String = aeso_test_utils:read_contract(Name),
|
||||
Opts = [{include, {file_system, [aeso_test_utils:contract_path()]}}],
|
||||
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
|
||||
Opts = case lists:member(Name, aeso_compiler_tests:debug_mode_contracts()) of
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
|
||||
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} | 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
|
||||
Ast ->
|
||||
try
|
||||
%% io:format("AST: ~120p\n", [Ast]),
|
||||
aeso_ast_infer_types:infer(Ast, [])
|
||||
aeso_ast_infer_types:infer(Ast, [no_code])
|
||||
catch throw:{type_errors, TE} ->
|
||||
io:format("Type error:\n~s\n", [TE]),
|
||||
error(TE);
|
||||
@@ -122,4 +139,3 @@ check_stub(Stub, Options) ->
|
||||
_ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ],
|
||||
error({parse_errors, Errs})
|
||||
end.
|
||||
|
||||
|
||||
@@ -19,19 +19,9 @@ calldata_test_() ->
|
||||
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||
fun() ->
|
||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
||||
AevmExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(aevm)) of
|
||||
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(fate)) of
|
||||
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs = ast_exprs(ContractString, Fun, Args),
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
?assertEqual(ParsedExprs, FateExprs),
|
||||
ok
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
@@ -39,28 +29,18 @@ calldata_aci_test_() ->
|
||||
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||
fun() ->
|
||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
||||
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
|
||||
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString, [no_code]),
|
||||
ContractACI = binary_to_list(ContractACIBin),
|
||||
io:format("ACI:\n~s\n", [ContractACIBin]),
|
||||
AevmExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(aevm)) of
|
||||
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs =
|
||||
case not lists:member(ContractName, not_yet_compilable(fate)) of
|
||||
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
FateExprs = ast_exprs(ContractACI, Fun, Args),
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
?assertEqual(ParsedExprs, FateExprs),
|
||||
ok
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract, _, _, [{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) ->
|
||||
@@ -75,6 +55,8 @@ strip_ann1(L) when is_list(L) ->
|
||||
lists:map(fun strip_ann/1, L);
|
||||
strip_ann1(X) -> X.
|
||||
|
||||
ast_exprs(ContractString, Fun, Args) ->
|
||||
ast_exprs(ContractString, Fun, Args, []).
|
||||
ast_exprs(ContractString, Fun, Args, Opts) ->
|
||||
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
|
||||
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
|
||||
@@ -112,7 +94,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", []},
|
||||
@@ -138,8 +141,3 @@ compilable_contracts() ->
|
||||
{"stub", "foo", ["-42"]},
|
||||
{"payable", "foo", ["42"]}
|
||||
].
|
||||
|
||||
not_yet_compilable(fate) ->
|
||||
[];
|
||||
not_yet_compilable(aevm) ->
|
||||
[].
|
||||
|
||||
+863
-453
File diff suppressed because it is too large
Load Diff
@@ -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.",
|
||||
@@ -63,7 +63,8 @@ simple_contracts_test_() ->
|
||||
%% Parse tests of example contracts
|
||||
[ {lists:concat(["Parse the ", Contract, " contract."]),
|
||||
fun() -> roundtrip_contract(Contract) end}
|
||||
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ]
|
||||
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof,
|
||||
multi_sig, simple_storage, fundme, dutch_auction, utf8] ]
|
||||
}.
|
||||
|
||||
parse_contract(Name) ->
|
||||
@@ -85,7 +86,7 @@ parse_expr(Text) ->
|
||||
round_trip(Text) ->
|
||||
Contract = parse_string(Text),
|
||||
Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))),
|
||||
Contract1 = parse_string(Text1),
|
||||
Contract1 = parse_string(aeso_scan:utf8_encode(Text1)),
|
||||
NoSrcLoc = remove_line_numbers(Contract),
|
||||
NoSrcLoc1 = remove_line_numbers(Contract1),
|
||||
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
## Requires ocaml >= 4.02, < 4.06
|
||||
## and reason-3.0.0 (opam install reason).
|
||||
|
||||
default : voting_test
|
||||
|
||||
%.ml : %.re
|
||||
refmt -p ml $< > $@
|
||||
|
||||
|
||||
voting_test : rte.ml voting.ml voting_test.ml
|
||||
ocamlopt -o $@ $^
|
||||
|
||||
clean :
|
||||
rm -f *.cmi *.cmx *.ml *.o voting_test
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
contract Identity =
|
||||
function main (x:int) = x
|
||||
function main_fun (x:int) = x
|
||||
|
||||
function __call() = 12
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// A simple test of the abort built-in function.
|
||||
|
||||
contract AbortTest =
|
||||
|
||||
record state = { value : int }
|
||||
|
||||
public function init(v : int) =
|
||||
{ value = v }
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) : unit =
|
||||
put_value(v)
|
||||
revert_abort(s)
|
||||
|
||||
// Accessing the value
|
||||
public function get_value() = state.value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_values() : list(int) = [state.value]
|
||||
public function put_values(v : int) = put(state{value = v})
|
||||
|
||||
// Some basic statistics
|
||||
public function get_stats(acct : address) =
|
||||
( Contract.balance, Chain.balance(acct) )
|
||||
|
||||
// Abort functions.
|
||||
private function revert_abort(s : string) =
|
||||
abort(s)
|
||||
|
||||
// This is still legal but will be stripped out.
|
||||
// TODO: This function confuses the type inference, so it cannot be present.
|
||||
//private function abort(s : string) = 42
|
||||
@@ -1,27 +0,0 @@
|
||||
contract Interface =
|
||||
function do_abort : (int, string) => unit
|
||||
function get_value : () => int
|
||||
function put_value : (int) => unit
|
||||
function get_values : () => list(int)
|
||||
function put_values : (int) => unit
|
||||
|
||||
contract AbortTestInt =
|
||||
|
||||
record state = {r : Interface, value : int}
|
||||
|
||||
public function init(r : Interface, value : int) =
|
||||
{r = r, value = value}
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) =
|
||||
put_value(v)
|
||||
state.r.do_abort(v + 100, s)
|
||||
|
||||
// Accessing the value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_value() = state.value
|
||||
public function get_values() : list(int) =
|
||||
state.value :: state.r.get_values()
|
||||
public function put_values(v : int) =
|
||||
put_value(v)
|
||||
state.r.put_values(v + 1000)
|
||||
@@ -1,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 =
|
||||
|
||||
+29
-3
@@ -1,5 +1,7 @@
|
||||
contract C = entrypoint init() = ()
|
||||
|
||||
// AENS tests
|
||||
contract AENSTest =
|
||||
main contract AENSTest =
|
||||
|
||||
// Name resolution
|
||||
|
||||
@@ -9,10 +11,19 @@ contract AENSTest =
|
||||
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) =
|
||||
AENS.resolve(name, key)
|
||||
|
||||
// Transactions
|
||||
|
||||
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash) : unit = // Commitment hash
|
||||
chash : hash) : unit = // Commitment hash
|
||||
AENS.preclaim(addr, chash)
|
||||
|
||||
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
@@ -33,7 +44,22 @@ contract AENSTest =
|
||||
sign : signature) : unit =
|
||||
AENS.claim(addr, name, salt, name_fee, signature = sign)
|
||||
|
||||
// TODO: update() -- how to handle pointers?
|
||||
|
||||
stateful entrypoint update(owner : address,
|
||||
name : string,
|
||||
ttl : option(Chain.ttl),
|
||||
client_ttl : option(int),
|
||||
pointers : option(map(string, AENS.pointee))) : unit =
|
||||
AENS.update(owner, name, ttl, client_ttl, pointers)
|
||||
|
||||
stateful entrypoint signedUpdate(owner : address,
|
||||
name : string,
|
||||
ttl : option(Chain.ttl),
|
||||
client_ttl : option(int),
|
||||
pointers : option(map(string, AENS.pointee)),
|
||||
sign : signature) : unit =
|
||||
AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign)
|
||||
|
||||
|
||||
stateful entrypoint transfer(owner : address,
|
||||
new_owner : address,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
contract AENSUpdate =
|
||||
stateful entrypoint update_name(owner : address, name : string) =
|
||||
let p1 : AENS.pointee = AENS.AccountPt(Call.caller)
|
||||
let p2 : AENS.pointee = AENS.OraclePt(Call.caller)
|
||||
let p3 : AENS.pointee = AENS.ContractPt(Call.caller)
|
||||
let p4 : AENS.pointee = AENS.ChannelPt(Call.caller)
|
||||
AENS.update(owner, name, None, None,
|
||||
Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2,
|
||||
["contract_pubkey"] = p3, ["misc"] = p4 }))
|
||||
|
||||
entrypoint get_ttl(name : string) =
|
||||
switch(AENS.lookup(name))
|
||||
Some(AENS.Name(_, FixedTTL(ttl), _)) => ttl
|
||||
|
||||
entrypoint expiry(o : oracle(int, int)) : int =
|
||||
Oracle.expiry(o)
|
||||
|
||||
@@ -36,6 +36,8 @@ contract AllSyntax =
|
||||
entrypoint init() = {
|
||||
johann = 1000,
|
||||
wolfgang = -10,
|
||||
|
||||
/* TODO: This does not compile because of bug in the parser tester.
|
||||
von = (2 + 2, 0, List.sum([x | k <- [1,2,3]
|
||||
, let l = k + 1
|
||||
, if(l < 10)
|
||||
@@ -43,11 +45,13 @@ contract AllSyntax =
|
||||
, Adam <- [Adam, Mickiewicz]
|
||||
, let x = f(l)
|
||||
])),
|
||||
*/
|
||||
von = (2 + 2, 0, List.sum([1,2,3,4])),
|
||||
goethe = () }
|
||||
|
||||
function f() =
|
||||
let kp = "nietzsche"
|
||||
let p = "Пушкин"
|
||||
// let p = "Пушкин" // TODO: this also doesn't do right round_trip...
|
||||
let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210)
|
||||
|
||||
let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
@@ -76,3 +80,4 @@ contract AllSyntax =
|
||||
let sh : shakespeare(shakespeare(int)) =
|
||||
{wolfgang = state}
|
||||
sh{wolfgang.wolfgang = sh.wolfgang} // comment
|
||||
exit("hope you had fun reading this")
|
||||
|
||||
@@ -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
-1
@@ -5,5 +5,5 @@ contract BadAENSresolve =
|
||||
function fail() : t(int) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main() = ()
|
||||
entrypoint main_fun() = ()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
contract BadAENSresolve =
|
||||
using AENS
|
||||
|
||||
type t('a) = option(list('a))
|
||||
|
||||
function fail() : t(int) =
|
||||
resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main_fun() = ()
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
function
|
||||
g(1) = 2
|
||||
f(2) = 3
|
||||
h(1) = 123
|
||||
@@ -0,0 +1,6 @@
|
||||
contract interface Remote =
|
||||
entrypoint id : int => int
|
||||
|
||||
contract ProtectedCall =
|
||||
entrypoint bad(r : Remote) =
|
||||
r.id(protected = 0 == 1, 18)
|
||||
@@ -0,0 +1,5 @@
|
||||
contract C =
|
||||
record state = { foo : int }
|
||||
entrypoint init(i : int) =
|
||||
state{ foo = i,
|
||||
foo = 42 }
|
||||
@@ -1,3 +1,3 @@
|
||||
function square(x) = x ^ 2
|
||||
contract Main =
|
||||
entrypoint main() = square(10)
|
||||
entrypoint main_fun() = square(10)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// namespace Chain =
|
||||
// record tx = { paying_for : option(Chain.paying_for_tx)
|
||||
// , ga_metas : list(Chain.ga_meta_tx)
|
||||
// , actor : address
|
||||
// , fee : int
|
||||
// , ttl : int
|
||||
// , tx : Chain.base_tx }
|
||||
|
||||
// datatype ga_meta_tx = GAMetaTx(address, int)
|
||||
// datatype paying_for_tx = PayingForTx(address, int)
|
||||
// datatype base_tx = SpendTx(address, int, string)
|
||||
// | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
|
||||
// | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
|
||||
// | NameRevokeTx(hash) | NameTransferTx(address, string)
|
||||
// | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
|
||||
// | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)
|
||||
// | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)
|
||||
// | ContractCreateTx(int) | ContractCallTx(address, int)
|
||||
// | GAAttachTx
|
||||
|
||||
|
||||
// Contract replicating "normal" Aeternity authentication
|
||||
contract BasicAuthTx =
|
||||
record state = { nonce : int, owner : address }
|
||||
datatype foo = Bar | Baz()
|
||||
|
||||
entrypoint init() = { nonce = 1, owner = Call.caller }
|
||||
|
||||
stateful entrypoint authorize(n : int, s : signature) : bool =
|
||||
require(n >= state.nonce, "Nonce too low")
|
||||
require(n =< state.nonce, "Nonce too high")
|
||||
put(state{ nonce = n + 1 })
|
||||
switch(Auth.tx_hash)
|
||||
None => abort("Not in Auth context")
|
||||
Some(tx_hash) =>
|
||||
let Some(tx0) = Auth.tx
|
||||
let x : option(Chain.paying_for_tx) = tx0.paying_for
|
||||
let x : list(Chain.ga_meta_tx) = tx0.ga_metas
|
||||
let x : int = tx0.fee + tx0.ttl
|
||||
let x : address = tx0.actor
|
||||
let x : Chain.tx = { tx = Chain.NamePreclaimTx, paying_for = None, ga_metas = [],
|
||||
fee = 123, ttl = 0, actor = Call.caller }
|
||||
switch(tx0.tx)
|
||||
Chain.SpendTx(receiver, amount, payload) => verify(tx_hash, n, s)
|
||||
Chain.OracleRegisterTx => false
|
||||
Chain.OracleQueryTx => false
|
||||
Chain.OracleResponseTx => false
|
||||
Chain.OracleExtendTx => false
|
||||
Chain.NamePreclaimTx => false
|
||||
Chain.NameClaimTx(name) => false
|
||||
Chain.NameUpdateTx(name) => false
|
||||
Chain.NameRevokeTx(name) => false
|
||||
Chain.NameTransferTx(to, name) => false
|
||||
Chain.ChannelCreateTx(other_party) => false
|
||||
Chain.ChannelDepositTx(channel, amount) => false
|
||||
Chain.ChannelWithdrawTx(channel, amount) => false
|
||||
Chain.ChannelForceProgressTx(channel) => false
|
||||
Chain.ChannelCloseMutualTx(channel) => false
|
||||
Chain.ChannelCloseSoloTx(channel) => false
|
||||
Chain.ChannelSlashTx(channel) => false
|
||||
Chain.ChannelSettleTx(channel) => false
|
||||
Chain.ChannelSnapshotSoloTx(channel) => false
|
||||
Chain.ContractCreateTx(amount) => false
|
||||
Chain.ContractCallTx(ct_address, amount) => false
|
||||
Chain.GAAttachTx => false
|
||||
|
||||
function verify(tx_hash, n, s) =
|
||||
Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s)
|
||||
|
||||
entrypoint to_sign(h : hash, n : int) =
|
||||
Crypto.blake2b((h, n))
|
||||
|
||||
entrypoint weird_string() : string =
|
||||
"\x19Weird String\x42\nMore\n"
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
include "String.aes"
|
||||
contract BytesToX =
|
||||
|
||||
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
entrypoint f() = F.g()
|
||||
@@ -1,8 +0,0 @@
|
||||
contract ChannelEnv =
|
||||
public function coinbase() : address = Chain.coinbase
|
||||
|
||||
public function timestamp() : int = Chain.timestamp
|
||||
|
||||
public function block_height() : int = Chain.block_height
|
||||
|
||||
public function difficulty() : int = Chain.difficulty
|
||||
@@ -1,7 +0,0 @@
|
||||
contract ChannelOnChainContractNameResolution =
|
||||
|
||||
public function can_resolve(name: string, key: string) : bool =
|
||||
switch(AENS.resolve(name, key) : option(string))
|
||||
None => false
|
||||
Some(_address) => true
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
contract ChannelOnChainContractOracle =
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
record state = { oracle : oracle_id,
|
||||
question : string,
|
||||
bets : map(string, address)
|
||||
}
|
||||
|
||||
|
||||
public function init(oracle: oracle_id, question: string) : state =
|
||||
{ oracle = oracle,
|
||||
question = question,
|
||||
bets = {}
|
||||
}
|
||||
|
||||
public stateful function place_bet(answer: string) =
|
||||
switch(Map.lookup(answer, state.bets))
|
||||
None =>
|
||||
put(state{ bets = state.bets{[answer] = Call.caller}})
|
||||
"ok"
|
||||
Some(_value) =>
|
||||
"bet_already_taken"
|
||||
|
||||
public function query_fee() =
|
||||
Oracle.query_fee(state.oracle)
|
||||
|
||||
public function get_question(q: query_id) =
|
||||
Oracle.get_question(state.oracle, q)
|
||||
|
||||
public stateful function resolve(q: query_id) =
|
||||
switch(Oracle.get_answer(state.oracle, q))
|
||||
None =>
|
||||
"no response"
|
||||
Some(result) =>
|
||||
if(state.question == Oracle.get_question(state.oracle, q))
|
||||
switch(Map.lookup(result, state.bets))
|
||||
None =>
|
||||
"no winning bet"
|
||||
Some(winner) =>
|
||||
Chain.spend(winner, Contract.balance)
|
||||
"ok"
|
||||
else
|
||||
"different question"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
contract Remote =
|
||||
function get : () => int
|
||||
function can_resolve : (string, string) => bool
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
function remote_resolve(r : Remote, name: string, key: string) : bool =
|
||||
r.can_resolve(name, key)
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
|
||||
contract Chess =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type state = board
|
||||
|
||||
private function get_row(r, m : board) =
|
||||
Map.lookup_default(r, m, {})
|
||||
|
||||
private function set_piece(r, c, p, m : board) =
|
||||
m { [r] = get_row(r, m) { [c] = p } }
|
||||
|
||||
private function get_piece(r, c, m : board) =
|
||||
Map.lookup(c, get_row(r, m))
|
||||
|
||||
private function from_list(xs, m : board) =
|
||||
switch(xs)
|
||||
[] => m
|
||||
(r, c, p) :: xs => from_list(xs, set_piece(r, c, p, m))
|
||||
|
||||
function init() =
|
||||
from_list([ (2, 1, "white pawn"), (7, 1, "black pawn")
|
||||
, (2, 2, "white pawn"), (7, 2, "black pawn")
|
||||
, (2, 3, "white pawn"), (7, 3, "black pawn")
|
||||
, (2, 4, "white pawn"), (7, 4, "black pawn")
|
||||
, (2, 5, "white pawn"), (7, 5, "black pawn")
|
||||
, (2, 6, "white pawn"), (7, 6, "black pawn")
|
||||
, (2, 7, "white pawn"), (7, 7, "black pawn")
|
||||
, (2, 8, "white pawn"), (7, 8, "black pawn")
|
||||
, (1, 1, "white rook"), (8, 1, "black rook")
|
||||
, (1, 2, "white knight"), (8, 2, "black knight")
|
||||
, (1, 3, "white bishop"), (8, 3, "black bishop")
|
||||
, (1, 4, "white queen"), (8, 4, "black queen")
|
||||
, (1, 5, "white king"), (8, 5, "black king")
|
||||
, (1, 6, "white bishop"), (8, 6, "black bishop")
|
||||
, (1, 7, "white knight"), (8, 7, "black knight")
|
||||
, (1, 8, "white rook"), (8, 8, "black rook")
|
||||
], {})
|
||||
|
||||
function piece(r, c) = get_piece(r, c, state)
|
||||
|
||||
function move_piece(r, c, r1, c1) =
|
||||
switch(piece(r, c))
|
||||
Some(p) => put(set_piece(r1, c1, p, state))
|
||||
|
||||
function destroy_piece(r, c) =
|
||||
put(state{ [r] = Map.delete(c, get_row(r, state)) })
|
||||
|
||||
function delete_row(r) =
|
||||
put(Map.delete(r, state))
|
||||
|
||||
@@ -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,5 @@
|
||||
contract C =
|
||||
entrypoint f : () => unit
|
||||
|
||||
main contract M =
|
||||
entrypoint f() = 123
|
||||
@@ -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
|
||||
@@ -1,2 +0,0 @@
|
||||
namespace LastDeclarationIsNotAContract =
|
||||
function add(x, y) = x + y
|
||||
@@ -1,3 +0,0 @@
|
||||
contract MissingInitFunction =
|
||||
type state = int * int
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user