Compare commits
133 Commits
Author | SHA1 | Date | |
---|---|---|---|
dbab49936d | |||
![]() |
927cd42592 | ||
![]() |
16308a7840 | ||
![]() |
46a307432f | ||
![]() |
83dcc6dbc4 | ||
![]() |
ffdd4ecf26 | ||
![]() |
51bae61736 | ||
![]() |
31301911a2 | ||
![]() |
de426a210b | ||
![]() |
944ed49f0b | ||
![]() |
1387e814f8 | ||
![]() |
44d6982d66 | ||
![]() |
aa532046d3 | ||
![]() |
fbaab570f2 | ||
![]() |
1a80f3faa0 | ||
![]() |
745eeda858 | ||
![]() |
78b758c337 | ||
![]() |
108cb1f948 | ||
![]() |
2c8dcf8032 | ||
![]() |
c51d0a5e21 | ||
![]() |
e44174b71c | ||
![]() |
dcea538e11 | ||
![]() |
f60f9122ba | ||
![]() |
5c3b42aff1 | ||
![]() |
dbeb792ca5 | ||
![]() |
f75455bb85 | ||
![]() |
cdbd430f23 | ||
![]() |
03d6dd6ca2 | ||
![]() |
33229c3513 | ||
![]() |
002e55d529 | ||
![]() |
9b518150c3 | ||
![]() |
67948513d5 | ||
![]() |
08fa372c24 | ||
![]() |
3b0ca28c8e | ||
![]() |
86d7b36ba7 | ||
![]() |
43c8328615 | ||
![]() |
c15d411660 | ||
![]() |
b902226c26 | ||
![]() |
c1e8195fd8 | ||
![]() |
d5ff9d4a2f | ||
![]() |
c395849684 | ||
![]() |
7bac15949c | ||
![]() |
7b6eba5319 | ||
![]() |
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 |
@ -1,53 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
executors:
|
||||
aebuilder:
|
||||
docker:
|
||||
- image: aeternity/builder
|
||||
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:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
||||
- dialyzer-cache-v2-{{ .Branch }}-
|
||||
- dialyzer-cache-v2-
|
||||
- run:
|
||||
name: Build
|
||||
command: ./rebar3 compile
|
||||
- run:
|
||||
name: Static Analysis
|
||||
command: ./rebar3 dialyzer
|
||||
- run:
|
||||
name: Eunit
|
||||
command: ./rebar3 eunit
|
||||
- run:
|
||||
name: Common Tests
|
||||
command: ./rebar3 ct
|
||||
- save_cache:
|
||||
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- _build/default/rebar3_20.3.8_plt
|
||||
- store_artifacts:
|
||||
path: _build/test/logs
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_test:
|
||||
jobs:
|
||||
- build
|
||||
- verify_rebar_lock
|
BIN
.docssite/docs/favicon.png
Normal file
BIN
.docssite/docs/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
7
.docssite/hook.py
Normal file
7
.docssite/hook.py
Normal file
@ -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')
|
55
.docssite/mkdocs.yml
Normal file
55
.docssite/mkdocs.yml
Normal file
@ -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
|
8
.docssite/overrides/main.html
Normal file
8
.docssite/overrides/main.html
Normal file
@ -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 %}
|
14
.gitea/workflows/test.yaml
Normal file
14
.gitea/workflows/test.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
name: Sophia Tests
|
||||
run-name: ${{ gitea.actor }} testing Sophia
|
||||
on: [push, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: linux_amd64
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: test
|
||||
run: |
|
||||
. /home/act_runner/.erts/27.2.1/activate
|
||||
./rebar3 eunit
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -18,7 +18,10 @@ _build
|
||||
rebar3.crashdump
|
||||
*.erl~
|
||||
*.aes~
|
||||
aesophia
|
||||
sophia
|
||||
.qcci
|
||||
current_counterexample.eqc
|
||||
test/contracts/test.aes
|
||||
__pycache__
|
||||
.docssite/docs/*.md
|
||||
.vscode
|
||||
|
173
CHANGELOG.md
173
CHANGELOG.md
@ -6,9 +6,168 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- Added a check for number of type variables in a type signature; it is serialized using 8 bits,
|
||||
so the upper limit is 256.
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [9.0.0]
|
||||
### Changed
|
||||
- stdlib dir discovery now works by finding a relative path from the loaded Sophia installation
|
||||
###Removed
|
||||
- Oracles
|
||||
|
||||
## [8.0.1]
|
||||
### Changed
|
||||
- Upgrade aebytecode to v3.4.1 to fix C warnings
|
||||
|
||||
## [8.0.0]
|
||||
### Added
|
||||
- Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`.
|
||||
- `Int.mulmod` - combined builtin operation for multiplication and modulus.
|
||||
- `Crypto.poseidon` - a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field).
|
||||
- `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.).
|
||||
- Raw data pointers added to AENS. In short we have introduced a new namespace
|
||||
`AENSv2`; they contain types similar to the old `AENS`; `AENS.name` and
|
||||
`AENS.pointee`, where the latter now has a constructor `DataPt(bytes())`. All
|
||||
AENS actions have been moved to `AENSv2`, and `AENSv2.lookup` and
|
||||
`AENSv2.update` consume and produce the new types. The old `AENS` namespace
|
||||
only contains the old datatypes, that can be used to interface existing
|
||||
contracts. Standard library `AENSCompat` is added to convert between old and
|
||||
new pointers.
|
||||
- Introduce arbitrary sized binary arrays (type `bytes()`); adding `Bytes.split_any`,
|
||||
`Bytes.to_fixed_size`, `Bytes.to_any_size`, `Bytes.size`, `String.to_bytes`,
|
||||
and `Int.to_bytes`; and adjust `Bytes.concat` to allow both fixed and arbitrary
|
||||
sized byte arrays.
|
||||
- `Chain.network_id` - a function to get hold of the Chain's network id.
|
||||
- Allowing `Bytes.to_any_size` in calldata creation, to enable creation of arguments
|
||||
with arbitray size.
|
||||
- Signature literals `sg_...` - they have type `signature` (which is an alias for `bytes(64)`).
|
||||
- Support for OTP-27 - no changes in behavior.
|
||||
### Changed
|
||||
- `Crypto.verify_sig` is changed to have `msg : bytes()`. I.e. the
|
||||
signed data can be of any length (used to be limited to `bytes(32)`/`hash`).
|
||||
- System aliases are handled explicitly when converting to a Sophia value, this is only
|
||||
observable for `signature` where a value of type `signature` is now represented as a
|
||||
(new) signature literal.
|
||||
- Allow self-qualification, i.e. referencing `X.foo` when in namespace `X`.
|
||||
### Removed
|
||||
- `Bitwise.aes` standard library is removed - the builtin operations are superior.
|
||||
|
||||
## [7.4.1]
|
||||
### Changed
|
||||
- Improve how includes with relative paths are resolved during parsing/compilation. Relative
|
||||
include paths are now always relative to the file containing the `include` statement.
|
||||
### Fixed
|
||||
- Disable unused type warnings for types used inside of records.
|
||||
|
||||
## [7.4.0]
|
||||
### Changed
|
||||
- Names of lifted lambdas now consist of parent function's name and their
|
||||
position in the source code.
|
||||
### Fixed
|
||||
- Lifted lambdas get their names assigned deterministically.
|
||||
|
||||
## [7.3.0]
|
||||
### Fixed
|
||||
- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
|
||||
- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions.
|
||||
|
||||
## [7.2.1]
|
||||
### Fixed
|
||||
- Fixed bugs with the newly added debugging symbols
|
||||
|
||||
## [7.2.0]
|
||||
### Added
|
||||
- Toplevel compile-time constants
|
||||
```
|
||||
namespace N =
|
||||
let nc = 1
|
||||
contract C =
|
||||
let cc = 2
|
||||
```
|
||||
- API functions for encoding/decoding Sophia values to/from FATE.
|
||||
### Removed
|
||||
- Remove the mapping from variables to FATE registers from the compilation output.
|
||||
### 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
|
||||
@ -303,7 +462,19 @@ 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/v6.0.1...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v8.0.1...HEAD
|
||||
[8.0.1]: https://github.com/aeternity/aesophia/compare/v8.0.0...v8.0.1
|
||||
[8.0.0]: https://github.com/aeternity/aesophia/compare/v7.4.1...v8.0.0
|
||||
[7.4.1]: https://github.com/aeternity/aesophia/compare/v7.4.0...v7.4.1
|
||||
[7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0
|
||||
[7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0
|
||||
[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1
|
||||
[7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0
|
||||
[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
|
||||
|
40
CONTRIBUTING.md
Normal file
40
CONTRIBUTING.md
Normal file
@ -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
Emakefile
Normal file
1
Emakefile
Normal file
@ -0,0 +1 @@
|
||||
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
|
3
LICENSE
3
LICENSE
@ -1,6 +1,7 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2017, aeternity developers
|
||||
Copyright (c) 2025, QPQ AG
|
||||
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
|
||||
|
25
README.md
25
README.md
@ -1,17 +1,22 @@
|
||||
# aesophia
|
||||
# The Sophia smart contract language
|
||||
|
||||
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
|
||||
This is the __sophia__ compiler which compiles contracts written in __sophia__ to [FATE](https://git.qpq.swiss/QPQ-AG/protocol/src/branch/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 [Aeternity node](https://github.com/aeternity/aeternity) tests
|
||||
- [The command line compiler](https://git.qpq.swiss/QPQ-AG/sophia_cli)
|
||||
- [Desktop wallet](https://git.qpq.swiss/QPQ-AG/GajuDesk)
|
||||
- In the [Gajumaru core node](https://git.qpq.swiss/QPQ-AG/gajumaru) 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://git.qpq.swiss/QPQ-AG/protocol/src/branch/master/contracts) of the Gajumaru blockchain specification.
|
||||
|
||||
## Versioning
|
||||
|
||||
@ -26,5 +31,5 @@ Versioning should follow the [semantic versioning](https://semver.org/spec/v2.0.
|
||||
|
||||
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)
|
||||
* [so_compiler: the Sophia compiler](docs/so_compiler.md)
|
||||
* [so_aci: the ACI interface](docs/so_aci.md)
|
||||
|
9
docs/index.md
Normal file
9
docs/index.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 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 blockchain specific primitives, constructions and types have been added.
|
||||
|
||||
The file extension used for Sophia source files is ".aes", reflecting Sophia's Aeternity heritage.
|
@ -1,8 +1,8 @@
|
||||
# aeso_aci
|
||||
# so_aci
|
||||
|
||||
### Module
|
||||
|
||||
### aeso_aci
|
||||
### so_aci
|
||||
|
||||
The ACI interface encoder and decoder.
|
||||
|
||||
@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_defs": [
|
||||
"typedefs": [
|
||||
{
|
||||
"name": "answers",
|
||||
"typedef": {
|
||||
@ -123,7 +123,7 @@ be included inside another contract.
|
||||
``` erlang
|
||||
1> {ok,Contract} = file:read_file("aci_test.aes").
|
||||
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
|
||||
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).
|
||||
2> {ok,JsonACI} = so_aci:contract_interface(json, Contract).
|
||||
{ok,[#{contract =>
|
||||
#{functions =>
|
||||
[#{arguments => [],name => <<"init">>,
|
||||
@ -138,13 +138,13 @@ be included inside another contract.
|
||||
state =>
|
||||
#{record =>
|
||||
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"answers">>,
|
||||
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
|
||||
vars => []}]}}]}
|
||||
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
|
||||
ok
|
||||
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).
|
||||
4> {ok,InterfaceStub} = so_aci:render_aci_json(JsonACI).
|
||||
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>}
|
||||
5> file:write_file("aci_test.include", InterfaceStub).
|
||||
ok
|
@ -1,8 +1,8 @@
|
||||
# aeso_compiler
|
||||
# so_compiler
|
||||
|
||||
### Module
|
||||
|
||||
### aeso_compiler
|
||||
### so_compiler
|
||||
|
||||
The Sophia compiler
|
||||
|
||||
@ -49,11 +49,35 @@ 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.
|
||||
|
||||
#### 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 +90,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
|
1196
docs/sophia.md
1196
docs/sophia.md
File diff suppressed because it is too large
Load Diff
67
docs/sophia_examples.md
Normal file
67
docs/sophia_examples.md
Normal file
@ -0,0 +1,67 @@
|
||||
# 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) })
|
||||
```
|
1042
docs/sophia_features.md
Normal file
1042
docs/sophia_features.md
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
292
docs/sophia_syntax.md
Normal file
292
docs/sophia_syntax.md
Normal file
@ -0,0 +1,292 @@
|
||||
# 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
|
||||
- `Signature` base58-encoded 64 byte cryptographic signature with `sg_` 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://git.qpq.swiss/QPQ-AG/protocol/src/branch/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
|
||||
| 'let' Id [':' Type] '=' Expr
|
||||
| (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
|
||||
| Signature // Signature
|
||||
| '???' // 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' | '^'
|
||||
| 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>'
|
||||
UnOp ::= '-' | '!' | 'bnot'
|
||||
```
|
||||
|
||||
## Operators types
|
||||
|
||||
| Operators | Type
|
||||
| --- | ---
|
||||
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
||||
| `!` `&&` `\|\|` | logical operators
|
||||
| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators
|
||||
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||
| `::` `++` | list operators
|
||||
| `\|>` | functional operators
|
||||
|
||||
## Operator precedence
|
||||
|
||||
In order of highest to lowest precedence.
|
||||
|
||||
| Operators | Associativity
|
||||
| --- | ---
|
||||
| `!` `bnot`| right
|
||||
| `^` | left
|
||||
| `*` `/` `mod` | left
|
||||
| `-` (unary) | right
|
||||
| `+` `-` | left
|
||||
| `<<` `>>` | left
|
||||
| `::` `++` | right
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `band` | left
|
||||
| `bxor` | left
|
||||
| `bor` | left
|
||||
| `&&` | right
|
||||
| `\|\|` | right
|
||||
| `\|>` | left
|
27
include/so_parse_lib.hrl
Normal file
27
include/so_parse_lib.hrl
Normal file
@ -0,0 +1,27 @@
|
||||
|
||||
-define(LET_P(X, P, Q), so_parse_lib:bind(P, fun(X) -> Q end)).
|
||||
-define(LAZY_P(P), so_parse_lib:lazy(fun() -> P end)).
|
||||
-define(MEMO_P(P), so_parse_lib:lazy(so_parse_lib:memoised(fun() -> P end))).
|
||||
|
||||
-define(GUARD_P(G, P),
|
||||
case G of
|
||||
true -> P;
|
||||
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, 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(so_parse_lib,
|
||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
|
||||
fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
|
||||
left/2, right/2, optional/1]).
|
||||
|
||||
|
17
priv/stdlib/AENSCompat.aes
Normal file
17
priv/stdlib/AENSCompat.aes
Normal file
@ -0,0 +1,17 @@
|
||||
namespace AENSCompat =
|
||||
// Translate old format to new format - always possible
|
||||
function pointee_to_V2(p : AENS.pointee) : AENSv2.pointee =
|
||||
switch(p)
|
||||
AENS.AccountPt(a) => AENSv2.AccountPt(a)
|
||||
AENS.OraclePt(a) => AENSv2.OraclePt(a)
|
||||
AENS.ContractPt(a) => AENSv2.ContractPt(a)
|
||||
AENS.ChannelPt(a) => AENSv2.ChannelPt(a)
|
||||
|
||||
// Translate new format to old format - option type!
|
||||
function pointee_from_V2(p2 : AENSv2.pointee) : option(AENS.pointee) =
|
||||
switch(p2)
|
||||
AENSv2.AccountPt(a) => Some(AENS.AccountPt(a))
|
||||
AENSv2.OraclePt(a) => Some(AENS.OraclePt(a))
|
||||
AENSv2.ContractPt(a) => Some(AENS.ContractPt(a))
|
||||
AENSv2.ChannelPt(a) => Some(AENS.ChannelPt(a))
|
||||
AENSv2.DataPt(_) => None
|
@ -7,13 +7,13 @@ namespace BLS12_381 =
|
||||
record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp,
|
||||
x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }
|
||||
|
||||
function pairing_check(xs : list(g1), ys : list(g2)) =
|
||||
switch((xs, ys))
|
||||
function pairing_check(us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => true
|
||||
(x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys)
|
||||
|
||||
function pairing_check_(acc : gt, xs : list(g1), ys : list(g2)) =
|
||||
switch((xs, ys))
|
||||
function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) =
|
||||
switch((us, vs))
|
||||
([], []) => gt_is_one(acc)
|
||||
(x :: xs, y :: ys) =>
|
||||
pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -80,6 +80,7 @@ namespace List =
|
||||
* `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0.
|
||||
*/
|
||||
function from_to_step(a : int, b : int, s : int) : list(int) =
|
||||
require(s > 0, "List.from_to_step: non-positive step")
|
||||
from_to_step_(a, b - (b-a) mod s, s, [])
|
||||
private function from_to_step_(a : int, b : int, s : int, acc : list(int)) : list(int) =
|
||||
if(b < a) acc
|
||||
@ -172,7 +173,7 @@ namespace List =
|
||||
if (n == 0) l
|
||||
else switch(l)
|
||||
[] => []
|
||||
h::t => drop_(n-1, t)
|
||||
_::t => drop_(n-1, t)
|
||||
|
||||
/** Get the longest prefix of a list in which every element
|
||||
* matches predicate `p`
|
||||
@ -190,7 +191,7 @@ namespace List =
|
||||
/** Splits list into two lists of elements that respectively
|
||||
* match and don't match predicate `p`
|
||||
*/
|
||||
function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = switch(l)
|
||||
function partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst)
|
||||
[] => ([], [])
|
||||
h::t =>
|
||||
let (l, r) = partition(p, t)
|
||||
@ -281,9 +282,9 @@ namespace List =
|
||||
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)
|
||||
if(lt(h, x)) reverse(x::acc) :: monotonic_subs(lt, h::t)
|
||||
else asc(lt, h, x::acc, t)
|
||||
asc(_, x, acc, []) = [List.reverse(x::acc)]
|
||||
asc(_, x, acc, []) = [reverse(x::acc)]
|
||||
|
||||
/** Merges list of sorted lists
|
||||
*/
|
||||
|
@ -2,8 +2,8 @@ namespace ListInternal =
|
||||
|
||||
// -- Flatmap ----------------------------------------------------------------
|
||||
|
||||
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
|
||||
switch(xs)
|
||||
function flat_map(f : 'a => list('b), lst : list('a)) : list('b) =
|
||||
switch(lst)
|
||||
[] => []
|
||||
x :: xs => f(x) ++ flat_map(f, xs)
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
include "List.aes"
|
||||
|
||||
namespace Option =
|
||||
|
||||
function is_none(o : option('a)) : bool = switch(o)
|
||||
@ -26,6 +24,12 @@ namespace Option =
|
||||
None => abort("Forced None value")
|
||||
Some(x) => x
|
||||
|
||||
/** Assume it is `Some` with custom error message
|
||||
*/
|
||||
function force_msg(o : option('a), err : string) : 'a = switch(o)
|
||||
None => abort(err)
|
||||
Some(x) => x
|
||||
|
||||
function contains(e : 'a, o : option('a)) = o == Some(e)
|
||||
|
||||
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
|
||||
|
51
priv/stdlib/Set.aes
Normal file
51
priv/stdlib/Set.aes
Normal file
@ -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)
|
@ -1,5 +1,8 @@
|
||||
include "List.aes"
|
||||
namespace String =
|
||||
// Gives a bytes() representation of the string
|
||||
function to_bytes(s : string) : bytes() = StringInternal.to_bytes(s)
|
||||
|
||||
// Computes the SHA3/Keccak hash of the string
|
||||
function sha3(s : string) : hash = StringInternal.sha3(s)
|
||||
// Computes the SHA256 hash of the string.
|
||||
@ -53,21 +56,21 @@ namespace String =
|
||||
|
||||
// Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string
|
||||
// into an integer. If the string doesn't contain a valid number `None` is returned.
|
||||
function to_int(s : string) : option(int) =
|
||||
let s = StringInternal.to_list(s)
|
||||
switch(is_prefix(['-'], s))
|
||||
None => to_int_pos(s)
|
||||
function to_int(str : string) : option(int) =
|
||||
let lst = StringInternal.to_list(str)
|
||||
switch(is_prefix(['-'], lst))
|
||||
None => to_int_pos(lst)
|
||||
Some(s) => switch(to_int_pos(s))
|
||||
None => None
|
||||
Some(x) => Some(-x)
|
||||
|
||||
// Private helper functions below
|
||||
private function to_int_pos(s : list(char)) =
|
||||
switch(is_prefix(['0', 'x'], s))
|
||||
private function to_int_pos(chs : list(char)) =
|
||||
switch(is_prefix(['0', 'x'], chs))
|
||||
None =>
|
||||
to_int_(s, ch_to_int_10, 0, 10)
|
||||
Some(s) =>
|
||||
to_int_(s, ch_to_int_16, 0, 16)
|
||||
to_int_(chs, ch_to_int_10, 0, 10)
|
||||
Some(str) =>
|
||||
to_int_(str, ch_to_int_16, 0, 16)
|
||||
|
||||
private function
|
||||
tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))]
|
||||
@ -84,8 +87,8 @@ namespace String =
|
||||
contains_(ix, str, substr) =
|
||||
switch(is_prefix(substr, str))
|
||||
None =>
|
||||
let _ :: str = str
|
||||
contains_(ix + 1, str, substr)
|
||||
let _ :: tailstr = str
|
||||
contains_(ix + 1, tailstr, substr)
|
||||
Some(_) =>
|
||||
Some(ix)
|
||||
|
||||
@ -101,15 +104,15 @@ namespace String =
|
||||
to_int_(i :: is, value, x, b) =
|
||||
switch(value(i))
|
||||
None => None
|
||||
Some(i) => to_int_(is, value, x * b + i, b)
|
||||
Some(n) => to_int_(is, value, x * b + n, b)
|
||||
|
||||
private function ch_to_int_10(c) =
|
||||
let c = Char.to_int(c)
|
||||
private function ch_to_int_10(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
else None
|
||||
|
||||
private function ch_to_int_16(c) =
|
||||
let c = Char.to_int(c)
|
||||
private function ch_to_int_16(ch) =
|
||||
let c = Char.to_int(ch)
|
||||
if(c >= 48 && c =< 57) Some(c - 48)
|
||||
elif(c >= 65 && c =< 70) Some(c - 55)
|
||||
elif(c >= 97 && c =< 102) Some(c - 87)
|
||||
|
13
rebar.config
13
rebar.config
@ -2,11 +2,11 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"05dfd7f"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
{deps, [ {gmbytecode,
|
||||
{git, "https://git.qpq.swiss/QPQ-AG/gmbytecode.git",
|
||||
{ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}}}
|
||||
, {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,10 +15,11 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "6.0.1"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
{relx, [{release, {sophia, "9.0.0"},
|
||||
[sophia, gmbytecode]},
|
||||
|
||||
{dev_mode, true},
|
||||
{include_erts, false},
|
||||
|
||||
{extended_start_script, true}]}.
|
||||
|
||||
|
38
rebar.lock
38
rebar.lock
@ -1,28 +1,30 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"05dfd7ffc7fb1e07ecc0b1e516da571f56d7dc8f"}},
|
||||
{"1.2.0",
|
||||
[{<<"gmbytecode">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/gmbytecode.git",
|
||||
{ref, "97cea33be8f3a35d26055664da7aa59531ff5537"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
|
||||
{<<"gmserialization">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/gmserialization.git",
|
||||
{ref,"ac64e01b0f675c1a34c70a827062f381920742db"}},
|
||||
1},
|
||||
{<<"base58">>,
|
||||
{git,"https://github.com/aeternity/erl-base58.git",
|
||||
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/erl-base58.git",
|
||||
{ref,"e6aa62eeae3d4388311401f06e4b939bf4e94b9c"}},
|
||||
2},
|
||||
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
|
||||
{<<"eblake2">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/eblake2.git",
|
||||
{ref,"b29d585b8760746142014884007eb8441a3b6a14"}},
|
||||
0},
|
||||
{<<"enacl">>,
|
||||
{git,"https://github.com/aeternity/enacl.git",
|
||||
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/enacl.git",
|
||||
{ref,"4eb7ec70084ba7c87b1af8797c4c4e90c84f95a2"}},
|
||||
2},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||
{<<"getopt">>,
|
||||
{git,"https://git.qpq.swiss/QPQ-AG/getopt.git",
|
||||
{ref,"dbab6262a2430809430deda9d8650f58f9d80898"}},
|
||||
1},
|
||||
{<<"jsx">>,
|
||||
{git,"https://github.com/talentdeficit/jsx.git",
|
||||
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
|
||||
0}]}.
|
||||
[
|
||||
{pkg_hash,[
|
||||
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
|
||||
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
|
||||
].
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,684 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler builtin functions for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 20 Dec 2018
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_builtins).
|
||||
|
||||
-export([ builtin_function/1
|
||||
, bytes_to_raw_string/2
|
||||
, check_event_type/1
|
||||
, used_builtins/1 ]).
|
||||
|
||||
-import(aeso_ast_to_icode, [prim_call/5]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
|
||||
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
|
||||
used_builtins([H|T]) ->
|
||||
lists:umerge(used_builtins(H), used_builtins(T));
|
||||
used_builtins(T) when is_tuple(T) ->
|
||||
used_builtins(tuple_to_list(T));
|
||||
used_builtins(M) when is_map(M) ->
|
||||
used_builtins(maps:to_list(M));
|
||||
used_builtins(_) -> [].
|
||||
|
||||
builtin_deps(Builtin) ->
|
||||
lists:usort(builtin_deps1(Builtin)).
|
||||
|
||||
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
|
||||
builtin_deps1(map_member) -> [{map_lookup, word}];
|
||||
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
|
||||
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
|
||||
builtin_deps1(map_from_list) -> [map_put];
|
||||
builtin_deps1(str_equal) -> [str_equal_p];
|
||||
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
|
||||
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
|
||||
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
|
||||
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
|
||||
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
|
||||
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
|
||||
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
|
||||
builtin_deps1(string_reverse) -> [string_reverse_];
|
||||
builtin_deps1(require) -> [abort];
|
||||
builtin_deps1(_) -> [].
|
||||
|
||||
dep_closure(Deps) ->
|
||||
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
|
||||
[] -> Deps;
|
||||
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
|
||||
end.
|
||||
|
||||
%% Helper functions/macros
|
||||
v(X) when is_atom(X) -> v(atom_to_list(X));
|
||||
v(X) when is_list(X) -> #var_ref{name = X}.
|
||||
|
||||
option_none() -> {tuple, [{integer, 0}]}.
|
||||
option_some(X) -> {tuple, [{integer, 1}, X]}.
|
||||
|
||||
-define(HASH_BYTES, 32).
|
||||
|
||||
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
|
||||
-define(I(X), {integer, X}).
|
||||
-define(V(X), v(X)).
|
||||
-define(A(Op), aeb_opcodes:mnemonic(Op)).
|
||||
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
|
||||
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
|
||||
-define(NXT(Ptr), op('+', Ptr, 32)).
|
||||
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
|
||||
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
|
||||
|
||||
-define(EQ(A, B), op('==', A, B)).
|
||||
-define(LT(A, B), op('<', A, B)).
|
||||
-define(GT(A, B), op('>', A, B)).
|
||||
-define(ADD(A, B), op('+', A, B)).
|
||||
-define(SUB(A, B), op('-', A, B)).
|
||||
-define(MUL(A, B), op('*', A, B)).
|
||||
-define(DIV(A, B), op('div', A, B)).
|
||||
-define(MOD(A, B), op('mod', A, B)).
|
||||
-define(EXP(A, B), op('^', A, B)).
|
||||
-define(AND(A, B), op('&&', A, B)).
|
||||
|
||||
%% Bit shift operations takes their arguments backwards!?
|
||||
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
|
||||
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
|
||||
|
||||
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
|
||||
|
||||
%% We generate a lot of B * 8 for integer B from BSL and BSR.
|
||||
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
|
||||
{integer, A * B};
|
||||
simpl(Op) -> Op.
|
||||
|
||||
|
||||
operand(A) when is_atom(A) -> v(A);
|
||||
operand(I) when is_integer(I) -> {integer, I};
|
||||
operand(T) -> T.
|
||||
|
||||
check_event_type(Icode) ->
|
||||
case maps:get(event_type, Icode) of
|
||||
{variant_t, Cons} ->
|
||||
check_event_type(Cons, Icode);
|
||||
_ ->
|
||||
error({event_should_be_variant_type})
|
||||
end.
|
||||
|
||||
check_event_type(Evts, Icode) ->
|
||||
[ check_event_type(Name, Ix, T, Icode)
|
||||
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
|
||||
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
|
||||
|
||||
check_event_type(EvtName, Ix, Type, Icode) ->
|
||||
VMType =
|
||||
try
|
||||
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
||||
catch _:_ ->
|
||||
error({EvtName, could_not_resolve_type, Type})
|
||||
end,
|
||||
case {Ix, VMType, Type} of
|
||||
{indexed, word, _} -> ok;
|
||||
{notindexed, string, _} -> ok;
|
||||
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
|
||||
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
||||
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
|
||||
end.
|
||||
|
||||
bfun(B, {IArgs, IExpr, IRet}) ->
|
||||
{{builtin, B}, [private], IArgs, IExpr, IRet}.
|
||||
|
||||
builtin_function(BF) ->
|
||||
case BF of
|
||||
{event, EventT} -> bfun(BF, builtin_event(EventT));
|
||||
abort -> bfun(BF, builtin_abort());
|
||||
block_hash -> bfun(BF, builtin_block_hash());
|
||||
require -> bfun(BF, builtin_require());
|
||||
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
|
||||
map_put -> bfun(BF, builtin_map_put());
|
||||
map_delete -> bfun(BF, builtin_map_delete());
|
||||
map_size -> bfun(BF, builtin_map_size());
|
||||
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
|
||||
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
|
||||
map_member -> bfun(BF, builtin_map_member());
|
||||
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
|
||||
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
|
||||
map_from_list -> bfun(BF, builtin_map_from_list());
|
||||
list_concat -> bfun(BF, builtin_list_concat());
|
||||
string_length -> bfun(BF, builtin_string_length());
|
||||
string_concat -> bfun(BF, builtin_string_concat());
|
||||
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
|
||||
string_copy -> bfun(BF, builtin_string_copy());
|
||||
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
|
||||
str_equal_p -> bfun(BF, builtin_str_equal_p());
|
||||
str_equal -> bfun(BF, builtin_str_equal());
|
||||
popcount -> bfun(BF, builtin_popcount());
|
||||
int_to_str -> bfun(BF, builtin_int_to_str());
|
||||
addr_to_str -> bfun(BF, builtin_addr_to_str());
|
||||
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
|
||||
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
|
||||
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
|
||||
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
|
||||
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
|
||||
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
|
||||
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
|
||||
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
|
||||
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
|
||||
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
|
||||
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
|
||||
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
|
||||
string_reverse -> bfun(BF, builtin_string_reverse());
|
||||
string_reverse_ -> bfun(BF, builtin_string_reverse_())
|
||||
end.
|
||||
|
||||
%% Event primitive (dependent on Event type)
|
||||
%%
|
||||
%% We need to switch on the event and prepare the correct #event for icode_to_asm
|
||||
%% NOTE: we assume all errors are already checked!
|
||||
builtin_event(EventT) ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
||||
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
||||
Payload = %% Should put data ptr, length on stack.
|
||||
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
||||
([{{id, _, "string"}, V}]) ->
|
||||
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
||||
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
|
||||
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
|
||||
end,
|
||||
Ix =
|
||||
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
|
||||
(_, V) -> V end,
|
||||
Clause =
|
||||
fun(_Tag, {con, _, Con}, IxTypes) ->
|
||||
Types = [ T || {_Ix, T} <- IxTypes ],
|
||||
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
||||
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
|
||||
EvtIndex = {integer, EvtIndexN},
|
||||
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
|
||||
end,
|
||||
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
|
||||
|
||||
{variant_t, Cons} = EventT,
|
||||
Tags = lists:seq(0, length(Cons) - 1),
|
||||
|
||||
{[{"e", event}],
|
||||
{switch, v(e),
|
||||
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|
||||
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Abort primitive.
|
||||
builtin_abort() ->
|
||||
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
||||
{[{"s", string}],
|
||||
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
|
||||
A(?REVERT)]}, %% Stack: 0,Ptr
|
||||
{tuple,[]}}.
|
||||
|
||||
builtin_block_hash() ->
|
||||
{[{"height", word}],
|
||||
?LET(hash, #prim_block_hash{ height = ?V(height)},
|
||||
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
|
||||
aeso_icode:option_typerep(word)}.
|
||||
|
||||
builtin_require() ->
|
||||
{[{"c", word}, {"msg", string}],
|
||||
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
|
||||
{tuple, []}}.
|
||||
|
||||
%% Map primitives
|
||||
builtin_map_lookup(Type) ->
|
||||
Ret = aeso_icode:option_typerep(Type),
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
|
||||
[#var_ref{name = "m"}, #var_ref{name = "k"}],
|
||||
[word, word], Ret),
|
||||
Ret}.
|
||||
|
||||
builtin_map_put() ->
|
||||
%% We don't need the types for put.
|
||||
{[{"m", word}, {"k", word}, {"v", word}],
|
||||
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
|
||||
[v(m), v(k), v(v)], [word, word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_delete() ->
|
||||
{[{"m", word}, {"k", word}],
|
||||
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
|
||||
[v(m), v(k)], [word, word], word),
|
||||
word}.
|
||||
|
||||
builtin_map_size() ->
|
||||
{[{"m", word}],
|
||||
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
|
||||
[v(m)], [word], word),
|
||||
word}.
|
||||
|
||||
%% Map builtins
|
||||
builtin_map_get(Type) ->
|
||||
%% function map_get(m, k) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_lookup_default(Type) ->
|
||||
%% function map_lookup_default(m, k, default) =
|
||||
%% switch(map_lookup(m, k))
|
||||
%% None => default
|
||||
%% Some(v) => v
|
||||
{[{"m", word}, {"k", word}, {"default", Type}],
|
||||
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
|
||||
[{option_none(), v(default)},
|
||||
{option_some(v(v)), v(v)}]},
|
||||
Type}.
|
||||
|
||||
builtin_map_member() ->
|
||||
%% function map_member(m, k) : bool =
|
||||
%% switch(Map.lookup(m, k))
|
||||
%% None => false
|
||||
%% _ => true
|
||||
{[{"m", word}, {"k", word}],
|
||||
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
|
||||
[{option_none(), {integer, 0}},
|
||||
{{var_ref, "_"}, {integer, 1}}]},
|
||||
word}.
|
||||
|
||||
builtin_map_upd(Type) ->
|
||||
%% function map_upd(map, key, fun) =
|
||||
%% map_put(map, key, fun(map_get(map, key)))
|
||||
{[{"map", word}, {"key", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_upd_default(Type) ->
|
||||
%% function map_upd(map, key, val, fun) =
|
||||
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
|
||||
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
|
||||
?call(map_put,
|
||||
[v(map), v(key),
|
||||
#funcall{ function = v(valfun),
|
||||
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
|
||||
word}.
|
||||
|
||||
builtin_map_from_list() ->
|
||||
%% function map_from_list(xs, acc) =
|
||||
%% switch(xs)
|
||||
%% [] => acc
|
||||
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
|
||||
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
|
||||
{switch, v(xs),
|
||||
[{{list, []}, v(acc)},
|
||||
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
|
||||
?call(map_from_list,
|
||||
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
|
||||
word}.
|
||||
|
||||
%% list_concat
|
||||
%%
|
||||
%% Concatenates two lists.
|
||||
builtin_list_concat() ->
|
||||
{[{"l1", {list, word}}, {"l2", {list, word}}],
|
||||
{switch, v(l1),
|
||||
[{{list, []}, v(l2)},
|
||||
{{binop, '::', v(hd), v(tl)},
|
||||
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
|
||||
]
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_string_length() ->
|
||||
%% function length(str) =
|
||||
%% switch(str)
|
||||
%% {n} -> n // (ab)use the representation
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s, ?V(n)),
|
||||
word}.
|
||||
|
||||
%% str_concat - concatenate two strings
|
||||
%%
|
||||
%% Unless the second string is the empty string, a new string is created at the
|
||||
%% top of the Heap and the address to it is returned. The tricky bit is when
|
||||
%% the words from the second string has to be shifted to fit next to the first
|
||||
%% string.
|
||||
builtin_string_concat() ->
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
{ifte, ?EQ(n1, 0),
|
||||
?V(s2), %% First string is empty return second string
|
||||
{ifte, ?EQ(n2, 0),
|
||||
?V(s1), %% Second string is empty return first string
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
|
||||
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
|
||||
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
|
||||
?V(ret) %% Put the actual return value
|
||||
]})}
|
||||
}
|
||||
)),
|
||||
word}.
|
||||
|
||||
builtin_string_concat_inner1() ->
|
||||
%% Copy all whole words from the first string, and set up for word fusion
|
||||
%% Special case when the length of the first string is divisible by 32.
|
||||
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
|
||||
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
|
||||
?LET(nx, ?MOD(n1, 32),
|
||||
{ifte, ?EQ(nx, 0),
|
||||
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
|
||||
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
|
||||
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
|
||||
})),
|
||||
word}.
|
||||
|
||||
builtin_string_copy() ->
|
||||
{[{"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{ifte, ?GT(n, 31),
|
||||
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
|
||||
?V(w)
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_string_shift_copy() ->
|
||||
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
|
||||
?DEREF(w, p,
|
||||
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
{ifte, ?GT(n, ?SUB(32, off)),
|
||||
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
|
||||
{inline_asm, [?A(?MSIZE)]}}]
|
||||
}),
|
||||
word}.
|
||||
|
||||
builtin_str_equal_p() ->
|
||||
%% function str_equal_p(n, p1, p2) =
|
||||
%% if(n =< 0) true
|
||||
%% else
|
||||
%% let w1 = *p1
|
||||
%% let w2 = *p2
|
||||
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
|
||||
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
|
||||
{ifte, ?LT(n, 1),
|
||||
?I(1),
|
||||
?DEREF(w1, p1,
|
||||
?DEREF(w2, p2,
|
||||
?AND(?EQ(w1, w2),
|
||||
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
|
||||
word}.
|
||||
|
||||
builtin_str_equal() ->
|
||||
%% function str_equal(s1, s2) =
|
||||
%% let n1 = length(s1)
|
||||
%% let n2 = length(s2)
|
||||
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
|
||||
{[{"s1", string}, {"s2", string}],
|
||||
?DEREF(n1, s1,
|
||||
?DEREF(n2, s2,
|
||||
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
|
||||
)),
|
||||
word}.
|
||||
|
||||
%% Count the number of 1s in a bit field.
|
||||
builtin_popcount() ->
|
||||
%% function popcount(bits, acc) =
|
||||
%% if (bits == 0) acc
|
||||
%% else popcount(bits bsr 1, acc + bits band 1)
|
||||
{[{"bits", word}, {"acc", word}],
|
||||
{ifte, ?EQ(bits, 0),
|
||||
?V(acc),
|
||||
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
|
||||
}, word}.
|
||||
|
||||
builtin_int_to_str() ->
|
||||
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
|
||||
|
||||
builtin_baseX_tab(_X = 10) ->
|
||||
{[{"ix", word}], ?ADD($0, ix), word};
|
||||
builtin_baseX_tab(_X = 58) ->
|
||||
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
|
||||
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
|
||||
{[{"ix", word}],
|
||||
{ifte, ?LT(ix, 32),
|
||||
?BYTE(ix, Fst32),
|
||||
?BYTE(?SUB(ix, 32), Lst26)
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int(X) ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_pad(X = 10) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?LT(src, 0),
|
||||
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 16) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
word};
|
||||
builtin_baseX_int_pad(X = 58) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
|
||||
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
||||
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode(X) ->
|
||||
{[{"src", word}, {"ix", word}, {"dst", word}],
|
||||
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
|
||||
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
|
||||
word}.
|
||||
|
||||
builtin_baseX_int_encode_(X) ->
|
||||
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
|
||||
{ifte, ?EQ(fac, 0),
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
{ifte, ?EQ(ix, 32),
|
||||
%% We've filled a word, write it and start on new word
|
||||
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
|
||||
?call({baseX_int_encode_, X},
|
||||
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
|
||||
?DIV(fac, X), ?ADD(ix, 1)])}
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_baseX_digits(X) ->
|
||||
{[{"x0", word}, {"dgts", word}],
|
||||
?LET(x1, ?DIV(x0, X),
|
||||
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_int(32) ->
|
||||
{[{"w", word}], ?V(w), word};
|
||||
builtin_bytes_to_int(N) when N < 32 ->
|
||||
{[{"w", word}], ?BSR(w, 32 - N), word};
|
||||
builtin_bytes_to_int(N) when N > 32 ->
|
||||
LastFullWord = N div 32 - 1,
|
||||
Body = case N rem 32 of
|
||||
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
|
||||
R ->
|
||||
?DEREF(hi, ?ADD(b, LastFullWord * 32),
|
||||
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
|
||||
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
|
||||
end,
|
||||
{[{"b", pointer}], Body, word}.
|
||||
|
||||
%% Two versions of this helper function, worker for sections not even 16 bytes long
|
||||
%% and worker_x for the full sized chunks.
|
||||
builtin_bytes_to_str_worker_x() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}],
|
||||
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_worker() ->
|
||||
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
||||
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
|
||||
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(b, ?BYTE(offs, w),
|
||||
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
||||
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
||||
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
|
||||
},
|
||||
word}.
|
||||
|
||||
builtin_bytes_to_str_body(Var, N) when N < 16 ->
|
||||
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
|
||||
builtin_bytes_to_str_body(Var, 16) ->
|
||||
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N < 32 ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
|
||||
builtin_bytes_to_str_body(Var, 32) ->
|
||||
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
||||
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
|
||||
builtin_bytes_to_str_body(Var, N) when N > 32 ->
|
||||
WholeWords = ((N + 31) div 32) - 1,
|
||||
lists:append(
|
||||
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|
||||
|| I <- lists:seq(0, WholeWords - 1) ]) ++
|
||||
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
|
||||
|
||||
builtin_bytes_to_str(N) when N =< 32 ->
|
||||
{[{"w", word}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(w, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string};
|
||||
builtin_bytes_to_str(N) when N > 32 ->
|
||||
{[{"p", pointer}],
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
||||
builtin_bytes_to_str_body(p, N) ++
|
||||
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
||||
string}.
|
||||
|
||||
builtin_string_reverse() ->
|
||||
{[{"s", string}],
|
||||
?DEREF(n, s,
|
||||
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
||||
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
|
||||
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
|
||||
word}.
|
||||
|
||||
builtin_string_reverse_() ->
|
||||
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
|
||||
{ifte, ?LT(i2, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
||||
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
|
||||
?DEREF(w, p1,
|
||||
?LET(b, ?BYTE(?MOD(i2, 32), w),
|
||||
{ifte, ?LT(i1, 0),
|
||||
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
|
||||
?call(string_reverse_,
|
||||
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
|
||||
word}.
|
||||
|
||||
builtin_addr_to_str() ->
|
||||
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
|
||||
|
||||
%% At most one word
|
||||
%% | ..... | ========= | ........ |
|
||||
%% Offs ^ ^- Len -^ TotalLen ^
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
|
||||
%% Bytes are packed into a single word
|
||||
Masked =
|
||||
case Offs of
|
||||
0 -> Bytes;
|
||||
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
|
||||
end,
|
||||
Unpadded =
|
||||
case 32 - (Offs + Len) of
|
||||
0 -> Masked;
|
||||
N -> ?BSR(Masked, N)
|
||||
end,
|
||||
case Len of
|
||||
32 -> Unpadded;
|
||||
_ -> ?BSL(Unpadded, 32 - Len)
|
||||
end;
|
||||
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
|
||||
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
|
||||
%% Might read one word more than necessary.
|
||||
Word = op('!', Offs, Bytes),
|
||||
case Len == 32 of
|
||||
true -> Word;
|
||||
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
|
||||
end.
|
||||
|
||||
builtin_bytes_concat(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Words = fun(N) -> (N + 31) div 32 end,
|
||||
WordsRes = Words(A + B),
|
||||
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
|
||||
(I) when 32 * I < A ->
|
||||
Len = A rem 32,
|
||||
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
|
||||
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
|
||||
?ADD(Hi, ?BSR(Lo, Len));
|
||||
(I) ->
|
||||
Offs = 32 * I - A,
|
||||
Len = min(32, B - Offs),
|
||||
bytes_slice(Offs, Len, B, ?V(b))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> ?V(b);
|
||||
{_, 0} -> ?V(a);
|
||||
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
|
||||
end,
|
||||
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
|
||||
|
||||
builtin_bytes_split(A, B) ->
|
||||
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
||||
MkBytes = fun([W]) -> W;
|
||||
(Ws) -> {tuple, Ws} end,
|
||||
Word = fun(I, Max) ->
|
||||
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
|
||||
end,
|
||||
Body =
|
||||
case {A, B} of
|
||||
{0, _} -> [?I(0), ?V(c)];
|
||||
{_, 0} -> [?V(c), ?I(0)];
|
||||
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
|
||||
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
|
||||
end,
|
||||
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
|
||||
|
||||
bytes_to_raw_string(N, Term) when N =< 32 ->
|
||||
{tuple, [?I(N), Term]};
|
||||
bytes_to_raw_string(N, Term) when N > 32 ->
|
||||
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
|
||||
end,
|
||||
Words = (N + 31) div 32,
|
||||
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
|
||||
|
@ -1,126 +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_main_contract, Decl = {Kind, _, {con, _, C}, _}}) ->
|
||||
Msg = io_lib:format("Expected a main contract as the last declaration instead of the ~p '~s'\n",
|
||||
[Kind, C]),
|
||||
mk_err(pos(Decl), Msg);
|
||||
format({missing_init_function, Con}) ->
|
||||
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
|
||||
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({var_args_not_set, Expr}) ->
|
||||
mk_err( pos(Expr), "Could not deduce type of variable arguments list"
|
||||
, "When compiling " ++ pp_expr(Expr)
|
||||
);
|
||||
format({found_void, Ann}) ->
|
||||
mk_err(pos(Ann), "Found a void-typed value.", "`void` is a restricted, uninhabited type. Did you mean `unit`?");
|
||||
|
||||
format(Err) ->
|
||||
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
|
||||
|
||||
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)).
|
||||
|
@ -1,694 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm.
|
||||
%%% @end
|
||||
%%% Created : 12 Dec 2017
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_compiler).
|
||||
|
||||
-export([ file/1
|
||||
, file/2
|
||||
, from_string/2
|
||||
, check_call/4
|
||||
, create_calldata/3 %% deprecated
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
, numeric_version/0
|
||||
, sophia_type_to_typerep/1
|
||||
, to_sophia_value/4 %% deprecated, need a backend
|
||||
, to_sophia_value/5
|
||||
, decode_calldata/3 %% deprecated
|
||||
, decode_calldata/4
|
||||
, parse/2
|
||||
, add_include_path/2
|
||||
, validate_byte_code/3
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
-include("aeso_utils.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code
|
||||
| pp_ast
|
||||
| pp_types
|
||||
| pp_typed_ast
|
||||
| pp_icode
|
||||
| pp_assembler
|
||||
| pp_bytecode
|
||||
| no_code
|
||||
| keep_included
|
||||
| debug_mode
|
||||
| {backend, aevm | fate}
|
||||
| {include, {file_system, [string()]} |
|
||||
{explicit_files, #{string() => binary()}}}
|
||||
| {src_file, string()}
|
||||
| {aci, aeso_aci:aci_type()}.
|
||||
-type options() :: [option()].
|
||||
|
||||
-export_type([ option/0
|
||||
, options/0
|
||||
]).
|
||||
|
||||
-spec version() -> {ok, binary()} | {error, term()}.
|
||||
version() ->
|
||||
case lists:keyfind(aesophia, 1, application:loaded_applications()) of
|
||||
false ->
|
||||
case application:load(aesophia) of
|
||||
ok ->
|
||||
case application:get_key(aesophia, vsn) of
|
||||
{ok, VsnString} ->
|
||||
{ok, list_to_binary(VsnString)};
|
||||
undefined ->
|
||||
{error, failed_to_load_aesophia}
|
||||
end;
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end;
|
||||
{_App, _Des, VsnString} ->
|
||||
{ok, list_to_binary(VsnString)}
|
||||
end.
|
||||
|
||||
-spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}.
|
||||
numeric_version() ->
|
||||
case version() of
|
||||
{ok, Bin} ->
|
||||
[NoSuf | _] = binary:split(Bin, <<"-">>),
|
||||
Numbers = binary:split(NoSuf, <<".">>, [global]),
|
||||
{ok, [binary_to_integer(Num) || Num <- Numbers]};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
|
||||
file(Filename) ->
|
||||
file(Filename, []).
|
||||
|
||||
-spec file(string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
|
||||
file(File, Options0) ->
|
||||
Options = add_include_path(File, Options0),
|
||||
case read_contract(File) of
|
||||
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
|
||||
{error, Error} ->
|
||||
Msg = lists:flatten([File,": ",file:format_error(Error)]),
|
||||
{error, [aeso_errors:new(file_error, Msg)]}
|
||||
end.
|
||||
|
||||
add_include_path(File, Options) ->
|
||||
case lists:keymember(include, 1, Options) of
|
||||
true -> Options;
|
||||
false ->
|
||||
Dir = filename:dirname(File),
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
[{include, {file_system, [Cwd, Dir]}} | 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) ->
|
||||
try
|
||||
from_string1(Backend, ContractString, Options)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
from_string1(aevm, ContractString, Options) ->
|
||||
#{ icode := Icode
|
||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||
TypeInfo = extract_type_info(Icode),
|
||||
Assembler = assemble(Icode, Options),
|
||||
pp_assembler(aevm, Assembler, Options),
|
||||
ByteCodeList = to_bytecode(Assembler, Options),
|
||||
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
|
||||
pp_bytecode(ByteCode, Options),
|
||||
{ok, Version} = version(),
|
||||
Res = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => TypeInfo,
|
||||
abi_version => aeb_aevm_abi:abi_version(),
|
||||
payable => maps:get(payable, Icode)
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
|
||||
from_string1(fate, ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||
pp_assembler(fate, FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
Res = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => [],
|
||||
fate_code => FateCode,
|
||||
abi_version => aeb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode)
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
||||
|
||||
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
|
||||
case proplists:get_value(aci, Options) of
|
||||
undefined ->
|
||||
Result;
|
||||
Type ->
|
||||
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
|
||||
maps:put(aci, Aci, Result)
|
||||
end.
|
||||
|
||||
-spec string_to_code(string(), options()) -> map().
|
||||
string_to_code(ContractString, Options) ->
|
||||
Ast = parse(ContractString, Options),
|
||||
pp_sophia_code(Ast, Options),
|
||||
pp_ast(Ast, Options),
|
||||
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
pp_typed_ast(UnfoldedTypedAst, Options),
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
Icode = ast_to_icode(UnfoldedTypedAst, Options),
|
||||
pp_icode(Icode, Options),
|
||||
#{ icode => Icode
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast };
|
||||
fate ->
|
||||
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
|
||||
#{ fcode => Fcode
|
||||
, fcode_env => Env
|
||||
, unfolded_typed_ast => UnfoldedTypedAst
|
||||
, folded_typed_ast => FoldedTypedAst
|
||||
, type_env => TypeEnv
|
||||
, ast => Ast }
|
||||
end.
|
||||
|
||||
-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
|
||||
%% function. Returns the name of the called functions, typereps and Erlang
|
||||
%% 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().
|
||||
check_call(Source, "init" = FunName, Args, Options) ->
|
||||
case check_call1(Source, FunName, Args, Options) of
|
||||
Err = {error, _} when Args == [] ->
|
||||
%% Try with default init-function
|
||||
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of
|
||||
{error, _} -> Err; %% The first error is most likely better...
|
||||
Res -> Res
|
||||
end;
|
||||
Res ->
|
||||
Res
|
||||
end;
|
||||
check_call(Source, FunName, Args, Options) ->
|
||||
check_call1(Source, FunName, Args, Options).
|
||||
|
||||
check_call1(ContractString0, FunName, Args, Options) ->
|
||||
try
|
||||
case proplists:get_value(backend, Options, aevm) of
|
||||
aevm ->
|
||||
%% First check the contract without the __call function
|
||||
#{ast := Ast} = string_to_code(ContractString0, Options),
|
||||
ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
|
||||
#{unfolded_typed_ast := TypedAst,
|
||||
icode := Icode} = string_to_code(ContractString, Options),
|
||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||
RetVMType = case RetType of
|
||||
{id, _, "_"} -> any;
|
||||
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
|
||||
end,
|
||||
#{ functions := Funs } = Icode,
|
||||
ArgIcode = get_arg_icode(Funs),
|
||||
ArgTerms = [ icode_to_term(T, Arg) ||
|
||||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
|
||||
RetVMType1 =
|
||||
case FunName of
|
||||
"init" -> {tuple, [typerep, RetVMType]};
|
||||
_ -> RetVMType
|
||||
end,
|
||||
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
|
||||
fate ->
|
||||
%% First check the contract without the __call function
|
||||
#{ fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast } = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
{ok, FunName, CallArgs}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
arguments_of_body(CallName, _FunName, Fcode) ->
|
||||
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
|
||||
{def, _FName, Args} = Body,
|
||||
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
|
||||
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
|
||||
|
||||
first_none_match(_CallName, _Hashes, []) ->
|
||||
error(unable_to_find_unique_call_name);
|
||||
first_none_match(CallName, Hashes, [Char|Chars]) ->
|
||||
case not lists:member(aeb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
|
||||
true ->
|
||||
CallName;
|
||||
false ->
|
||||
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
|
||||
end.
|
||||
|
||||
%% Add the __call function to a contract.
|
||||
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
|
||||
insert_call_function(Ast, Code, Call, FunName, Args) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||
]).
|
||||
|
||||
-spec insert_init_function(string(), options()) -> string().
|
||||
insert_init_function(Code, Options) ->
|
||||
Ast = parse(Code, Options),
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "), "entrypoint init() = ()\n"
|
||||
]).
|
||||
|
||||
last_contract_indent(Decls) ->
|
||||
case lists:last(Decls) of
|
||||
{_, _, _, [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()]}.
|
||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
|
||||
|
||||
-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
|
||||
end;
|
||||
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
Code = string_to_code(ContractString, Options),
|
||||
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
{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
|
||||
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()]}.
|
||||
create_calldata(Code, Fun, Args) ->
|
||||
create_calldata(Code, Fun, Args, [{backend, aevm}]).
|
||||
|
||||
-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
|
||||
end.
|
||||
|
||||
-spec decode_calldata(string(), string(), binary()) ->
|
||||
{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, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
Code = string_to_code(ContractString, Options),
|
||||
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
|
||||
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
||||
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
|
||||
ArgTypes = lists:map(GetType, Args),
|
||||
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]),
|
||||
{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
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
get_arg_icode(Funs) ->
|
||||
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||
[Args] -> Args;
|
||||
[] -> error_missing_call_function()
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, error_missing_call_function/0}).
|
||||
error_missing_call_function() ->
|
||||
Msg = "Internal error: missing '__call'-function",
|
||||
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
|
||||
|
||||
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case [ {lists:last(QFunName), FunType}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
{typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
|
||||
[Call] -> {ok, Call};
|
||||
[] -> error_missing_call_function()
|
||||
end;
|
||||
get_call_type([_ | Contracts]) ->
|
||||
%% The __call should be in the final contract
|
||||
get_call_type(Contracts).
|
||||
|
||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||
get_decode_type(FunName, [{Contract, Ann, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
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,
|
||||
case lists:flatmap(GetType, Defs) of
|
||||
[{Args, Ret}] -> {ok, Args, Ret};
|
||||
[] ->
|
||||
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),
|
||||
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
|
||||
end
|
||||
end;
|
||||
get_decode_type(FunName, [_ | Contracts]) ->
|
||||
%% The __decode should be in the final contract
|
||||
get_decode_type(FunName, Contracts).
|
||||
|
||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||
%% consumed by aeb_heap:to_binary().
|
||||
icode_to_term(word, {integer, N}) -> N;
|
||||
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
|
||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||
Str;
|
||||
icode_to_term({list, T}, {list, Vs}) ->
|
||||
[ icode_to_term(T, V) || V <- Vs ];
|
||||
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
|
||||
list_to_tuple(icodes_to_terms(Ts, Vs));
|
||||
icode_to_term({variant, Cs}, {tuple, [{integer, Tag} | Args]}) ->
|
||||
Ts = lists:nth(Tag + 1, Cs),
|
||||
{variant, Tag, icodes_to_terms(Ts, Args)};
|
||||
icode_to_term(T = {map, KT, VT}, M) ->
|
||||
%% Maps are compiled to builtin and primop calls, so this gets a little hairy
|
||||
case M of
|
||||
{funcall, {var_ref, {builtin, map_put}}, [M1, K, V]} ->
|
||||
Map = icode_to_term(T, M1),
|
||||
Key = icode_to_term(KT, K),
|
||||
Val = icode_to_term(VT, V),
|
||||
Map#{ Key => Val };
|
||||
#prim_call_contract{ address = {integer, 0},
|
||||
arg = {tuple, [{integer, ?PRIM_CALL_MAP_EMPTY}, _, _]} } ->
|
||||
#{};
|
||||
_ -> throw({todo, M})
|
||||
end;
|
||||
icode_to_term(word, {unop, 'bnot', A}) ->
|
||||
bnot icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'bor', A, B}) ->
|
||||
icode_to_term(word, A) bor icode_to_term(word, B);
|
||||
icode_to_term(word, {binop, 'bsl', A, B}) ->
|
||||
icode_to_term(word, B) bsl icode_to_term(word, A);
|
||||
icode_to_term(word, {binop, 'band', A, B}) ->
|
||||
icode_to_term(word, A) band icode_to_term(word, B);
|
||||
icode_to_term(typerep, _) ->
|
||||
throw({todo, typerep});
|
||||
icode_to_term(T, V) ->
|
||||
throw({not_a_value, T, V}).
|
||||
|
||||
icodes_to_terms(Ts, Vs) ->
|
||||
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
|
||||
|
||||
ast_to_icode(TypedAst, Options) ->
|
||||
aeso_ast_to_icode:convert_typed(TypedAst, Options).
|
||||
|
||||
assemble(Icode, Options) ->
|
||||
aeso_icode_to_asm:convert(Icode, Options).
|
||||
|
||||
|
||||
to_bytecode(['COMMENT',_|Rest],_Options) ->
|
||||
to_bytecode(Rest,_Options);
|
||||
to_bytecode([Op|Rest], Options) ->
|
||||
[aeb_opcodes:m_to_op(Op)|to_bytecode(Rest, Options)];
|
||||
to_bytecode([], _) -> [].
|
||||
|
||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
||||
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end,
|
||||
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
|
||||
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|
||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||
not is_tuple(Name),
|
||||
not lists:member(private, Attrs)
|
||||
],
|
||||
lists:sort(TypeInfo).
|
||||
|
||||
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
|
||||
io:format("~s\n", [prettypr:format(aeso_pretty:decls(Code))])
|
||||
end).
|
||||
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
|
||||
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
|
||||
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
|
||||
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
|
||||
|
||||
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
|
||||
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
|
||||
|
||||
pp(Code, Options, Option, PPFun) ->
|
||||
case proplists:lookup(Option, Options) of
|
||||
{Option, true} ->
|
||||
PPFun(Code);
|
||||
none ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% -- Byte code validation ---------------------------------------------------
|
||||
|
||||
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
|
||||
|
||||
-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
|
||||
end.
|
||||
|
||||
compare_fate_code(FCode1, FCode2) ->
|
||||
Funs1 = aeb_fate_code:functions(FCode1),
|
||||
Funs2 = aeb_fate_code:functions(FCode2),
|
||||
Syms1 = aeb_fate_code:symbols(FCode1),
|
||||
Syms2 = aeb_fate_code:symbols(FCode2),
|
||||
FunHashes1 = maps:keys(Funs1),
|
||||
FunHashes2 = maps:keys(Funs2),
|
||||
case FunHashes1 == FunHashes2 of
|
||||
false ->
|
||||
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
|
||||
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
|
||||
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
|
||||
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
|
||||
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
|
||||
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
|
||||
{error, Msg};
|
||||
true ->
|
||||
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|
||||
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
|
||||
maps:to_list(Funs2)) ]) of
|
||||
[] -> ok;
|
||||
Errs -> {error, Errs}
|
||||
end
|
||||
end.
|
||||
|
||||
compare_fate_fun(_Name, Fun, Fun) -> [];
|
||||
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
|
||||
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
|
||||
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
|
||||
[io_lib:format("- The attributes of the function ~s differ:\n"
|
||||
" Byte code: ~s\n"
|
||||
" Source code: ~s\n",
|
||||
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
|
||||
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
|
||||
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
|
||||
[io_lib:format("- The type of the function ~s differs:\n"
|
||||
" Byte code: ~s\n"
|
||||
" Source code: ~s\n",
|
||||
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
|
||||
|
||||
pp_fate_sig({[Arg], Res}) ->
|
||||
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
|
||||
pp_fate_sig({Args, Res}) ->
|
||||
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
|
||||
|
||||
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).
|
||||
|
||||
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
|
||||
parse(Text, Included, Options) ->
|
||||
aeso_parser:string(Text, Included, Options).
|
||||
|
||||
read_contract(Name) ->
|
||||
file:read_file(Name).
|
@ -1,153 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Intermediate Code for Aeterinty Sophia language.
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode).
|
||||
|
||||
-export([new/1,
|
||||
pp/1,
|
||||
set_name/2,
|
||||
set_namespace/2,
|
||||
set_payable/2,
|
||||
enter_namespace/2,
|
||||
get_namespace/1,
|
||||
in_main_contract/1,
|
||||
qualify/2,
|
||||
set_functions/2,
|
||||
map_typerep/2,
|
||||
option_typerep/1,
|
||||
get_constructor_tag/2]).
|
||||
|
||||
-export_type([icode/0]).
|
||||
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
|
||||
|
||||
-type bindings() :: any().
|
||||
-type fun_dec() :: { string()
|
||||
, [modifier()]
|
||||
, arg_list()
|
||||
, expr()
|
||||
, aeb_aevm_data:type()}.
|
||||
|
||||
-type modifier() :: private | stateful.
|
||||
|
||||
-type type_name() :: string() | [string()].
|
||||
|
||||
-type icode() :: #{ contract_name => string()
|
||||
, functions => [fun_dec()]
|
||||
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||
, env => [bindings()]
|
||||
, state_type => aeb_aevm_data:type()
|
||||
, event_type => aeb_aevm_data:type()
|
||||
, types => #{ type_name() => type_def() }
|
||||
, type_vars => #{ string() => aeb_aevm_data:type() }
|
||||
, constructors => #{ [string()] => integer() } %% name to tag
|
||||
, options => [any()]
|
||||
, payable => boolean()
|
||||
}.
|
||||
|
||||
pp(Icode) ->
|
||||
%% TODO: Actually do *Pretty* printing.
|
||||
io:format("~p~n", [Icode]).
|
||||
|
||||
-spec new([any()]) -> icode().
|
||||
new(Options) ->
|
||||
#{ contract_name => ""
|
||||
, functions => []
|
||||
, env => new_env()
|
||||
%% Default to unit type for state and event
|
||||
, state_type => {tuple, []}
|
||||
, event_type => {tuple, []}
|
||||
, types => builtin_types()
|
||||
, type_vars => #{}
|
||||
, constructors => builtin_constructors()
|
||||
, options => Options
|
||||
, payable => false }.
|
||||
|
||||
builtin_types() ->
|
||||
Word = fun([]) -> word end,
|
||||
#{ "bool" => Word
|
||||
, "int" => Word
|
||||
, "char" => Word
|
||||
, "bits" => Word
|
||||
, "string" => fun([]) -> string end
|
||||
, "address" => Word
|
||||
, "hash" => Word
|
||||
, "unit" => fun([]) -> {tuple, []} end
|
||||
, "signature" => fun([]) -> {tuple, [word, word]} end
|
||||
, "oracle" => fun([_, _]) -> word end
|
||||
, "oracle_query" => fun([_, _]) -> word end
|
||||
, "list" => fun([A]) -> {list, A} end
|
||||
, "option" => fun([A]) -> {variant, [[], [A]]} end
|
||||
, "map" => fun([K, V]) -> map_typerep(K, V) end
|
||||
, ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end
|
||||
, ["AENS", "pointee"] => fun([]) -> {variant, [[word], [word], [word]]} end
|
||||
}.
|
||||
|
||||
builtin_constructors() ->
|
||||
#{ ["RelativeTTL"] => 0
|
||||
, ["FixedTTL"] => 1
|
||||
, ["None"] => 0
|
||||
, ["Some"] => 1
|
||||
, ["AccountPointee"] => 0
|
||||
, ["OraclePointee"] => 1
|
||||
, ["ContractPointee"] => 2
|
||||
}.
|
||||
|
||||
map_typerep(K, V) ->
|
||||
{map, K, V}.
|
||||
|
||||
option_typerep(A) ->
|
||||
{variant, [[], [A]]}.
|
||||
|
||||
new_env() ->
|
||||
[].
|
||||
|
||||
-spec set_name(string(), icode()) -> icode().
|
||||
set_name(Name, Icode) ->
|
||||
maps:put(contract_name, Name, Icode).
|
||||
|
||||
-spec set_payable(boolean(), icode()) -> icode().
|
||||
set_payable(Payable, Icode) ->
|
||||
maps:put(payable, Payable, Icode).
|
||||
|
||||
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
|
||||
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
|
||||
|
||||
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
|
||||
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
|
||||
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
|
||||
enter_namespace(NS, Icode) ->
|
||||
Icode#{ namespace => NS }.
|
||||
|
||||
-spec in_main_contract(icode()) -> boolean().
|
||||
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
|
||||
in_main_contract(_Icode) -> false.
|
||||
|
||||
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
get_namespace(Icode) -> maps:get(namespace, Icode, false).
|
||||
|
||||
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
|
||||
qualify(X, Icode) ->
|
||||
case get_namespace(Icode) of
|
||||
false -> X;
|
||||
NS -> aeso_syntax:qualify(NS, X)
|
||||
end.
|
||||
|
||||
-spec set_functions([fun_dec()], icode()) -> icode().
|
||||
set_functions(NewFuns, Icode) ->
|
||||
maps:put(functions, NewFuns, Icode).
|
||||
|
||||
-spec get_constructor_tag([string()], icode()) -> integer().
|
||||
get_constructor_tag(Name, #{constructors := Constructors}) ->
|
||||
case maps:get(Name, Constructors, undefined) of
|
||||
undefined -> error({undefined_constructor, Name});
|
||||
Tag -> Tag
|
||||
end.
|
@ -1,59 +0,0 @@
|
||||
|
||||
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
|
||||
|
||||
-record(arg, {name::string(), type::?Type()}).
|
||||
|
||||
-type expr() :: term().
|
||||
-type arg() :: #arg{name::string(), type::?Type()}.
|
||||
-type arg_list() :: [arg()].
|
||||
|
||||
-record(fun_dec, { name :: string()
|
||||
, args :: arg_list()
|
||||
, body :: expr()}).
|
||||
|
||||
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
|
||||
|
||||
-record(prim_call_contract,
|
||||
{ gas :: expr()
|
||||
, address :: expr()
|
||||
, value :: expr()
|
||||
, arg :: expr()
|
||||
, type_hash:: expr()
|
||||
}).
|
||||
|
||||
-record(prim_balance, { address :: expr() }).
|
||||
-record(prim_block_hash, { height :: expr() }).
|
||||
-record(prim_put, { state :: expr() }).
|
||||
|
||||
-record(integer, {value :: integer()}).
|
||||
|
||||
-record(tuple, {cpts :: [expr()]}).
|
||||
|
||||
-record(list, {elems :: [expr()]}).
|
||||
|
||||
-record(unop, { op :: term()
|
||||
, rand :: expr()}).
|
||||
|
||||
-record(binop, { op :: term()
|
||||
, left :: expr()
|
||||
, right :: expr()}).
|
||||
|
||||
-record(ifte, { decision :: expr()
|
||||
, then :: expr()
|
||||
, else :: expr()}).
|
||||
|
||||
-record(switch, { expr :: expr()
|
||||
, cases :: [{expr(),expr()}]}).
|
||||
|
||||
-record(funcall, { function :: expr()
|
||||
, args :: [expr()]}).
|
||||
|
||||
-record(lambda, { args :: arg_list(),
|
||||
body :: expr()}).
|
||||
|
||||
-record(missing_field, { format :: string()
|
||||
, args :: [term()]}).
|
||||
|
||||
-record(seq, {exprs :: [expr()]}).
|
||||
|
||||
-record(event, {topics :: [expr()], payload :: expr()}).
|
@ -1,983 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Translator from Aesophia Icode to Aevm Assebly
|
||||
%%% @end
|
||||
%%% Created : 21 Dec 2017
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_icode_to_asm).
|
||||
|
||||
-export([convert/2]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
-include("aeso_icode.hrl").
|
||||
|
||||
i(Code) -> aeb_opcodes:mnemonic(Code).
|
||||
|
||||
%% We don't track purity or statefulness in the type checker yet.
|
||||
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
|
||||
|
||||
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
|
||||
|
||||
convert(#{ contract_name := _ContractName
|
||||
, state_type := StateType
|
||||
, functions := Functions
|
||||
},
|
||||
_Options) ->
|
||||
%% Create a function dispatcher
|
||||
DispatchFun = {"%main", [], [{"arg", "_"}],
|
||||
{switch, {var_ref, "arg"},
|
||||
[{{tuple, [fun_hash(Fun),
|
||||
{tuple, make_args(Args)}]},
|
||||
icode_seq([ hack_return_address(Fun, length(Args) + 1) ] ++
|
||||
[ {funcall, {var_ref, FName}, make_args(Args)}]
|
||||
)}
|
||||
|| Fun={FName, _, Args, _,_TypeRep} <- Functions, is_public(Fun) ]},
|
||||
word},
|
||||
NewFunctions = Functions ++ [DispatchFun],
|
||||
%% Create a function environment
|
||||
Funs = [{Name, length(Args), make_ref()}
|
||||
|| {Name, _Attrs, Args, _Body, _Type} <- NewFunctions],
|
||||
%% Create dummy code to call the main function with one argument
|
||||
%% taken from the stack
|
||||
StopLabel = make_ref(),
|
||||
StatefulStopLabel = make_ref(),
|
||||
MainFunction = lookup_fun(Funs, "%main"),
|
||||
|
||||
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
|
||||
|
||||
DispatchCode = [%% push two return addresses to stop, one for stateful
|
||||
%% functions and one for non-stateful functions.
|
||||
push_label(StatefulStopLabel),
|
||||
push_label(StopLabel),
|
||||
%% The calldata is already on the stack when we start. Put
|
||||
%% it on top (also reorders StatefulStop and Stop).
|
||||
swap(2),
|
||||
|
||||
jump(MainFunction),
|
||||
jumpdest(StatefulStopLabel),
|
||||
|
||||
%% We need to encode the state type and put it
|
||||
%% underneath the return value.
|
||||
assemble_expr(Funs, [], nontail, StateTypeValue), %% StateT Ret
|
||||
swap(1), %% Ret StateT
|
||||
|
||||
%% We should also change the state value at address 0 to a
|
||||
%% pointer to the state value (to allow 0 to represent an
|
||||
%% unchanged state).
|
||||
i(?MSIZE), %% Ptr
|
||||
push(0), i(?MLOAD), %% Val Ptr
|
||||
i(?MSIZE), i(?MSTORE), %% Ptr Mem[Ptr] := Val
|
||||
push(0), i(?MSTORE), %% Mem[0] := Ptr
|
||||
|
||||
%% The pointer to the return value is on top of
|
||||
%% the stack, but the return instruction takes two
|
||||
%% stack arguments.
|
||||
push(0),
|
||||
i(?RETURN),
|
||||
jumpdest(StopLabel),
|
||||
%% Set state pointer to 0 to indicate that we didn't change state
|
||||
push(0), dup(1), i(?MSTORE),
|
||||
%% Same as StatefulStopLabel above
|
||||
push(0),
|
||||
i(?RETURN)
|
||||
],
|
||||
%% Code is a deep list of instructions, containing labels and
|
||||
%% references to them. Labels take the form {'JUMPDEST', Ref}, and
|
||||
%% references take the form {push_label, Ref}, which is translated
|
||||
%% into a PUSH instruction.
|
||||
Code = [assemble_function(Funs, Name, Args, Body)
|
||||
|| {Name, _, Args, Body, _Type} <- NewFunctions],
|
||||
resolve_references(
|
||||
[%% i(?COMMENT), "CONTRACT: " ++ ContractName,
|
||||
DispatchCode,
|
||||
Code]).
|
||||
|
||||
%% Generate error on correct format.
|
||||
|
||||
gen_error(Error) ->
|
||||
error({code_errors, [Error]}).
|
||||
|
||||
make_args(Args) ->
|
||||
[{var_ref, [I-1 + $a]} || I <- lists:seq(1, length(Args))].
|
||||
|
||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||
{integer, Hash}.
|
||||
|
||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||
%% one for stateful functions and the bottom one for non-stateful.
|
||||
hack_return_address(Fun, N) ->
|
||||
case is_stateful(Fun) of
|
||||
true -> {inline_asm, [i(?MSIZE)]};
|
||||
false ->
|
||||
{inline_asm, %% X1 .. XN State NoState
|
||||
[ dup(N + 2) %% NoState X1 .. XN State NoState
|
||||
, swap(N + 1) %% State X1 .. XN NoState NoState
|
||||
]} %% Top of the stack will be discarded.
|
||||
end.
|
||||
|
||||
assemble_function(Funs, Name, Args, Body) ->
|
||||
[jumpdest(lookup_fun(Funs, Name)),
|
||||
assemble_expr(Funs, lists:reverse(Args), tail, Body),
|
||||
%% swap return value and first argument
|
||||
pop_args(length(Args)),
|
||||
swap(1),
|
||||
i(?JUMP)].
|
||||
|
||||
%% {seq, Es} - should be "one" operation in terms of stack content
|
||||
%% i.e. after the `seq` there should be one new element on the stack.
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E]}) ->
|
||||
assemble_expr(Funs, Stack, Tail, E);
|
||||
assemble_expr(Funs, Stack, Tail, {seq, [E | Es]}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_expr(Funs, Stack, Tail, {seq, Es})];
|
||||
assemble_expr(_Funs, _Stack, _Tail, {inline_asm, Code}) ->
|
||||
Code; %% Unsafe! Code should take care to respect the stack!
|
||||
assemble_expr(Funs, Stack, _TailPosition, {var_ref, Id}) ->
|
||||
case lists:keymember(Id, 1, Stack) of
|
||||
true ->
|
||||
dup(lookup_var(Id, Stack));
|
||||
false ->
|
||||
%% Build a closure
|
||||
%% When a top-level fun is called directly, we do not
|
||||
%% reach this case.
|
||||
Eta = make_ref(),
|
||||
Continue = make_ref(),
|
||||
[i(?MSIZE),
|
||||
push_label(Eta),
|
||||
dup(2),
|
||||
i(?MSTORE),
|
||||
jump(Continue),
|
||||
%% the code of the closure
|
||||
jumpdest(Eta),
|
||||
%% pop the pointer to the function
|
||||
pop(1),
|
||||
jump(lookup_fun(Funs, Id)),
|
||||
jumpdest(Continue)]
|
||||
end;
|
||||
assemble_expr(_, _, _, {missing_field, Format, Args}) ->
|
||||
io:format(Format, Args),
|
||||
gen_error(missing_field);
|
||||
assemble_expr(_Funs, _Stack, _, {integer, N}) ->
|
||||
push(N);
|
||||
assemble_expr(Funs, Stack, _, {tuple, Cpts}) ->
|
||||
%% We build tuples right-to-left, so that the first write to the
|
||||
%% tuple extends the memory size. Because we use ?MSIZE as the
|
||||
%% heap pointer, we must allocate the tuple AFTER computing the
|
||||
%% first element.
|
||||
%% We store elements into the tuple as soon as possible, to avoid
|
||||
%% keeping them for a long time on the stack.
|
||||
case lists:reverse(Cpts) of
|
||||
[] ->
|
||||
i(?MSIZE);
|
||||
[Last|Rest] ->
|
||||
[assemble_expr(Funs, Stack, nontail, Last),
|
||||
%% allocate the tuple memory
|
||||
i(?MSIZE),
|
||||
%% compute address of last word
|
||||
push(32 * (length(Cpts) - 1)), i(?ADD),
|
||||
%% Stack: <last-value> <pointer>
|
||||
%% Write value to memory (allocates the tuple)
|
||||
swap(1), dup(2), i(?MSTORE),
|
||||
%% Stack: pointer to last word written
|
||||
[[%% Update pointer to next word to be written
|
||||
push(32), swap(1), i(?SUB),
|
||||
%% Compute element
|
||||
assemble_expr(Funs, [pointer|Stack], nontail, A),
|
||||
%% Write element to memory
|
||||
dup(2), i(?MSTORE)]
|
||||
%% And we leave a pointer to the last word written on
|
||||
%% the stack
|
||||
|| A <- Rest]]
|
||||
%% The pointer to the entire tuple is on the stack
|
||||
end;
|
||||
assemble_expr(_Funs, _Stack, _, {list, []}) ->
|
||||
%% Use Erik's value of -1 for []
|
||||
[push(0), i(?NOT)];
|
||||
assemble_expr(Funs, Stack, _, {list, [A|B]}) ->
|
||||
assemble_expr(Funs, Stack, nontail, {tuple, [A, {list, B}]});
|
||||
assemble_expr(Funs, Stack, _, {unop, '!', A}) ->
|
||||
case A of
|
||||
{binop, Logical, _, _} when Logical=='&&'; Logical=='||' ->
|
||||
assemble_expr(Funs, Stack, nontail, {ifte, A, {integer, 0}, {integer, 1}});
|
||||
_ ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
i(?ISZERO)
|
||||
]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, _, {event, Topics, Payload}) ->
|
||||
[assemble_exprs(Funs, Stack, Topics ++ [Payload]),
|
||||
case length(Topics) of
|
||||
0 -> i(?LOG0);
|
||||
1 -> i(?LOG1);
|
||||
2 -> i(?LOG2);
|
||||
3 -> i(?LOG3);
|
||||
4 -> i(?LOG4)
|
||||
end, i(?MSIZE)];
|
||||
assemble_expr(Funs, Stack, _, {unop, Op, A}) ->
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_prefix(Op)];
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '&&', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, B, {integer, 0}});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '||', A, B}) ->
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, A, {integer, 1}, B});
|
||||
assemble_expr(Funs, Stack, Tail, {binop, '::', A, B}) ->
|
||||
%% Take advantage of optimizations in tuple construction.
|
||||
assemble_expr(Funs, Stack, Tail, {tuple, [A, B]});
|
||||
assemble_expr(Funs, Stack, _, {binop, Op, A, B}) ->
|
||||
%% EEVM binary instructions take their first argument from the top
|
||||
%% of the stack, so to get operands on the stack in the right
|
||||
%% order, we evaluate from right to left.
|
||||
[assemble_expr(Funs, Stack, nontail, B),
|
||||
assemble_expr(Funs, [dummy|Stack], nontail, A),
|
||||
assemble_infix(Op)];
|
||||
assemble_expr(Funs, Stack, _, {lambda, Args, Body}) ->
|
||||
Function = make_ref(),
|
||||
FunBody = make_ref(),
|
||||
Continue = make_ref(),
|
||||
NoMatch = make_ref(),
|
||||
FreeVars = free_vars({lambda, Args, Body}),
|
||||
{NewVars, MatchingCode} = assemble_pattern(FunBody, NoMatch, {tuple, [{var_ref, "_"}|FreeVars]}),
|
||||
BodyCode = assemble_expr(Funs, NewVars ++ lists:reverse([ {Arg#arg.name, Arg#arg.type} || Arg <- Args ]), tail, Body),
|
||||
[assemble_expr(Funs, Stack, nontail, {tuple, [{label, Function}|FreeVars]}),
|
||||
jump(Continue), %% will be optimized away
|
||||
jumpdest(Function),
|
||||
%% A pointer to the closure is on the stack
|
||||
MatchingCode,
|
||||
jumpdest(FunBody),
|
||||
BodyCode,
|
||||
pop_args(length(Args)+length(NewVars)),
|
||||
swap(1),
|
||||
i(?JUMP),
|
||||
jumpdest(NoMatch), %% dead code--raise an exception just in case
|
||||
push(0),
|
||||
i(?NOT),
|
||||
i(?MLOAD),
|
||||
i(?STOP),
|
||||
jumpdest(Continue)];
|
||||
assemble_expr(_, _, _, {label, Label}) ->
|
||||
push_label(Label);
|
||||
assemble_expr(Funs, Stack, nontail, {funcall, Fun, Args}) ->
|
||||
Return = make_ref(),
|
||||
%% This is the obvious code:
|
||||
%% [{push_label, Return},
|
||||
%% assemble_exprs(Funs, [return_address|Stack], Args++[Fun]),
|
||||
%% 'JUMP',
|
||||
%% {'JUMPDEST', Return}];
|
||||
%% Its problem is that it stores the return address on the stack
|
||||
%% while the arguments are computed, which is unnecessary. To
|
||||
%% avoid that, we compute the last argument FIRST, and replace it
|
||||
%% with the return address using a SWAP.
|
||||
%%
|
||||
%% assemble_function leaves the code pointer of the function to
|
||||
%% call on top of the stack, and--if the function is not a
|
||||
%% top-level name--a pointer to its tuple of free variables. In
|
||||
%% either case a JUMP is the right way to call it.
|
||||
case Args of
|
||||
[] ->
|
||||
[push_label(Return),
|
||||
assemble_function(Funs, [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)];
|
||||
_ ->
|
||||
{Init, [Last]} = lists:split(length(Args) - 1, Args),
|
||||
[assemble_exprs(Funs, Stack, [Last|Init]),
|
||||
%% Put the return address in the right place, which also
|
||||
%% reorders the args correctly.
|
||||
push_label(Return),
|
||||
swap(length(Args)),
|
||||
assemble_function(Funs, [dummy || _ <- Args] ++ [return_address|Stack], Fun),
|
||||
i(?JUMP),
|
||||
jumpdest(Return)]
|
||||
end;
|
||||
assemble_expr(Funs, Stack, tail, {funcall, Fun, Args}) ->
|
||||
IsTopLevel = is_top_level_fun(Stack, Fun),
|
||||
%% If the fun is not top-level, then it may refer to local
|
||||
%% variables and must be computed before stack shuffling.
|
||||
ArgsAndFun = Args++[Fun || not IsTopLevel],
|
||||
ComputeArgsAndFun = assemble_exprs(Funs, Stack, ArgsAndFun),
|
||||
%% Copy arguments back down the stack to the start of the frame
|
||||
ShuffleSpec = lists:seq(length(ArgsAndFun), 1, -1) ++ [discard || _ <- Stack],
|
||||
Shuffle = shuffle_stack(ShuffleSpec),
|
||||
[ComputeArgsAndFun, Shuffle,
|
||||
if IsTopLevel ->
|
||||
%% still need to compute function
|
||||
assemble_function(Funs, [], Fun);
|
||||
true ->
|
||||
%% need to unpack a closure
|
||||
[dup(1), i(?MLOAD)]
|
||||
end,
|
||||
i(?JUMP)];
|
||||
assemble_expr(Funs, Stack, Tail, {ifte, Decision, Then, Else}) ->
|
||||
%% This compilation scheme introduces a lot of labels and
|
||||
%% jumps. Unnecessary ones are removed later in
|
||||
%% resolve_references.
|
||||
Close = make_ref(),
|
||||
ThenL = make_ref(),
|
||||
ElseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, Decision, ThenL, ElseL),
|
||||
jumpdest(ElseL),
|
||||
assemble_expr(Funs, Stack, Tail, Else),
|
||||
jump(Close),
|
||||
jumpdest(ThenL),
|
||||
assemble_expr(Funs, Stack, Tail, Then),
|
||||
jumpdest(Close)
|
||||
];
|
||||
assemble_expr(Funs, Stack, Tail, {switch, A, Cases}) ->
|
||||
Close = make_ref(),
|
||||
[assemble_expr(Funs, Stack, nontail, A),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases),
|
||||
{'JUMPDEST', Close}];
|
||||
%% State primitives
|
||||
%% (A pointer to) the contract state is stored at address 0.
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_state) ->
|
||||
[push(0), i(?MLOAD)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, State),
|
||||
push(0), i(?MSTORE), %% We need something for the unit value on the stack,
|
||||
i(?MSIZE)]; %% MSIZE is the cheapest instruction.
|
||||
%% Environment primitives
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
||||
[i(?ADDRESS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
|
||||
[i(?CREATOR)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
||||
[i(?ORIGIN)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
||||
[i(?CALLER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_value) ->
|
||||
[i(?CALLVALUE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_price) ->
|
||||
[i(?GASPRICE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_left) ->
|
||||
[i(?GAS)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_coinbase) ->
|
||||
[i(?COINBASE)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_timestamp) ->
|
||||
[i(?TIMESTAMP)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_block_height) ->
|
||||
[i(?NUMBER)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_difficulty) ->
|
||||
[i(?DIFFICULTY)];
|
||||
assemble_expr(_Funs, _Stack, _Tail, prim_gas_limit) ->
|
||||
[i(?GASLIMIT)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_balance{ address = Addr }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Addr),
|
||||
i(?BALANCE)];
|
||||
assemble_expr(Funs, Stack, _Tail, #prim_block_hash{ height = Height }) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Height),
|
||||
i(?BLOCKHASH)];
|
||||
assemble_expr(Funs, Stack, _Tail,
|
||||
#prim_call_contract{ gas = Gas
|
||||
, address = To
|
||||
, value = Value
|
||||
, arg = Arg
|
||||
, type_hash= TypeHash
|
||||
}) ->
|
||||
%% ?CALL takes (from the top)
|
||||
%% Gas, To, Value, Arg, TypeHash, _OOffset,_OSize
|
||||
%% So assemble these in reverse order.
|
||||
[ assemble_exprs(Funs, Stack, [ {integer, 0}, {integer, 0}, TypeHash
|
||||
, Arg, Value, To, Gas ])
|
||||
, i(?CALL)
|
||||
].
|
||||
|
||||
|
||||
assemble_exprs(_Funs, _Stack, []) ->
|
||||
[];
|
||||
assemble_exprs(Funs, Stack, [E|Es]) ->
|
||||
[assemble_expr(Funs, Stack, nontail, E),
|
||||
assemble_exprs(Funs, [dummy|Stack], Es)].
|
||||
|
||||
assemble_decision(Funs, Stack, {binop, '&&', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Label, Else),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {binop, '||', A, B}, Then, Else) ->
|
||||
Label = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, Then, Label),
|
||||
jumpdest(Label),
|
||||
assemble_decision(Funs, Stack, B, Then, Else)];
|
||||
assemble_decision(Funs, Stack, {unop, '!', A}, Then, Else) ->
|
||||
assemble_decision(Funs, Stack, A, Else, Then);
|
||||
assemble_decision(Funs, Stack, {ifte, A, B, C}, Then, Else) ->
|
||||
TrueL = make_ref(),
|
||||
FalseL = make_ref(),
|
||||
[assemble_decision(Funs, Stack, A, TrueL, FalseL),
|
||||
jumpdest(TrueL), assemble_decision(Funs, Stack, B, Then, Else),
|
||||
jumpdest(FalseL), assemble_decision(Funs, Stack, C, Then, Else)];
|
||||
assemble_decision(Funs, Stack, Decision, Then, Else) ->
|
||||
[assemble_expr(Funs, Stack, nontail, Decision),
|
||||
jump_if(Then), jump(Else)].
|
||||
|
||||
%% Entered with value to switch on on top of the stack
|
||||
%% Evaluate selected case, then jump to Close with result on the
|
||||
%% stack.
|
||||
assemble_cases(_Funs, _Stack, _Tail, _Close, []) ->
|
||||
%% No match! What should be do? There's no real way to raise an
|
||||
%% exception, except consuming all the gas.
|
||||
%% There should not be enough gas to do this:
|
||||
[push(1), i(?NOT),
|
||||
i(?MLOAD),
|
||||
%% now stop, so that jump optimizer realizes we will not fall
|
||||
%% through this code.
|
||||
i(?STOP)];
|
||||
assemble_cases(Funs, Stack, Tail, Close, [{Pattern, Body}|Cases]) ->
|
||||
Succeed = make_ref(),
|
||||
Fail = make_ref(),
|
||||
{NewVars, MatchingCode} =
|
||||
assemble_pattern(Succeed, Fail, Pattern),
|
||||
%% In the code that follows, if this is NOT the last case, then we
|
||||
%% save the value being switched on, and discard it on
|
||||
%% success. The code is simpler if this IS the last case.
|
||||
[[dup(1) || Cases /= []], %% save value for next case, if there is one
|
||||
MatchingCode,
|
||||
jumpdest(Succeed),
|
||||
%% Discard saved value, if we saved one
|
||||
[case NewVars of
|
||||
[] ->
|
||||
pop(1);
|
||||
[_] ->
|
||||
%% Special case for peep-hole optimization
|
||||
pop_args(1);
|
||||
_ ->
|
||||
[swap(length(NewVars)), pop(1)]
|
||||
end
|
||||
|| Cases/=[]],
|
||||
assemble_expr(Funs,
|
||||
case Cases of
|
||||
[] -> NewVars;
|
||||
_ -> reorder_vars(NewVars)
|
||||
end
|
||||
++Stack, Tail, Body),
|
||||
%% If the Body makes a tail call, then we will not return
|
||||
%% here--but it doesn't matter, because
|
||||
%% (a) the NewVars will be popped before the tailcall
|
||||
%% (b) the code below will be deleted since it is dead
|
||||
pop_args(length(NewVars)),
|
||||
jump(Close),
|
||||
jumpdest(Fail),
|
||||
assemble_cases(Funs, Stack, Tail, Close, Cases)].
|
||||
|
||||
%% Entered with value to match on top of the stack.
|
||||
%% Generated code removes value, and
|
||||
%% - jumps to Fail if no match, or
|
||||
%% - binds variables, leaves them on the stack, and jumps to Succeed
|
||||
%% Result is a list of variables to add to the stack, and the matching
|
||||
%% code.
|
||||
assemble_pattern(Succeed, Fail, {integer, N}) ->
|
||||
{[], [push(N),
|
||||
i(?EQ),
|
||||
jump_if(Succeed),
|
||||
jump(Fail)]};
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, "_"}) ->
|
||||
{[], [i(?POP), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {missing_field, _, _}) ->
|
||||
%% Missing record fields are quite ok in patterns.
|
||||
assemble_pattern(Succeed, Fail, {var_ref, "_"});
|
||||
assemble_pattern(Succeed, _Fail, {var_ref, Id}) ->
|
||||
{[{Id, "_"}], jump(Succeed)};
|
||||
assemble_pattern(Succeed, _Fail, {tuple, []}) ->
|
||||
{[], [pop(1), jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A]}) ->
|
||||
%% Treat this case specially, because we don't need to save the
|
||||
%% pointer to the tuple.
|
||||
{AVars, ACode} = assemble_pattern(Succeed, Fail, A),
|
||||
{AVars, [i(?MLOAD),
|
||||
ACode]};
|
||||
assemble_pattern(Succeed, Fail, {tuple, [A|B]}) ->
|
||||
%% Entered with the address of the tuple on the top of the
|
||||
%% stack. We will duplicate the address before matching on A.
|
||||
Continue = make_ref(), %% the label for matching B
|
||||
Pop1Fail = make_ref(), %% pop 1 word and goto Fail
|
||||
PopNFail = make_ref(), %% pop length(AVars) words and goto Fail
|
||||
{AVars, ACode} =
|
||||
assemble_pattern(Continue, Pop1Fail, A),
|
||||
{BVars, BCode} =
|
||||
assemble_pattern(Succeed, PopNFail, {tuple, B}),
|
||||
{BVars ++ reorder_vars(AVars),
|
||||
[%% duplicate the pointer so we don't lose it when we match on A
|
||||
dup(1),
|
||||
i(?MLOAD),
|
||||
ACode,
|
||||
jumpdest(Continue),
|
||||
%% Bring the pointer to the top of the stack--this reorders AVars!
|
||||
swap(length(AVars)),
|
||||
push(32),
|
||||
i(?ADD),
|
||||
BCode,
|
||||
case AVars of
|
||||
[] ->
|
||||
[jumpdest(Pop1Fail), pop(1),
|
||||
jumpdest(PopNFail),
|
||||
jump(Fail)];
|
||||
_ ->
|
||||
[{'JUMPDEST', PopNFail}, pop(length(AVars)-1),
|
||||
{'JUMPDEST', Pop1Fail}, pop(1),
|
||||
{push_label, Fail}, 'JUMP']
|
||||
end]};
|
||||
assemble_pattern(Succeed, Fail, {list, []}) ->
|
||||
%% [] is represented by -1.
|
||||
{[], [push(1),
|
||||
i(?ADD),
|
||||
jump_if(Fail),
|
||||
jump(Succeed)]};
|
||||
assemble_pattern(Succeed, Fail, {list, [A|B]}) ->
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, {list, B}});
|
||||
assemble_pattern(Succeed, Fail, {binop, '::', A, B}) ->
|
||||
%% Make sure it's not [], then match as tuple.
|
||||
NotNil = make_ref(),
|
||||
{Vars, Code} = assemble_pattern(Succeed, Fail, {tuple, [A, B]}),
|
||||
{Vars, [dup(1), push(1), i(?ADD), %% Check for [] without consuming the value
|
||||
jump_if(NotNil), %% so it's still there when matching the tuple.
|
||||
pop(1), %% It was [] so discard the saved value.
|
||||
jump(Fail),
|
||||
jumpdest(NotNil),
|
||||
Code]}.
|
||||
|
||||
%% When Vars are on the stack, with a value we want to discard
|
||||
%% below them, then we swap the top variable with that value and pop.
|
||||
%% This reorders the variables on the stack, as follows:
|
||||
reorder_vars([]) ->
|
||||
[];
|
||||
reorder_vars([V|Vs]) ->
|
||||
Vs ++ [V].
|
||||
|
||||
assemble_prefix('sha3') -> [i(?DUP1), i(?MLOAD), %% length, ptr
|
||||
i(?SWAP1), push(32), i(?ADD), %% ptr+32, length
|
||||
i(?SHA3)];
|
||||
assemble_prefix('-') -> [push(0), i(?SUB)];
|
||||
assemble_prefix('bnot') -> i(?NOT).
|
||||
|
||||
assemble_infix('+') -> i(?ADD);
|
||||
assemble_infix('-') -> i(?SUB);
|
||||
assemble_infix('*') -> i(?MUL);
|
||||
assemble_infix('/') -> i(?SDIV);
|
||||
assemble_infix('div') -> i(?DIV);
|
||||
assemble_infix('mod') -> i(?MOD);
|
||||
assemble_infix('^') -> i(?EXP);
|
||||
assemble_infix('bor') -> i(?OR);
|
||||
assemble_infix('band') -> i(?AND);
|
||||
assemble_infix('bxor') -> i(?XOR);
|
||||
assemble_infix('bsl') -> i(?SHL);
|
||||
assemble_infix('bsr') -> i(?SHR);
|
||||
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
|
||||
assemble_infix('>') -> i(?SGT);
|
||||
assemble_infix('==') -> i(?EQ);
|
||||
assemble_infix('<=') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('=<') -> [i(?SGT), i(?ISZERO)];
|
||||
assemble_infix('>=') -> [i(?SLT), i(?ISZERO)];
|
||||
assemble_infix('!=') -> [i(?EQ), i(?ISZERO)];
|
||||
assemble_infix('!') -> [i(?ADD), i(?MLOAD)];
|
||||
assemble_infix('byte') -> i(?BYTE).
|
||||
%% assemble_infix('::') -> [i(?MSIZE), write_word(0), write_word(1)].
|
||||
|
||||
%% a function may either refer to a top-level function, in which case
|
||||
%% we fetch the code label from Funs, or it may be a lambda-expression
|
||||
%% (including a top-level function passed as a parameter). In the
|
||||
%% latter case, the function value is a pointer to a tuple of the code
|
||||
%% pointer and the free variables: we keep the pointer and push the
|
||||
%% code pointer onto the stack. In either case, we are ready to enter
|
||||
%% the function with JUMP.
|
||||
assemble_function(Funs, Stack, Fun) ->
|
||||
case is_top_level_fun(Stack, Fun) of
|
||||
true ->
|
||||
{var_ref, Name} = Fun,
|
||||
{push_label, lookup_fun(Funs, Name)};
|
||||
false ->
|
||||
[assemble_expr(Funs, Stack, nontail, Fun),
|
||||
dup(1),
|
||||
i(?MLOAD)]
|
||||
end.
|
||||
|
||||
free_vars(V={var_ref, _}) ->
|
||||
[V];
|
||||
free_vars({switch, E, Cases}) ->
|
||||
lists:umerge(free_vars(E),
|
||||
lists:umerge([free_vars(Body)--free_vars(Pattern)
|
||||
|| {Pattern, Body} <- Cases]));
|
||||
free_vars({lambda, Args, Body}) ->
|
||||
free_vars(Body) -- [{var_ref, Arg#arg.name} || Arg <- Args];
|
||||
free_vars(T) when is_tuple(T) ->
|
||||
free_vars(tuple_to_list(T));
|
||||
free_vars([H|T]) ->
|
||||
lists:umerge(free_vars(H), free_vars(T));
|
||||
free_vars(_) ->
|
||||
[].
|
||||
|
||||
|
||||
|
||||
%% shuffle_stack reorders the stack, for example before a tailcall. It is called
|
||||
%% with a description of the current stack, and how the final stack
|
||||
%% should appear. The argument is a list containing
|
||||
%% a NUMBER for each element that should be kept, the number being
|
||||
%% the position this element should occupy in the final stack
|
||||
%% discard, for elements that can be discarded.
|
||||
%% The positions start at 1, referring to the variable to be placed at
|
||||
%% the bottom of the stack, and ranging up to the size of the final stack.
|
||||
shuffle_stack([]) ->
|
||||
[];
|
||||
shuffle_stack([discard|Stack]) ->
|
||||
[i(?POP) | shuffle_stack(Stack)];
|
||||
shuffle_stack([N|Stack]) ->
|
||||
case length(Stack) + 1 - N of
|
||||
0 ->
|
||||
%% the job should be finished
|
||||
CorrectStack = lists:seq(N - 1, 1, -1),
|
||||
CorrectStack = Stack,
|
||||
[];
|
||||
MoveBy ->
|
||||
{Pref, [_|Suff]} = lists:split(MoveBy - 1, Stack),
|
||||
[swap(MoveBy) | shuffle_stack([lists:nth(MoveBy, Stack) | Pref ++ [N|Suff]])]
|
||||
end.
|
||||
|
||||
|
||||
|
||||
lookup_fun(Funs, Name) ->
|
||||
case [Ref || {Name1, _, Ref} <- Funs,
|
||||
Name == Name1] of
|
||||
[Ref] -> Ref;
|
||||
[] -> gen_error({undefined_function, Name})
|
||||
end.
|
||||
|
||||
is_top_level_fun(Stack, {var_ref, Id}) ->
|
||||
not lists:keymember(Id, 1, Stack);
|
||||
is_top_level_fun(_, _) ->
|
||||
false.
|
||||
|
||||
lookup_var(Id, Stack) ->
|
||||
lookup_var(1, Id, Stack).
|
||||
|
||||
lookup_var(N, Id, [{Id, _Type}|_]) ->
|
||||
N;
|
||||
lookup_var(N, Id, [_|Stack]) ->
|
||||
lookup_var(N + 1, Id, Stack);
|
||||
lookup_var(_, Id, []) ->
|
||||
gen_error({var_not_in_scope, Id}).
|
||||
|
||||
%% Smart instruction generation
|
||||
|
||||
%% TODO: handle references to the stack beyond depth 16. Perhaps the
|
||||
%% best way is to repush variables that will be needed in
|
||||
%% subexpressions before evaluating he subexpression... i.e. fix the
|
||||
%% problem in assemble_expr, rather than here. A fix here would have
|
||||
%% to save the top elements of the stack in memory, duplicate the
|
||||
%% targetted element, and then repush the values from memory.
|
||||
dup(N) when 1 =< N, N =< 16 ->
|
||||
i(?DUP1 + N - 1).
|
||||
|
||||
push(N) ->
|
||||
Bytes = binary:encode_unsigned(N),
|
||||
true = size(Bytes) =< 32,
|
||||
[i(?PUSH1 + size(Bytes) - 1) |
|
||||
binary_to_list(Bytes)].
|
||||
|
||||
%% Pop N values from UNDER the top element of the stack.
|
||||
%% This is a pseudo-instruction so peephole optimization can
|
||||
%% combine pop_args(M), pop_args(N) to pop_args(M+N)
|
||||
pop_args(0) ->
|
||||
[];
|
||||
pop_args(N) ->
|
||||
{pop_args, N}.
|
||||
%% [swap(N), pop(N)].
|
||||
|
||||
pop(N) ->
|
||||
[i(?POP) || _ <- lists:seq(1, N)].
|
||||
|
||||
swap(0) ->
|
||||
%% Doesn't exist, but is logically a no-op.
|
||||
[];
|
||||
swap(N) when 1 =< N, N =< 16 ->
|
||||
i(?SWAP1 + N - 1).
|
||||
|
||||
jumpdest(Label) -> {i(?JUMPDEST), Label}.
|
||||
push_label(Label) -> {push_label, Label}.
|
||||
|
||||
jump(Label) -> [push_label(Label), i(?JUMP)].
|
||||
jump_if(Label) -> [push_label(Label), i(?JUMPI)].
|
||||
|
||||
%% ICode utilities (TODO: move to separate module)
|
||||
|
||||
icode_noname() -> #var_ref{name = "_"}.
|
||||
|
||||
icode_seq([A]) -> A;
|
||||
icode_seq([A | As]) ->
|
||||
icode_seq(A, icode_seq(As)).
|
||||
|
||||
icode_seq(A, B) ->
|
||||
#switch{ expr = A, cases = [{icode_noname(), B}] }.
|
||||
|
||||
%% Stack: <N elements> ADDR
|
||||
%% Write elements at addresses ADDR, ADDR+32, ADDR+64...
|
||||
%% Stack afterwards: ADDR
|
||||
% write_words(N) ->
|
||||
% [write_word(I) || I <- lists:seq(N-1, 0, -1)].
|
||||
|
||||
%% Unused at the moment. Comment out to please dialyzer.
|
||||
%% write_word(I) ->
|
||||
%% [%% Stack: elements e ADDR
|
||||
%% swap(1),
|
||||
%% dup(2),
|
||||
%% %% Stack: elements ADDR e ADDR
|
||||
%% push(32*I),
|
||||
%% i(?ADD),
|
||||
%% %% Stack: elements ADDR e ADDR+32I
|
||||
%% i(?MSTORE)].
|
||||
|
||||
%% Resolve references, and convert code from deep list to flat list.
|
||||
%% List elements are:
|
||||
%% Opcodes
|
||||
%% Byte values
|
||||
%% {'JUMPDEST', Ref} -- assembles to ?JUMPDEST and sets Ref
|
||||
%% {push_label, Ref} -- assembles to ?PUSHN address bytes
|
||||
|
||||
%% For now, we assemble all code addresses as three bytes.
|
||||
|
||||
resolve_references(Code) ->
|
||||
Peephole = peep_hole(lists:flatten(Code)),
|
||||
%% WARNING: Optimizing jumps reorders the code and deletes
|
||||
%% instructions. When debugging the assemble_ functions, it can be
|
||||
%% useful to replace the next line by:
|
||||
%% Instrs = lists:flatten(Code),
|
||||
%% thus disabling the optimization.
|
||||
OptimizedJumps = optimize_jumps(Peephole),
|
||||
Instrs = lists:reverse(peep_hole_backwards(lists:reverse(OptimizedJumps))),
|
||||
Labels = define_labels(0, Instrs),
|
||||
lists:flatten([use_labels(Labels, I) || I <- Instrs]).
|
||||
|
||||
define_labels(Addr, [{'JUMPDEST', Lab}|More]) ->
|
||||
[{Lab, Addr}|define_labels(Addr + 1, More)];
|
||||
define_labels(Addr, [{push_label, _}|More]) ->
|
||||
define_labels(Addr + 4, More);
|
||||
define_labels(Addr, [{pop_args, N}|More]) ->
|
||||
define_labels(Addr + N + 1, More);
|
||||
define_labels(Addr, [_|More]) ->
|
||||
define_labels(Addr + 1, More);
|
||||
define_labels(_, []) ->
|
||||
[].
|
||||
|
||||
use_labels(_, {'JUMPDEST', _}) ->
|
||||
'JUMPDEST';
|
||||
use_labels(Labels, {push_label, Ref}) ->
|
||||
case proplists:get_value(Ref, Labels) of
|
||||
undefined ->
|
||||
gen_error({undefined_label, Ref});
|
||||
Addr when is_integer(Addr) ->
|
||||
[i(?PUSH3),
|
||||
Addr div 65536, (Addr div 256) rem 256, Addr rem 256]
|
||||
end;
|
||||
use_labels(_, {pop_args, N}) ->
|
||||
[swap(N), pop(N)];
|
||||
use_labels(_, I) ->
|
||||
I.
|
||||
|
||||
%% Peep-hole optimization.
|
||||
%% The compilation of conditionals can introduce jumps depending on
|
||||
%% constants 1 and 0. These are removed by peep-hole optimization.
|
||||
|
||||
peep_hole(['PUSH1', 0, {push_label, _}, 'JUMPI'|More]) ->
|
||||
peep_hole(More);
|
||||
peep_hole(['PUSH1', 1, {push_label, Lab}, 'JUMPI'|More]) ->
|
||||
[{push_label, Lab}, 'JUMP'|peep_hole(More)];
|
||||
peep_hole([{pop_args, M}, {pop_args, N}|More]) when M + N =< 16 ->
|
||||
peep_hole([{pop_args, M + N}|More]);
|
||||
peep_hole([I|More]) ->
|
||||
[I|peep_hole(More)];
|
||||
peep_hole([]) ->
|
||||
[].
|
||||
|
||||
%% Peep-hole optimization on reversed instructions lists.
|
||||
|
||||
peep_hole_backwards(Code) ->
|
||||
NewCode = peep_hole_backwards1(Code),
|
||||
if Code == NewCode -> Code;
|
||||
true -> peep_hole_backwards(NewCode)
|
||||
end.
|
||||
|
||||
peep_hole_backwards1(['ADD', 0, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1(['POP', UnOp|Code]) when UnOp=='MLOAD';UnOp=='ISZERO';UnOp=='NOT' ->
|
||||
peep_hole_backwards1(['POP'|Code]);
|
||||
peep_hole_backwards1(['POP', BinOp|Code]) when
|
||||
%% TODO: more binary operators
|
||||
BinOp=='ADD';BinOp=='SUB';BinOp=='MUL';BinOp=='SDIV' ->
|
||||
peep_hole_backwards1(['POP', 'POP'|Code]);
|
||||
peep_hole_backwards1(['POP', _, 'PUSH1'|Code]) ->
|
||||
peep_hole_backwards1(Code);
|
||||
peep_hole_backwards1([I|Code]) ->
|
||||
[I|peep_hole_backwards1(Code)];
|
||||
peep_hole_backwards1([]) ->
|
||||
[].
|
||||
|
||||
%% Jump optimization:
|
||||
%% Replaces a jump to a jump with a jump to the final destination
|
||||
%% Moves basic blocks to eliminate an unconditional jump to them.
|
||||
|
||||
%% The compilation of conditionals generates a lot of labels and
|
||||
%% jumps, some of them unnecessary. This optimization phase reorders
|
||||
%% code so that as many jumps as possible can be eliminated, and
|
||||
%% replaced by just falling through to the destination label. This
|
||||
%% both optimizes the code generated by conditionals, and converts one
|
||||
%% call of a function into falling through into its code--so it
|
||||
%% reorders code quite aggressively. Function returns are indirect
|
||||
%% jumps, however, and are never optimized away.
|
||||
|
||||
%% IMPORTANT: since execution begins at address zero, then the first
|
||||
%% block of code must never be moved elsewhere. The code below has
|
||||
%% this property, because it processes blocks from left to right, and
|
||||
%% because the first block does not begin with a label, and so can
|
||||
%% never be jumped to--hence no code can be inserted before it.
|
||||
|
||||
%% The optimization works by taking one block of code at a time, and
|
||||
%% then prepending blocks that jump directly to it, and appending
|
||||
%% blocks that it jumps directly to, resulting in a jump-free sequence
|
||||
%% that is as long as possible. To do so, we store blocks in the form
|
||||
%% {OptionalLabel, Body, OptionalJump} which represents the code block
|
||||
%% OptionalLabel++Body++OptionalJump; the optional parts are the empty
|
||||
%% list of instructions if not present. Two blocks can be merged if
|
||||
%% the first ends in an OptionalJump to the OptionalLabel beginning
|
||||
%% the second; the OptionalJump can then be removed (and the
|
||||
%% OptionalLabel if there are no other references to it--this happens
|
||||
%% during dead code elimination.
|
||||
|
||||
%% TODO: the present implementation is QUADRATIC, because we search
|
||||
%% repeatedly for matching blocks to merge with the first one, storing
|
||||
%% the blocks in a list. A near linear time implementation could use
|
||||
%% two ets tables, one keyed on the labels, and the other keyed on the
|
||||
%% final jumps.
|
||||
|
||||
optimize_jumps(Code) ->
|
||||
JJs = jumps_to_jumps(Code),
|
||||
ShortCircuited = [short_circuit_jumps(JJs, Instr) || Instr <- Code],
|
||||
NoDeadCode = eliminate_dead_code(ShortCircuited),
|
||||
MovedCode = merge_blocks(moveable_blocks(NoDeadCode)),
|
||||
%% Moving code may have made some labels superfluous.
|
||||
eliminate_dead_code(MovedCode).
|
||||
|
||||
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {push_label, Target}, 'JUMP'|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps(More)];
|
||||
jumps_to_jumps([{'JUMPDEST', Label}, {'JUMPDEST', Target}|More]) ->
|
||||
[{Label, Target}|jumps_to_jumps([{'JUMPDEST', Target}|More])];
|
||||
jumps_to_jumps([_|More]) ->
|
||||
jumps_to_jumps(More);
|
||||
jumps_to_jumps([]) ->
|
||||
[].
|
||||
|
||||
short_circuit_jumps(JJs, {push_label, Lab}) ->
|
||||
case proplists:get_value(Lab, JJs) of
|
||||
undefined ->
|
||||
{push_label, Lab};
|
||||
Target ->
|
||||
%% I wonder if this will ever loop infinitely?
|
||||
short_circuit_jumps(JJs, {push_label, Target})
|
||||
end;
|
||||
short_circuit_jumps(_JJs, Instr) ->
|
||||
Instr.
|
||||
|
||||
eliminate_dead_code(Code) ->
|
||||
Jumps = lists:usort([Lab || {push_label, Lab} <- Code]),
|
||||
NewCode = live_code(Jumps, Code),
|
||||
if Code==NewCode ->
|
||||
Code;
|
||||
true ->
|
||||
eliminate_dead_code(NewCode)
|
||||
end.
|
||||
|
||||
live_code(Jumps, ['JUMP'|More]) ->
|
||||
['JUMP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, ['STOP'|More]) ->
|
||||
['STOP'|dead_code(Jumps, More)];
|
||||
live_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
live_code(Jumps, More)
|
||||
end;
|
||||
live_code(Jumps, [I|More]) ->
|
||||
[I|live_code(Jumps, More)];
|
||||
live_code(_, []) ->
|
||||
[].
|
||||
|
||||
dead_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
|
||||
case lists:member(Lab, Jumps) of
|
||||
true ->
|
||||
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
|
||||
false ->
|
||||
dead_code(Jumps, More)
|
||||
end;
|
||||
dead_code(Jumps, [_I|More]) ->
|
||||
dead_code(Jumps, More);
|
||||
dead_code(_, []) ->
|
||||
[].
|
||||
|
||||
%% Split the code into "moveable blocks" that control flow only
|
||||
%% reaches via jumps.
|
||||
moveable_blocks([]) ->
|
||||
[];
|
||||
moveable_blocks([I]) ->
|
||||
[[I]];
|
||||
moveable_blocks([Jump|More]) when Jump=='JUMP'; Jump=='STOP' ->
|
||||
[[Jump]|moveable_blocks(More)];
|
||||
moveable_blocks([I|More]) ->
|
||||
[Block|MoreBlocks] = moveable_blocks(More),
|
||||
[[I|Block]|MoreBlocks].
|
||||
|
||||
%% Merge blocks to eliminate jumps where possible.
|
||||
merge_blocks(Blocks) ->
|
||||
BlocksAndTargets = [label_and_jump(B) || B <- Blocks],
|
||||
[I || {Pref, Body, Suff} <- merge_after(BlocksAndTargets),
|
||||
I <- Pref++Body++Suff].
|
||||
|
||||
%% Merge the first block with other blocks that come after it
|
||||
merge_after(All=[{Label, Body, [{push_label, Target}, 'JUMP']}|BlocksAndTargets]) ->
|
||||
case [{B, J} || {[{'JUMPDEST', L}], B, J} <- BlocksAndTargets,
|
||||
L == Target] of
|
||||
[{B, J}|_] ->
|
||||
merge_after([{Label, Body ++ [{'JUMPDEST', Target}] ++ B, J}|
|
||||
lists:delete({[{'JUMPDEST', Target}], B, J},
|
||||
BlocksAndTargets)]);
|
||||
[] ->
|
||||
merge_before(All)
|
||||
end;
|
||||
merge_after(All) ->
|
||||
merge_before(All).
|
||||
|
||||
%% The first block cannot be merged with any blocks that it jumps
|
||||
%% to... but maybe it can be merged with a block that jumps to it!
|
||||
merge_before([Block={[{'JUMPDEST', Label}], Body, Jump}|BlocksAndTargets]) ->
|
||||
case [{L, B, T} || {L, B, [{push_label, T}, 'JUMP']} <- BlocksAndTargets,
|
||||
T == Label] of
|
||||
[{L, B, T}|_] ->
|
||||
merge_before([{L, B ++ [{'JUMPDEST', Label}] ++ Body, Jump}
|
||||
|lists:delete({L, B, [{push_label, T}, 'JUMP']}, BlocksAndTargets)]);
|
||||
_ ->
|
||||
[Block | merge_after(BlocksAndTargets)]
|
||||
end;
|
||||
merge_before([Block|BlocksAndTargets]) ->
|
||||
[Block | merge_after(BlocksAndTargets)];
|
||||
merge_before([]) ->
|
||||
[].
|
||||
|
||||
%% Convert each block to a PREFIX, which is a label or empty, a
|
||||
%% middle, and a SUFFIX which is a JUMP to a label, or empty.
|
||||
label_and_jump(B) ->
|
||||
{Label, B1} = case B of
|
||||
[{'JUMPDEST', L}|More1] ->
|
||||
{[{'JUMPDEST', L}], More1};
|
||||
_ ->
|
||||
{[], B}
|
||||
end,
|
||||
{Target, B2} = case lists:reverse(B1) of
|
||||
['JUMP', {push_label, T}|More2] ->
|
||||
{[{push_label, T}, 'JUMP'], lists:reverse(More2)};
|
||||
_ ->
|
||||
{[], B1}
|
||||
end,
|
||||
{Label, B2, Target}.
|
@ -1,25 +0,0 @@
|
||||
|
||||
-define(LET_P(X, P, Q), aeso_parse_lib:bind(P, fun(X) -> Q end)).
|
||||
-define(LAZY_P(P), aeso_parse_lib:lazy(fun() -> P end)).
|
||||
-define(MEMO_P(P), aeso_parse_lib:lazy(aeso_parse_lib:memoised(fun() -> P end))).
|
||||
|
||||
-define(GUARD_P(G, P),
|
||||
case G of
|
||||
true -> P;
|
||||
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})).
|
||||
|
||||
-import(aeso_parse_lib,
|
||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
|
||||
fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
|
||||
left/2, right/2, optional/1]).
|
||||
|
||||
|
@ -1,137 +0,0 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Decoding aevm and fate data to AST
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_vm_decode).
|
||||
|
||||
-export([ from_aevm/3, 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};
|
||||
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
|
||||
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
|
||||
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
|
||||
from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
|
||||
from_fate({id, _, "int"}, N) when is_integer(N) ->
|
||||
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
|
||||
true -> {int, [], N} end;
|
||||
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
|
||||
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
|
||||
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
|
||||
{list, [], [from_fate(Type, X) || X <- List]};
|
||||
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||
case Val of
|
||||
{variant, [0, 1], 0, {}} -> {con, [], "None"};
|
||||
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
|
||||
end;
|
||||
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
|
||||
{tuple, [], []};
|
||||
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)}
|
||||
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
|
||||
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||
when is_map(Map) ->
|
||||
{map, [], [ {from_fate(KeyType, Key),
|
||||
from_fate(ValType, Val)}
|
||||
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
|
||||
when length(Cons) > Tag ->
|
||||
ConType = lists:nth(Tag + 1, Cons),
|
||||
Arity = lists:nth(Tag + 1, Ar),
|
||||
case tuple_to_list(Args) of
|
||||
ArgList when length(ArgList) == Arity ->
|
||||
from_fate(ConType, ArgList);
|
||||
_ -> throw(cannot_translate_to_sophia)
|
||||
end;
|
||||
from_fate({constr_t, _, Con, []}, []) -> Con;
|
||||
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(_Type, _Data) ->
|
||||
throw(cannot_translate_to_sophia).
|
||||
|
||||
|
||||
make_bits(N) ->
|
||||
Id = fun(F) -> {qid, [], ["Bits", F]} end,
|
||||
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
|
||||
true -> make_bits(Id("set"), Id("none"), 0, N) end.
|
||||
|
||||
make_bits(_Set, Zero, _I, 0) -> Zero;
|
||||
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
|
||||
make_bits(Set, Zero, I + 1, N div 2);
|
||||
make_bits(Set, Zero, I, N) ->
|
||||
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
|
||||
|
@ -1,13 +1,14 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Robert Virding
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% ACI interface
|
||||
%%% @end
|
||||
%%% Created : 12 Jan 2019
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_aci).
|
||||
-module(so_aci).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([ file/2
|
||||
, file/3
|
||||
@ -21,7 +22,7 @@
|
||||
, json_encode_expr/1
|
||||
, json_encode_type/1]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
-include("so_utils.hrl").
|
||||
|
||||
-type aci_type() :: json | string.
|
||||
-type json() :: jsx:json_term().
|
||||
@ -35,7 +36,7 @@ file(Type, File) ->
|
||||
file(Type, File, []).
|
||||
|
||||
file(Type, File, Options0) ->
|
||||
Options = aeso_compiler:add_include_path(File, Options0),
|
||||
Options = so_compiler:add_include_path(File, Options0),
|
||||
case file:read_file(File) of
|
||||
{ok, BinCode} ->
|
||||
do_contract_interface(Type, binary_to_list(BinCode), Options);
|
||||
@ -56,11 +57,11 @@ contract_interface(Type, ContractString, CompilerOpts) ->
|
||||
render_aci_json(Json) ->
|
||||
do_render_aci_json(Json).
|
||||
|
||||
-spec json_encode_expr(aeso_syntax:expr()) -> json().
|
||||
-spec json_encode_expr(so_syntax:expr()) -> json().
|
||||
json_encode_expr(Expr) ->
|
||||
encode_expr(Expr).
|
||||
|
||||
-spec json_encode_type(aeso_syntax:type()) -> json().
|
||||
-spec json_encode_type(so_syntax:type()) -> json().
|
||||
json_encode_type(Type) ->
|
||||
encode_type(Type).
|
||||
|
||||
@ -69,8 +70,8 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
|
||||
do_contract_interface(Type, binary_to_list(Contract), Options);
|
||||
do_contract_interface(Type, ContractString, Options) ->
|
||||
try
|
||||
Ast = aeso_compiler:parse(ContractString, Options),
|
||||
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
Ast = so_compiler:parse(ContractString, Options),
|
||||
{TypedAst, _, _} = so_ast_infer_types:infer(Ast, [dont_unfold | Options]),
|
||||
from_typed_ast(Type, TypedAst)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
@ -83,7 +84,7 @@ from_typed_ast(Type, TypedAst) ->
|
||||
string -> do_render_aci_json(JArray)
|
||||
end.
|
||||
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
|
||||
C0 = #{name => encode_name(Name)},
|
||||
|
||||
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
|
||||
@ -91,7 +92,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(
|
||||
{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;
|
||||
@ -111,7 +112,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _}) when ?IS_CONTRACT_HEAD(
|
||||
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.
|
||||
@ -198,8 +199,9 @@ encode_expr({bytes, _, B}) ->
|
||||
<<N:Digits/unit:8>> = B,
|
||||
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
|
||||
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
|
||||
Lit == contract_pubkey; Lit == account_pubkey ->
|
||||
aeser_api_encoder:encode(Lit, L);
|
||||
Lit == contract_pubkey; Lit == account_pubkey;
|
||||
Lit == signature ->
|
||||
gmser_api_encoder:encode(Lit, L);
|
||||
encode_expr({app, _, {'-', _}, [{int, _, N}]}) ->
|
||||
encode_expr({int, [], -N});
|
||||
encode_expr({app, _, F, As}) ->
|
||||
@ -234,7 +236,7 @@ do_render_aci_json(Json) ->
|
||||
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) ] ++
|
||||
@ -246,7 +248,7 @@ decode_contract(#{contract := #{name := Name,
|
||||
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(_) -> [].
|
||||
@ -254,8 +256,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) ->
|
||||
@ -282,6 +284,8 @@ decode_type(#{list := [Et]}) ->
|
||||
decode_type(#{map := Ets}) ->
|
||||
Ts = decode_types(Ets),
|
||||
["map",$(,lists:join(",", Ts),$)];
|
||||
decode_type(#{bytes := any}) ->
|
||||
["bytes()"];
|
||||
decode_type(#{bytes := Len}) ->
|
||||
["bytes(", integer_to_list(Len), ")"];
|
||||
decode_type(#{variant := Ets}) ->
|
||||
@ -336,12 +340,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 ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_funcs({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_fun(D)].
|
||||
|
||||
contract_types({C, _, _, Decls}) when ?IS_CONTRACT_HEAD(C); C == namespace ->
|
||||
contract_types({namespace, _, _, Decls}) ->
|
||||
[ D || D <- Decls, is_type(D) ];
|
||||
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
|
||||
[ D || D <- Decls, is_type(D) ].
|
||||
|
||||
is_fun({letfun, _, _, _, _, _}) -> true;
|
||||
@ -353,14 +362,14 @@ is_type(_) -> false.
|
||||
|
||||
sort_decls(Ds) ->
|
||||
Sort = fun (D1, D2) ->
|
||||
aeso_syntax:get_ann(line, D1, 0) =<
|
||||
aeso_syntax:get_ann(line, D2, 0)
|
||||
so_syntax:get_ann(line, D1, 0) =<
|
||||
so_syntax:get_ann(line, D2, 0)
|
||||
end,
|
||||
lists:sort(Sort, Ds).
|
||||
|
||||
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false).
|
||||
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
|
||||
is_payable(Node) -> aeso_syntax:get_ann(payable, Node, false).
|
||||
is_entrypoint(Node) -> so_syntax:get_ann(entrypoint, Node, false).
|
||||
is_stateful(Node) -> so_syntax:get_ann(stateful, Node, false).
|
||||
is_payable(Node) -> so_syntax:get_ann(payable, Node, false).
|
||||
|
||||
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name.
|
||||
|
@ -1,4 +1,5 @@
|
||||
-module(aeso_ast).
|
||||
-module(so_ast).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([int/2,
|
||||
line/1,
|
||||
@ -17,11 +18,11 @@ line({symbol, Line, _}) -> Line.
|
||||
symbol_name({symbol, _, Name}) -> Name.
|
||||
|
||||
pp(Ast) ->
|
||||
String = prettypr:format(aeso_pretty:decls(Ast, [])),
|
||||
String = prettypr:format(so_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])),
|
||||
String = prettypr:format(so_pretty:decls(TypedAst, [show_generated])),
|
||||
io:format("Type ast:\n~s\n",[String]).
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
526
src/so_compiler.erl
Normal file
526
src/so_compiler.erl
Normal file
@ -0,0 +1,526 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Happi (Erik Stenman)
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Compiler from Sophia language to FATE.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(so_compiler).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([ file/1
|
||||
, file/2
|
||||
, from_string/2
|
||||
, check_call/4
|
||||
, decode_value/4
|
||||
, encode_value/4
|
||||
, create_calldata/3
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
, numeric_version/0
|
||||
, to_sophia_value/4
|
||||
, to_sophia_value/5
|
||||
, decode_calldata/3
|
||||
, decode_calldata/4
|
||||
, parse/2
|
||||
, add_include_path/2
|
||||
, validate_byte_code/3
|
||||
]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_opcodes.hrl").
|
||||
-include("so_utils.hrl").
|
||||
|
||||
|
||||
-type option() :: pp_sophia_code
|
||||
| pp_ast
|
||||
| pp_types
|
||||
| pp_typed_ast
|
||||
| pp_assembler
|
||||
| no_code
|
||||
| keep_included
|
||||
| debug_mode
|
||||
| {include, {file_system, [string()]} |
|
||||
{explicit_files, #{string() => binary()}}}
|
||||
| {src_file, string()}
|
||||
| {src_dir, string()}
|
||||
| {aci, so_aci:aci_type()}.
|
||||
-type options() :: [option()].
|
||||
|
||||
-export_type([ option/0
|
||||
, options/0
|
||||
]).
|
||||
|
||||
-spec version() -> {ok, binary()} | {error, term()}.
|
||||
version() ->
|
||||
case lists:keyfind(sophia, 1, application:loaded_applications()) of
|
||||
false ->
|
||||
case application:load(sophia) of
|
||||
ok ->
|
||||
case application:get_key(sophia, vsn) of
|
||||
{ok, VsnString} ->
|
||||
{ok, list_to_binary(VsnString)};
|
||||
undefined ->
|
||||
{error, failed_to_load_sophia}
|
||||
end;
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end;
|
||||
{_App, _Des, VsnString} ->
|
||||
{ok, list_to_binary(VsnString)}
|
||||
end.
|
||||
|
||||
-spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}.
|
||||
numeric_version() ->
|
||||
case version() of
|
||||
{ok, Bin} ->
|
||||
[NoSuf | _] = binary:split(Bin, <<"-">>),
|
||||
Numbers = binary:split(NoSuf, <<".">>, [global]),
|
||||
{ok, [binary_to_integer(Num) || Num <- Numbers]};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec file(string()) -> {ok, map()} | {error, [so_errors:error()]}.
|
||||
file(Filename) ->
|
||||
file(Filename, []).
|
||||
|
||||
-spec file(string(), options()) -> {ok, map()} | {error, [so_errors:error()]}.
|
||||
file(File, Options0) ->
|
||||
Options = add_include_path(File, Options0),
|
||||
case read_contract(File) of
|
||||
{ok, Bin} ->
|
||||
SrcDir = so_utils:canonical_dir(filename:dirname(File)),
|
||||
from_string(Bin, [{src_file, File}, {src_dir, SrcDir} | Options]);
|
||||
{error, Error} ->
|
||||
Msg = lists:flatten([File,": ",file:format_error(Error)]),
|
||||
{error, [so_errors:new(file_error, Msg)]}
|
||||
end.
|
||||
|
||||
add_include_path(File, Options) ->
|
||||
case lists:keymember(include, 1, Options) of
|
||||
true -> Options;
|
||||
false ->
|
||||
Dir = filename:dirname(File),
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
[{include, {file_system, [Cwd, so_utils:canonical_dir(Dir)]}} | Options]
|
||||
end.
|
||||
|
||||
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [so_errors:error()]}.
|
||||
from_string(ContractBin, Options) when is_binary(ContractBin) ->
|
||||
from_string(binary_to_list(ContractBin), Options);
|
||||
from_string(ContractString, Options) ->
|
||||
try
|
||||
from_string1(ContractString, Options)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
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 = so_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
|
||||
pp_assembler(FateCode, Options),
|
||||
ByteCode = gmb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
Res = #{byte_code => ByteCode,
|
||||
compiler_version => Version,
|
||||
contract_source => ContractString,
|
||||
type_info => [],
|
||||
fate_code => FateCode,
|
||||
abi_version => gmb_fate_abi:abi_version(),
|
||||
payable => maps:get(payable, FCode),
|
||||
warnings => Warnings
|
||||
},
|
||||
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
|
||||
|
||||
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
|
||||
case proplists:get_value(aci, Options) of
|
||||
undefined ->
|
||||
Result;
|
||||
Type ->
|
||||
{ok, Aci} = so_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, FoldedTypedAst, UnfoldedTypedAst, Warnings} = so_ast_infer_types:infer(Ast, [return_env | Options]),
|
||||
pp_typed_ast(UnfoldedTypedAst, Options),
|
||||
{Env, Fcode} = so_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").
|
||||
|
||||
%% Takes a string containing a contract with a declaration/prototype of a
|
||||
%% function (foo, say) and adds function __call() = foo(args) calling this
|
||||
%% function. Returns the name of the called functions, typereps and Erlang
|
||||
%% 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(), [term()]}
|
||||
| {error, [so_errors:error()]}.
|
||||
check_call(Source, "init" = FunName, Args, Options) ->
|
||||
case check_call1(Source, FunName, Args, Options) of
|
||||
Err = {error, _} when Args == [] ->
|
||||
%% Try with default init-function
|
||||
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of
|
||||
{error, _} -> Err; %% The first error is most likely better...
|
||||
Res -> Res
|
||||
end;
|
||||
Res ->
|
||||
Res
|
||||
end;
|
||||
check_call(Source, FunName, Args, Options) ->
|
||||
check_call1(Source, FunName, Args, Options).
|
||||
|
||||
check_call1(ContractString0, FunName, Args, Options) ->
|
||||
case add_extra_call(ContractString0, {call, FunName, Args}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
{def, _, _, FcodeArgs} = get_call_body(CallName, Code),
|
||||
{ok, FunName, [ so_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]};
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
add_extra_call(Contract0, Call, Options) ->
|
||||
try
|
||||
%% First check the contract without the __call function
|
||||
#{fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast} = string_to_code(Contract0, Options),
|
||||
FateCode = so_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(gmb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
Contract = insert_call_function(Ast, Contract0, CallName, Call),
|
||||
{ok, CallName, string_to_code(Contract, Options)}
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
get_call_body(CallName, #{fcode := Fcode}) ->
|
||||
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
|
||||
Body.
|
||||
|
||||
encode_value(Contract0, Type, Value, Options) ->
|
||||
case add_extra_call(Contract0, {value, Type, Value}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
Body = get_call_body(CallName, Code),
|
||||
{ok, gmb_fate_encoding:serialize(so_fcode_to_fate:term_to_fate(Body))};
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
decode_value(Contract0, Type, FateValue, Options) ->
|
||||
case add_extra_call(Contract0, {type, Type}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
#{ folded_typed_ast := TypedAst
|
||||
, type_env := TypeEnv} = Code,
|
||||
{ok, _, Type0} = get_decode_type(CallName, TypedAst),
|
||||
Type1 = so_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
|
||||
[ unfold_record_types
|
||||
, unfold_variant_types
|
||||
, not_unfold_system_alias_types ]),
|
||||
fate_data_to_sophia_value(Type0, Type1, FateValue);
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
first_none_match(_CallName, _Hashes, []) ->
|
||||
error(unable_to_find_unique_call_name);
|
||||
first_none_match(CallName, Hashes, [Char|Chars]) ->
|
||||
case not lists:member(gmb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
|
||||
true ->
|
||||
CallName;
|
||||
false ->
|
||||
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
|
||||
end.
|
||||
|
||||
%% Add the __call function to a contract.
|
||||
-spec insert_call_function(so_syntax:ast(), string(), string(),
|
||||
{call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string().
|
||||
insert_call_function(Ast, Code, Call, {call, FunName, Args}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||
]);
|
||||
insert_call_function(Ast, Code, Call, {value, Type, Value}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"entrypoint ", Call, "() : ", Type, " = ", Value, "\n"
|
||||
]);
|
||||
insert_call_function(Ast, Code, Call, {type, Type}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"entrypoint ", Call, "(val : ", Type, ") : ", Type, " = val\n"
|
||||
]).
|
||||
|
||||
-spec insert_init_function(string(), options()) -> string().
|
||||
insert_init_function(Code, Options) ->
|
||||
Ast = parse(Code, Options),
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "), "entrypoint init() = ()\n"
|
||||
]).
|
||||
|
||||
last_contract_indent(Decls) ->
|
||||
case lists:last(Decls) of
|
||||
{_, _, _, _, [Decl | _]} -> so_syntax:get_ann(col, Decl, 1) - 1;
|
||||
_ -> 0
|
||||
end.
|
||||
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
|
||||
{ok, so_syntax:expr()} | {error, [so_errors:error()]}.
|
||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
||||
{ok, so_syntax:expr()} | {error, [so_errors:error()]}.
|
||||
to_sophia_value(_, _, error, Err, _Options) ->
|
||||
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||
try so_vm_decode:from_fate({id, [], "string"}, gmb_fate_encoding:deserialize(Data)) of
|
||||
Err ->
|
||||
{ok, {app, [], {id, [], "abort"}, [Err]}}
|
||||
catch _:_ ->
|
||||
Msg = "Could not deserialize the revert message",
|
||||
{error, [so_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),
|
||||
#{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||
Type = so_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
|
||||
[ unfold_record_types
|
||||
, unfold_variant_types
|
||||
, not_unfold_system_alias_types]),
|
||||
|
||||
fate_data_to_sophia_value(Type0, Type, Data)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
fate_data_to_sophia_value(Type, UnfoldedType, FateData) ->
|
||||
try
|
||||
{ok, so_vm_decode:from_fate(UnfoldedType, gmb_fate_encoding:deserialize(FateData))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(so_pretty:type(Type)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
|
||||
[gmb_fate_encoding:deserialize(FateData), Type1]),
|
||||
{error, [so_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(so_pretty:type(Type)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
|
||||
{error, [so_errors:new(data_error, Msg)]}
|
||||
end.
|
||||
|
||||
-spec create_calldata(string(), string(), [string()]) ->
|
||||
{ok, binary()} | {error, [so_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args) ->
|
||||
create_calldata(Code, Fun, Args, []).
|
||||
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
|
||||
{ok, binary()} | {error, [so_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
case check_call(Code, Fun, Args, Options) of
|
||||
{ok, FunName, FateArgs} ->
|
||||
gmb_fate_abi:create_calldata(FunName, FateArgs);
|
||||
{error, _} = Err -> Err
|
||||
end.
|
||||
|
||||
-spec decode_calldata(string(), string(), binary()) ->
|
||||
{ok, [so_syntax:type()], [so_syntax:expr()]}
|
||||
| {error, [so_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata) ->
|
||||
decode_calldata(ContractString, FunName, Calldata, []).
|
||||
-spec decode_calldata(string(), string(), binary(), options()) ->
|
||||
{ok, [so_syntax:type()], [so_syntax:expr()]}
|
||||
| {error, [so_errors:error()]}.
|
||||
decode_calldata(ContractString, FunName, Calldata, Options0) ->
|
||||
Options = [no_code | Options0],
|
||||
try
|
||||
Code = string_to_code(ContractString, Options),
|
||||
#{ folded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||
|
||||
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
||||
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
|
||||
ArgTypes = lists:map(GetType, Args),
|
||||
Type0 = {tuple_t, [], ArgTypes},
|
||||
%% user defined data types such as variants needed to match against
|
||||
Type = so_ast_infer_types:unfold_types_in_type(TypeEnv, Type0,
|
||||
[ unfold_record_types
|
||||
, unfold_variant_types
|
||||
, not_unfold_system_alias_types]),
|
||||
case gmb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||
{ok, FateArgs} ->
|
||||
try
|
||||
{tuple_t, [], ArgTypes1} = Type,
|
||||
AstArgs = [ so_vm_decode:from_fate(ArgType, FateArg)
|
||||
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||
{ok, ArgTypes, AstArgs}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type0Str = prettypr:format(so_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s",
|
||||
[FateArgs, Type0Str]),
|
||||
{error, [so_errors:new(data_error, Msg)]}
|
||||
end;
|
||||
{error, _} ->
|
||||
Msg = io_lib:format("Failed to decode calldata binary", []),
|
||||
{error, [so_errors:new(data_error, Msg)]}
|
||||
end
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, get_decode_type/2}).
|
||||
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,
|
||||
case lists:flatmap(GetType, Defs) of
|
||||
[{Args, Ret}] -> {ok, Args, Ret};
|
||||
[] ->
|
||||
case FunName of
|
||||
"init" -> {ok, [], {tuple_t, [], []}};
|
||||
_ ->
|
||||
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
|
||||
Pos = so_errors:pos(Ann),
|
||||
so_errors:throw(so_errors:new(data_error, Pos, Msg))
|
||||
end
|
||||
end;
|
||||
get_decode_type(FunName, [_ | Contracts]) ->
|
||||
%% The __decode should be in the final contract
|
||||
get_decode_type(FunName, Contracts).
|
||||
|
||||
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
|
||||
io:format("~s\n", [prettypr:format(so_pretty:decls(Code))])
|
||||
end).
|
||||
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun so_ast:pp/1).
|
||||
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun so_ast:pp_typed/1).
|
||||
|
||||
pp_assembler(C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [gmb_fate_asm:pp(Asm)]) end).
|
||||
|
||||
pp(Code, Options, Option, PPFun) ->
|
||||
case proplists:lookup(Option, Options) of
|
||||
{Option1, true} when Option1 =:= Option ->
|
||||
PPFun(Code);
|
||||
none ->
|
||||
ok
|
||||
end.
|
||||
|
||||
%% -- Byte code validation ---------------------------------------------------
|
||||
|
||||
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
|
||||
|
||||
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [so_errors:error()]}.
|
||||
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
|
||||
Fail = fun(Err) -> {error, [so_errors:new(data_error, Err)]} end,
|
||||
try
|
||||
FCode1 = ?protect(deserialize, gmb_fate_code:strip_init_function(gmb_fate_code:deserialize(ByteCode))),
|
||||
{FCode2, SrcPayable} =
|
||||
?protect(compile,
|
||||
begin
|
||||
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||
from_string1(Source, Options),
|
||||
FCode = gmb_fate_code:deserialize(SrcByteCode),
|
||||
{gmb_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) ->
|
||||
Funs1 = gmb_fate_code:functions(FCode1),
|
||||
Funs2 = gmb_fate_code:functions(FCode2),
|
||||
Syms1 = gmb_fate_code:symbols(FCode1),
|
||||
Syms2 = gmb_fate_code:symbols(FCode2),
|
||||
FunHashes1 = maps:keys(Funs1),
|
||||
FunHashes2 = maps:keys(Funs2),
|
||||
case FunHashes1 == FunHashes2 of
|
||||
false ->
|
||||
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
|
||||
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
|
||||
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
|
||||
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
|
||||
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
|
||||
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
|
||||
{error, Msg};
|
||||
true ->
|
||||
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|
||||
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
|
||||
maps:to_list(Funs2)) ]) of
|
||||
[] -> ok;
|
||||
Errs -> {error, Errs}
|
||||
end
|
||||
end.
|
||||
|
||||
compare_fate_fun(_Name, Fun, Fun) -> [];
|
||||
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
|
||||
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
|
||||
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
|
||||
[io_lib:format("- The attributes of the function ~s differ:\n"
|
||||
" Byte code: ~s\n"
|
||||
" Source code: ~s\n",
|
||||
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
|
||||
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
|
||||
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
|
||||
[io_lib:format("- The type of the function ~s differs:\n"
|
||||
" Byte code: ~s\n"
|
||||
" Source code: ~s\n",
|
||||
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
|
||||
|
||||
pp_fate_sig({[Arg], Res}) ->
|
||||
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
|
||||
pp_fate_sig({Args, Res}) ->
|
||||
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
|
||||
|
||||
pp_fate_type(T) -> io_lib:format("~w", [T]).
|
||||
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
-spec parse(string(), so_compiler:options()) -> none() | so_syntax:ast().
|
||||
parse(Text, Options) ->
|
||||
parse(Text, sets:new(), Options).
|
||||
|
||||
-spec parse(string(), sets:set(), so_compiler:options()) -> none() | so_syntax:ast().
|
||||
parse(Text, Included, Options) ->
|
||||
so_parser:string(Text, Included, Options).
|
||||
|
||||
read_contract(Name) ->
|
||||
file:read_file(Name).
|
@ -1,11 +1,13 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc ADT for structured error messages + formatting.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_errors).
|
||||
-module(so_errors).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-type src_file() :: no_file | iolist().
|
||||
|
||||
@ -30,12 +32,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 +56,12 @@ new(Type, Pos, Msg) ->
|
||||
new(Type, Pos, Msg, Ctxt) ->
|
||||
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
|
||||
|
||||
pos(Ann) ->
|
||||
File = so_syntax:get_ann(file, Ann, no_file),
|
||||
Line = so_syntax:get_ann(line, Ann, 0),
|
||||
Col = so_syntax:get_ann(col, Ann, 0),
|
||||
pos(File, Line, Col).
|
||||
|
||||
pos(Line, Col) ->
|
||||
#pos{ line = Line, col = Col }.
|
||||
|
||||
@ -65,10 +77,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 +93,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";
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,14 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Parser combinators for the Sophia parser. Based on
|
||||
%%% Koen Claessen. 2004. Parallel Parsing Processes. J. Functional
|
||||
%%% Programming 14, 6 (November 2004)
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_parse_lib).
|
||||
-module(so_parse_lib).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([parse/2,
|
||||
return/1, fail/0, fail/1, fail/2, map/2, bind/2,
|
||||
@ -15,7 +17,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_dir/0, set_current_dir/1,
|
||||
current_include_type/0, set_current_include_type/1]).
|
||||
|
||||
%% -- Types ------------------------------------------------------------------
|
||||
|
||||
@ -26,16 +29,16 @@
|
||||
-type tokens() :: [token()].
|
||||
-type error() :: {pos(), string() | no_error}.
|
||||
|
||||
-define(lazy(F), {aeso_parse_lazy, F}).
|
||||
-define(fail(Err), {aeso_parse_fail, Err}).
|
||||
-define(choice(Ps), {aeso_parse_choice, Ps}).
|
||||
-define(bind(P, F), {aeso_parse_bind, P, F}).
|
||||
-define(right(P, Q), {aeso_parse_right, P, Q}).
|
||||
-define(left(P, Q), {aeso_parse_left, P, Q}).
|
||||
-define(map(F, P), {aeso_parse_map, F, P}).
|
||||
-define(layout, aeso_parse_layout).
|
||||
-define(tok(Atom), {aeso_parse_tok, Atom}).
|
||||
-define(return(X), {aeso_parse_return, X}).
|
||||
-define(lazy(F), {so_parse_lazy, F}).
|
||||
-define(fail(Err), {so_parse_fail, Err}).
|
||||
-define(choice(Ps), {so_parse_choice, Ps}).
|
||||
-define(bind(P, F), {so_parse_bind, P, F}).
|
||||
-define(right(P, Q), {so_parse_right, P, Q}).
|
||||
-define(left(P, Q), {so_parse_left, P, Q}).
|
||||
-define(map(F, P), {so_parse_map, F, P}).
|
||||
-define(layout, so_parse_layout).
|
||||
-define(tok(Atom), {so_parse_tok, Atom}).
|
||||
-define(return(X), {so_parse_return, X}).
|
||||
|
||||
%% Type synonyms since you can't have function types as macro arguments for some reason.
|
||||
-type delayed(A) :: fun(() -> A).
|
||||
@ -465,6 +468,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').
|
||||
@ -472,6 +482,13 @@ current_file() ->
|
||||
set_current_file(File) ->
|
||||
put('$current_file', File).
|
||||
|
||||
%% Current source directory
|
||||
current_dir() ->
|
||||
get('$current_dir').
|
||||
|
||||
set_current_dir(File) ->
|
||||
put('$current_dir', File).
|
||||
|
||||
add_current_file({L, C}) -> {current_file(), L, C};
|
||||
add_current_file(Pos) -> Pos.
|
||||
|
@ -1,8 +1,9 @@
|
||||
%%% File : aeso_parser.erl
|
||||
%%% File : so_parser.erl
|
||||
%%% Author : Ulf Norell
|
||||
%%% Description :
|
||||
%%% Created : 1 Mar 2018 by Ulf Norell
|
||||
-module(aeso_parser).
|
||||
-module(so_parser).
|
||||
-vsn("9.0.0").
|
||||
-compile({no_auto_import,[map_get/2]}).
|
||||
|
||||
-export([string/1,
|
||||
@ -17,10 +18,12 @@
|
||||
run_parser/2,
|
||||
run_parser/3]).
|
||||
|
||||
-include("aeso_parse_lib.hrl").
|
||||
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
|
||||
-include("so_parse_lib.hrl").
|
||||
-import(so_parse_lib, [current_file/0, set_current_file/1,
|
||||
current_dir/0, set_current_dir/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 parse_result() :: so_syntax:ast() | {so_syntax:ast(), sets:set(include_hash())} | none().
|
||||
|
||||
-type include_hash() :: {string(), binary()}.
|
||||
|
||||
@ -34,14 +37,14 @@ escape_errors({error, Err}) ->
|
||||
string(String) ->
|
||||
string(String, sets:new(), []).
|
||||
|
||||
-spec string(string(), aeso_compiler:options()) -> parse_result().
|
||||
-spec string(string(), so_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().
|
||||
-spec string(string(), sets:set(include_hash()), so_compiler:options()) -> parse_result().
|
||||
string(String, Included, Opts) ->
|
||||
AST = run_parser(file(), String, Opts),
|
||||
case expand_includes(AST, Included, Opts) of
|
||||
@ -57,18 +60,20 @@ run_parser(P, Inp, Opts) ->
|
||||
|
||||
parse_and_scan(P, S, Opts) ->
|
||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||
case aeso_scan:scan(S) of
|
||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||
set_current_dir(proplists:get_value(src_dir, Opts, no_file)),
|
||||
set_current_include_type(proplists:get_value(include_type, Opts, none)),
|
||||
case so_scan:scan(S) of
|
||||
{ok, Tokens} -> so_parse_lib:parse(P, Tokens);
|
||||
{error, {{Input, Pos}, _}} ->
|
||||
{error, {Pos, scan_error, Input}}
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, parse_error/1}).
|
||||
parse_error(Err) ->
|
||||
aeso_errors:throw(mk_error(Err)).
|
||||
so_errors:throw(mk_error(Err)).
|
||||
|
||||
mk_p_err(Pos, Msg) ->
|
||||
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
|
||||
so_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
|
||||
|
||||
mk_error({Pos, scan_error, Input}) ->
|
||||
mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
|
||||
@ -82,8 +87,8 @@ mk_error({Pos, include_error, File}) ->
|
||||
Msg = io_lib:format("Couldn't find include file '~s'\n", [File]),
|
||||
mk_p_err(Pos, Msg).
|
||||
|
||||
mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col);
|
||||
mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col).
|
||||
mk_pos({Line, Col}) -> so_errors:pos(Line, Col);
|
||||
mk_pos({File, Line, Col}) -> so_errors:pos(File, Line, Col).
|
||||
|
||||
%% -- Parsing rules ----------------------------------------------------------
|
||||
|
||||
@ -94,21 +99,34 @@ decl() ->
|
||||
choice(
|
||||
%% Contract declaration
|
||||
[ ?RULE(token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, [], _5})
|
||||
, ?RULE(token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4})
|
||||
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
|
||||
, ?RULE(keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5})
|
||||
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
|
||||
, ?RULE(keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
|
||||
, ?RULE(token(payable), token(main), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
|
||||
, ?RULE(token(payable), keyword(contract),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6}))
|
||||
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
|
||||
, ?RULE(token(payable), keyword(contract), token(interface),
|
||||
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
|
||||
|
||||
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||
, using()
|
||||
, pragma()
|
||||
|
||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||
@ -135,6 +153,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}}).
|
||||
@ -195,10 +228,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()).
|
||||
@ -208,6 +247,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()).
|
||||
@ -225,10 +267,11 @@ type300() ->
|
||||
type400() ->
|
||||
choice(
|
||||
[?RULE(typeAtom(), optional(type_args()),
|
||||
any_bytes(
|
||||
case _2 of
|
||||
none -> _1;
|
||||
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
||||
end),
|
||||
end)),
|
||||
?RULE(id("bytes"), parens(token(int)),
|
||||
{bytes_t, get_ann(_1), element(3, _2)})
|
||||
]).
|
||||
@ -254,16 +297,23 @@ body() ->
|
||||
|
||||
stmt() ->
|
||||
?LAZY_P(choice(
|
||||
[ expr()
|
||||
[ using()
|
||||
, expr()
|
||||
, letdecl()
|
||||
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
|
||||
, {'if', keyword('if'), parens(expr()), body()}
|
||||
, {elif, keyword(elif), parens(expr()), body()}
|
||||
, {else, keyword(else), body()}
|
||||
, {'else', keyword('else'), body()}
|
||||
])).
|
||||
|
||||
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)).
|
||||
@ -274,33 +324,39 @@ 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('&&')).
|
||||
expr300() -> infixr(expr325(), binop('&&')).
|
||||
expr325() -> infixl(expr350(), binop('bor')).
|
||||
expr350() -> infixl(expr375(), binop('bxor')).
|
||||
expr375() -> infixl(expr400(), binop('band')).
|
||||
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
|
||||
expr500() -> infixr(expr600(), binop(['::', '++'])).
|
||||
expr500() -> infixr(expr550(), binop(['::', '++'])).
|
||||
expr550() -> infixl(expr600(), binop(['<<', '>>'])).
|
||||
expr600() -> infixl(expr650(), binop(['+', '-'])).
|
||||
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
|
||||
expr700() -> infixl(expr750(), binop(['*', '/', mod])).
|
||||
expr750() -> infixl(expr800(), binop(['^'])).
|
||||
expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)).
|
||||
expr800() -> ?RULE(many(token('!')), expr850(), prefixes(_1, _2)).
|
||||
expr850() -> ?RULE(many(token('bnot')), expr900(), prefixes(_1, _2)).
|
||||
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
|
||||
|
||||
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)))
|
||||
@ -311,9 +367,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()
|
||||
@ -352,7 +412,7 @@ map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
|
||||
|
||||
elim(E, []) -> E;
|
||||
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
|
||||
elim(E, [{app, _Ann, Args} | Es]) -> elim({app, aeso_syntax:get_ann(E), E, Args}, Es);
|
||||
elim(E, [{app, _Ann, Args} | Es]) -> elim({app, so_syntax:get_ann(E), E, Args}, Es);
|
||||
elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es);
|
||||
elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es);
|
||||
elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es).
|
||||
@ -436,6 +496,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
|
||||
@ -453,10 +526,10 @@ id_or_addr() ->
|
||||
?RULE(id(), parse_addr_literal(_1)).
|
||||
|
||||
parse_addr_literal(Id = {id, Ann, Name}) ->
|
||||
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
|
||||
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_", "sg_"]) of
|
||||
false -> Id;
|
||||
true ->
|
||||
try aeser_api_encoder:decode(list_to_binary(Name)) of
|
||||
try gmser_api_encoder:decode(list_to_binary(Name)) of
|
||||
{Type, Bin} -> {Type, Ann, Bin}
|
||||
catch _:_ ->
|
||||
Id
|
||||
@ -485,12 +558,17 @@ bracket_list(P) -> brackets(comma_sep(P)).
|
||||
|
||||
%% -- Annotations ------------------------------------------------------------
|
||||
|
||||
-type ann() :: aeso_syntax:ann().
|
||||
-type ann_line() :: aeso_syntax:ann_line().
|
||||
-type ann_col() :: aeso_syntax:ann_col().
|
||||
-type ann() :: so_syntax:ann().
|
||||
-type ann_line() :: so_syntax:ann_line().
|
||||
-type ann_col() :: so_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()}
|
||||
, {dir, current_dir()}
|
||||
, {include_type, current_include_type()}
|
||||
, {line, Line}
|
||||
, {col, Col} ].
|
||||
|
||||
ann_pos(Ann) ->
|
||||
{proplists:get_value(file, Ann),
|
||||
@ -535,7 +613,7 @@ group_ifs([], Acc) ->
|
||||
group_ifs([{'if', Ann, Cond, Then} | Stmts], Acc) ->
|
||||
{Elses, Rest} = else_branches(Stmts, []),
|
||||
group_ifs(Rest, [build_if(Ann, Cond, Then, Elses) | Acc]);
|
||||
group_ifs([{else, Ann, _} | _], _) ->
|
||||
group_ifs([{'else', Ann, _} | _], _) ->
|
||||
fail({Ann, "No matching 'if' for 'else'"});
|
||||
group_ifs([{elif, Ann, _, _} | _], _) ->
|
||||
fail({Ann, "No matching 'if' for 'elif'"});
|
||||
@ -545,14 +623,14 @@ group_ifs([Stmt | Stmts], Acc) ->
|
||||
build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
|
||||
{'if', Ann, Cond, Then,
|
||||
set_ann(format, elif, build_if(Ann1, Cond1, Then1, Elses))};
|
||||
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
||||
build_if(Ann, Cond, Then, [{'else', _Ann, Else}]) ->
|
||||
{'if', Ann, Cond, Then, Else};
|
||||
build_if(Ann, Cond, Then, []) ->
|
||||
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
|
||||
|
||||
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
||||
else_branches(Stmts, [Elif | Acc]);
|
||||
else_branches([Else = {else, _, _} | Stmts], Acc) ->
|
||||
else_branches([Else = {'else', _, _} | Stmts], Acc) ->
|
||||
{lists:reverse([Else | Acc]), Stmts};
|
||||
else_branches(Stmts, Acc) ->
|
||||
{lists:reverse(Acc), Stmts}.
|
||||
@ -570,7 +648,9 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
||||
|
||||
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
||||
|
||||
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
||||
-spec parse_pattern(so_syntax:expr()) -> so_parse_lib:parser(so_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}]}) ->
|
||||
@ -595,19 +675,19 @@ parse_pattern(E = {string, _, _}) -> E;
|
||||
parse_pattern(E = {char, _, _}) -> E;
|
||||
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
||||
|
||||
-spec parse_field_pattern(aeso_syntax:field(aeso_syntax:expr())) -> aeso_parse_lib:parser(aeso_syntax:field(aeso_syntax:pat())).
|
||||
-spec parse_field_pattern(so_syntax:field(so_syntax:expr())) -> so_parse_lib:parser(so_syntax:field(so_syntax:pat())).
|
||||
parse_field_pattern({field, Ann, F, E}) ->
|
||||
{field, Ann, F, parse_pattern(E)}.
|
||||
|
||||
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
|
||||
-spec ret_doc_err(ann(), prettypr:document()) -> so_parse_lib:parser(none()).
|
||||
ret_doc_err(Ann, Doc) ->
|
||||
fail(ann_pos(Ann), prettypr:format(Doc)).
|
||||
|
||||
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
|
||||
-spec bad_expr_err(string(), so_syntax:expr()) -> so_parse_lib:parser(none()).
|
||||
bad_expr_err(Reason, E) ->
|
||||
ret_doc_err(get_ann(E),
|
||||
prettypr:sep([prettypr:text(Reason ++ ":"),
|
||||
prettypr:nest(2, aeso_pretty:expr(E))])).
|
||||
prettypr:nest(2, so_pretty:expr(E))])).
|
||||
|
||||
%% -- Helper functions -------------------------------------------------------
|
||||
|
||||
@ -626,13 +706,20 @@ expand_includes([], Included, Acc, Opts) ->
|
||||
end;
|
||||
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
|
||||
case get_include_code(File, Ann, Opts) of
|
||||
{ok, Code} ->
|
||||
{ok, AbsDir, Code} ->
|
||||
Hashed = hash_include(File, Code),
|
||||
case sets:is_element(Hashed, Included) of
|
||||
false ->
|
||||
SrcFile = proplists:get_value(src_file, Opts, no_file),
|
||||
IncludeType = case proplists:get_value(file, Ann) of
|
||||
SrcFile -> direct;
|
||||
_ -> indirect
|
||||
end,
|
||||
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
|
||||
Opts2 = lists:keystore(src_dir, 1, Opts1, {src_dir, AbsDir}),
|
||||
Opts3 = lists:keystore(include_type, 1, Opts2, {include_type, IncludeType}),
|
||||
Included1 = sets:add_element(Hashed, Included),
|
||||
case parse_and_scan(file(), Code, Opts1) of
|
||||
case parse_and_scan(file(), Code, Opts3) of
|
||||
{ok, AST1} ->
|
||||
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|
||||
Err = {error, _} ->
|
||||
@ -650,22 +737,21 @@ expand_includes([E | AST], Included, 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);
|
||||
lists:foldr(fun(Path, {error, _}) -> read_file_(Path, File);
|
||||
(_Path, OK) -> OK end, {error, not_found}, Paths);
|
||||
{explicit_files, Files} ->
|
||||
case maps:get(binary_to_list(File), Files, not_found) of
|
||||
not_found -> {error, not_found};
|
||||
Src -> {ok, Src}
|
||||
Src -> {ok, File, 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])),
|
||||
FileName = binary_to_list(filename:join([sophia, priv, stdlib, File])),
|
||||
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
|
||||
{ok, [{_, Src}]} -> {ok, Src};
|
||||
{ok, [{_, Src}]} -> {ok, escript, Src};
|
||||
_ -> {error, not_found}
|
||||
end
|
||||
catch _:_ ->
|
||||
@ -673,31 +759,52 @@ read_file(File, Opts) ->
|
||||
end
|
||||
end.
|
||||
|
||||
read_file_(Path, File) ->
|
||||
AbsFile = filename:join(Path, File),
|
||||
case file:read_file(AbsFile) of
|
||||
{ok, Bin} -> {ok, so_utils:canonical_dir(filename:dirname(AbsFile)), Bin};
|
||||
Err -> Err
|
||||
end.
|
||||
|
||||
stdlib_options() ->
|
||||
StdLibDir = aeso_stdlib:stdlib_include_path(),
|
||||
StdLibDir = so_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, _}} ->
|
||||
%% Temporarily extend include paths with the directory of the current file
|
||||
Opts1 = include_current_file_dir(Opts, Ann),
|
||||
case {read_file(File, Opts1), read_file(File, stdlib_options())} of
|
||||
{{ok, Dir, 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)}
|
||||
false -> {ok, Dir, binary_to_list(Bin)}
|
||||
end;
|
||||
{_, {ok, Bin}} ->
|
||||
{ok, binary_to_list(Bin)};
|
||||
{{ok, Bin}, _} ->
|
||||
{ok, binary_to_list(Bin)};
|
||||
{_, {ok, _, Bin}} ->
|
||||
{ok, stdlib, binary_to_list(Bin)};
|
||||
{{ok, Dir, Bin}, _} ->
|
||||
{ok, Dir, binary_to_list(Bin)};
|
||||
{_, _} ->
|
||||
{error, {ann_pos(Ann), include_error, File}}
|
||||
end.
|
||||
|
||||
include_current_file_dir(Opts, Ann) ->
|
||||
case {proplists:get_value(dir, Ann, undefined),
|
||||
proplists:get_value(include, Opts, undefined)} of
|
||||
{undefined, _} -> Opts;
|
||||
{CurrDir, {file_system, Paths}} ->
|
||||
case lists:member(CurrDir, Paths) of
|
||||
false -> [{include, {file_system, [CurrDir | Paths]}} | Opts];
|
||||
true -> Opts
|
||||
end;
|
||||
{_, _} -> Opts
|
||||
end.
|
||||
|
||||
-spec hash_include(string() | binary(), string()) -> include_hash().
|
||||
hash_include(File, Code) when is_binary(File) ->
|
||||
hash_include(binary_to_list(File), Code);
|
||||
@ -711,3 +818,7 @@ auto_imports(L) when is_list(L) ->
|
||||
auto_imports(T) when is_tuple(T) ->
|
||||
auto_imports(tuple_to_list(T));
|
||||
auto_imports(_) -> [].
|
||||
|
||||
any_bytes({id, Ann, "bytes"}) -> {bytes_t, Ann, any};
|
||||
any_bytes({app_t, _, {id, Ann, "bytes"}, []}) -> {bytes_t, Ann, any};
|
||||
any_bytes(Type) -> Type.
|
@ -1,11 +1,13 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Pretty printer for Sophia.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_pretty).
|
||||
-module(so_pretty).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-import(prettypr, [text/1, sep/1, above/2, beside/2, nest/2, empty/0]).
|
||||
|
||||
@ -13,7 +15,7 @@
|
||||
|
||||
-export_type([options/0]).
|
||||
|
||||
-include("aeso_utils.hrl").
|
||||
-include("so_utils.hrl").
|
||||
|
||||
-type doc() :: prettypr:document().
|
||||
-type options() :: [{indent, non_neg_integer()} | show_generated].
|
||||
@ -24,11 +26,11 @@
|
||||
|
||||
%% -- Options ----------------------------------------------------------------
|
||||
|
||||
-define(aeso_pretty_opts, aeso_pretty_opts).
|
||||
-define(so_pretty_opts, so_pretty_opts).
|
||||
|
||||
-spec options() -> options().
|
||||
options() ->
|
||||
case get(?aeso_pretty_opts) of
|
||||
case get(?so_pretty_opts) of
|
||||
undefined -> [];
|
||||
Opts -> Opts
|
||||
end.
|
||||
@ -45,9 +47,9 @@ indent() -> option(indent, 2).
|
||||
|
||||
-spec with_options(options(), fun(() -> A)) -> A.
|
||||
with_options(Options, Fun) ->
|
||||
put(?aeso_pretty_opts, Options),
|
||||
put(?so_pretty_opts, Options),
|
||||
Res = Fun(),
|
||||
erase(?aeso_pretty_opts),
|
||||
erase(?so_pretty_opts),
|
||||
Res.
|
||||
|
||||
%% -- Pretty printing helpers ------------------------------------------------
|
||||
@ -125,9 +127,9 @@ record(Ds) ->
|
||||
equals(A, B) -> follow(hsep(A, text("=")), B).
|
||||
|
||||
%% typed(A, B) -> A : B.
|
||||
-spec typed(doc(), aeso_syntax:type()) -> doc().
|
||||
-spec typed(doc(), so_syntax:type()) -> doc().
|
||||
typed(A, Type) ->
|
||||
case aeso_syntax:get_ann(origin, Type) == system andalso
|
||||
case so_syntax:get_ann(origin, Type) == system andalso
|
||||
not show_generated() of
|
||||
true -> A;
|
||||
false -> follow(hsep(A, text(":")), type(Type))
|
||||
@ -139,24 +141,28 @@ contract_head(contract_interface) -> text("contract interface").
|
||||
|
||||
%% -- Exports ----------------------------------------------------------------
|
||||
|
||||
-spec decls([aeso_syntax:decl()], options()) -> doc().
|
||||
-spec decls([so_syntax:decl()], options()) -> doc().
|
||||
decls(Ds, Options) ->
|
||||
with_options(Options, fun() -> decls(Ds) end).
|
||||
|
||||
-spec decls([aeso_syntax:decl()]) -> doc().
|
||||
-spec decls([so_syntax:decl()]) -> doc().
|
||||
decls(Ds) -> above([ decl(D) || D <- Ds ]).
|
||||
|
||||
-spec decl(aeso_syntax:decl(), options()) -> doc().
|
||||
-spec decl(so_syntax:decl(), options()) -> doc().
|
||||
decl(D, Options) ->
|
||||
with_options(Options, fun() -> decl(D) end).
|
||||
|
||||
-spec decl(aeso_syntax:decl()) -> doc().
|
||||
decl({Con, Attrs, C, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
-spec decl(so_syntax:decl()) -> doc().
|
||||
decl({Con, Attrs, C, Is, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
|
||||
Mod = fun({Mod, true}) when Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
ImplsList = case Is of
|
||||
[] -> [empty()];
|
||||
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
|
||||
end,
|
||||
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
|
||||
, hsep(name(C), text("="))), decls(Ds));
|
||||
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({pragma, _, Pragma}) -> pragma(Pragma);
|
||||
@ -168,7 +174,7 @@ decl({fun_decl, Ann, F, T}) ->
|
||||
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of
|
||||
Fun = case so_syntax:get_ann(entrypoint, Ann, false) of
|
||||
true -> text("entrypoint");
|
||||
false -> text("function")
|
||||
end,
|
||||
@ -177,7 +183,7 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
|
||||
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
|
||||
text(atom_to_list(Mod));
|
||||
(_) -> empty() end,
|
||||
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of
|
||||
Fun = case so_syntax:get_ann(entrypoint, Attrs, false) of
|
||||
true -> "entrypoint";
|
||||
false -> "function"
|
||||
end,
|
||||
@ -188,20 +194,20 @@ decl(D = {letval, _, _, _}) -> letdecl("let", D);
|
||||
decl({block, _, Ds}) ->
|
||||
above([ decl(D) || D <- Ds ]).
|
||||
|
||||
-spec pragma(aeso_syntax:pragma()) -> doc().
|
||||
-spec pragma(so_syntax:pragma()) -> doc().
|
||||
pragma({compiler, Op, Ver}) ->
|
||||
text("@compiler " ++ atom_to_list(Op) ++ " " ++ string:join([integer_to_list(N) || N <- Ver], ".")).
|
||||
|
||||
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
||||
-spec expr(so_syntax:expr(), options()) -> doc().
|
||||
expr(E, Options) ->
|
||||
with_options(Options, fun() -> expr(E) end).
|
||||
|
||||
-spec expr(aeso_syntax:expr()) -> doc().
|
||||
-spec expr(so_syntax:expr()) -> doc().
|
||||
expr(E) -> expr_p(0, E).
|
||||
|
||||
%% -- Not exported -----------------------------------------------------------
|
||||
|
||||
-spec name(aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon() | aeso_syntax:tvar()) -> doc().
|
||||
-spec name(so_syntax:id() | so_syntax:qid() | so_syntax:con() | so_syntax:qcon() | so_syntax:tvar()) -> doc().
|
||||
name({id, _, Name}) -> text(Name);
|
||||
name({con, _, Name}) -> text(Name);
|
||||
name({qid, _, Names}) -> text(string:join(Names, "."));
|
||||
@ -209,20 +215,22 @@ name({qcon, _, Names}) -> text(string:join(Names, "."));
|
||||
name({tvar, _, Name}) -> text(Name);
|
||||
name({typed, _, Name, _}) -> name(Name).
|
||||
|
||||
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
|
||||
-spec letdecl(string(), so_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().
|
||||
-spec args([so_syntax:arg()]) -> doc().
|
||||
args(Args) ->
|
||||
tuple(lists:map(fun arg/1, Args)).
|
||||
|
||||
-spec arg(aeso_syntax:arg()) -> doc().
|
||||
-spec arg(so_syntax:arg()) -> doc().
|
||||
arg({arg, _, X, T}) -> typed(name(X), T).
|
||||
|
||||
-spec typedecl(alias_t | record_t | variant_t, aeso_syntax:id(), [aeso_syntax:tvar()]) -> doc().
|
||||
-spec typedecl(alias_t | record_t | variant_t, so_syntax:id(), [so_syntax:tvar()]) -> doc().
|
||||
typedecl(Kind, T, Vars) ->
|
||||
KW = case Kind of
|
||||
alias_t -> text("type");
|
||||
@ -235,26 +243,28 @@ typedecl(Kind, T, Vars) ->
|
||||
tuple(lists:map(fun name/1, Vars)))
|
||||
end.
|
||||
|
||||
-spec typedef(aeso_syntax:typedef()) -> doc().
|
||||
-spec typedef(so_syntax:typedef()) -> doc().
|
||||
typedef({alias_t, Type}) -> type(Type);
|
||||
typedef({record_t, Fields}) ->
|
||||
record(lists:map(fun field_t/1, Fields));
|
||||
typedef({variant_t, Constructors}) ->
|
||||
par(punctuate(text(" |"), lists:map(fun constructor_t/1, Constructors))).
|
||||
|
||||
-spec constructor_t(aeso_syntax:constructor_t()) -> doc().
|
||||
-spec constructor_t(so_syntax:constructor_t()) -> doc().
|
||||
constructor_t({constr_t, _, C, []}) -> name(C);
|
||||
constructor_t({constr_t, _, C, Args}) -> beside(name(C), args_type(Args)).
|
||||
|
||||
-spec field_t(aeso_syntax:field_t()) -> doc().
|
||||
-spec field_t(so_syntax:field_t()) -> doc().
|
||||
field_t({field_t, _, Name, Type}) ->
|
||||
typed(name(Name), Type).
|
||||
|
||||
-spec type(aeso_syntax:type(), options()) -> doc().
|
||||
-spec type(so_syntax:type(), options()) -> doc().
|
||||
type(Type, Options) ->
|
||||
with_options(Options, fun() -> type(Type) end).
|
||||
|
||||
-spec type(aeso_syntax:type()) -> doc().
|
||||
-spec type(so_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}) ->
|
||||
@ -267,7 +277,9 @@ type({tuple_t, _, Args}) ->
|
||||
tuple_type(Args);
|
||||
type({args_t, _, Args}) ->
|
||||
args_type(Args);
|
||||
type({bytes_t, _, any}) -> text("bytes(_)");
|
||||
type({bytes_t, _, any}) -> text("bytes()");
|
||||
type({bytes_t, _, '_'}) -> text("bytes(_)");
|
||||
type({bytes_t, _, fixed}) -> text("bytes(_)");
|
||||
type({bytes_t, _, Len}) ->
|
||||
text(lists:concat(["bytes(", Len, ")"]));
|
||||
type({if_t, _, Id, Then, Else}) ->
|
||||
@ -282,13 +294,15 @@ 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);
|
||||
|
||||
-spec args_type([aeso_syntax:type()]) -> doc().
|
||||
type(var_args) -> text("var_args").
|
||||
|
||||
-spec args_type([so_syntax:type()]) -> doc().
|
||||
args_type(Args) ->
|
||||
tuple(lists:map(fun type/1, Args)).
|
||||
|
||||
-spec tuple_type([aeso_syntax:type()]) -> doc().
|
||||
-spec tuple_type([so_syntax:type()]) -> doc().
|
||||
tuple_type([]) ->
|
||||
text("unit");
|
||||
tuple_type(Factors) ->
|
||||
@ -298,13 +312,15 @@ tuple_type(Factors) ->
|
||||
, text(")")
|
||||
]).
|
||||
|
||||
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
||||
-spec expr_p(integer(), so_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}) ->
|
||||
Format = aeso_syntax:get_ann(format, If),
|
||||
Format = so_syntax:get_ann(format, If),
|
||||
if Format == '?:' ->
|
||||
paren(P > 100,
|
||||
follow(expr_p(200, Cond),
|
||||
@ -347,7 +363,7 @@ expr_p(P, {assign, _, LV, E}) ->
|
||||
expr_p(_, {app, _, {'..', _}, [A, B]}) ->
|
||||
list([infix(0, '..', A, B)]);
|
||||
expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
|
||||
case {aeso_syntax:get_ann(format, E), Args} of
|
||||
case {so_syntax:get_ann(format, E), Args} of
|
||||
{infix, [A, B]} -> infix(P, Op, A, B);
|
||||
{prefix, [A]} -> prefix(P, Op, A);
|
||||
_ -> app(P, F, Args)
|
||||
@ -358,7 +374,7 @@ expr_p(P, {app, _, F, Args}) ->
|
||||
app(P, F, Args);
|
||||
%% -- Constants
|
||||
expr_p(_, E = {int, _, N}) ->
|
||||
S = case aeso_syntax:get_ann(format, E) of
|
||||
S = case so_syntax:get_ann(format, E) of
|
||||
hex -> "0x" ++ integer_to_list(N, 16);
|
||||
_ -> integer_to_list(N)
|
||||
end,
|
||||
@ -373,8 +389,9 @@ expr_p(_, {Type, _, Bin})
|
||||
when Type == account_pubkey;
|
||||
Type == contract_pubkey;
|
||||
Type == oracle_pubkey;
|
||||
Type == oracle_query_id ->
|
||||
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
|
||||
Type == oracle_query_id;
|
||||
Type == signature ->
|
||||
text(binary_to_list(gmser_api_encoder:encode(Type, Bin)));
|
||||
expr_p(_, {string, _, <<>>}) -> text("\"\"");
|
||||
expr_p(_, {string, _, S}) ->
|
||||
text(io_lib:format("\"~s\"", [binary_to_list(S)]));
|
||||
@ -387,7 +404,7 @@ expr_p(_, {char, _, C}) ->
|
||||
text("'" ++ tl(lists:droplast(S)) ++ "'");
|
||||
_ ->
|
||||
S = lists:flatten(
|
||||
io_lib:format("'~ts'", [list_to_binary(aeso_scan:utf8_encode([C]))])),
|
||||
io_lib:format("'~ts'", [list_to_binary(so_scan:utf8_encode([C]))])),
|
||||
text(S)
|
||||
end;
|
||||
%% -- Names
|
||||
@ -404,9 +421,9 @@ stmt_p({'if', _, Cond, Then}) ->
|
||||
block_expr(200, beside(text("if"), paren(expr(Cond))), Then);
|
||||
stmt_p({elif, _, Cond, Then}) ->
|
||||
block_expr(200, beside(text("elif"), paren(expr(Cond))), Then);
|
||||
stmt_p({else, Else}) ->
|
||||
stmt_p({'else', Else}) ->
|
||||
HideGenerated = not show_generated(),
|
||||
case aeso_syntax:get_ann(origin, Else) of
|
||||
case so_syntax:get_ann(origin, Else) of
|
||||
system when HideGenerated -> empty();
|
||||
_ -> block_expr(200, text("else"), Else)
|
||||
end.
|
||||
@ -418,20 +435,26 @@ lc_bind({comprehension_if, _, E}) ->
|
||||
lc_bind(Let) ->
|
||||
letdecl("let", Let).
|
||||
|
||||
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
|
||||
-spec bin_prec(so_syntax:bin_op()) -> {integer(), integer(), integer()}.
|
||||
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('&&') -> {300, 325, 300};
|
||||
bin_prec('bor') -> {325, 350, 325};
|
||||
bin_prec('bxor') -> {350, 375, 350};
|
||||
bin_prec('band') -> {375, 400, 375};
|
||||
bin_prec('<') -> {400, 500, 500};
|
||||
bin_prec('>') -> {400, 500, 500};
|
||||
bin_prec('=<') -> {400, 500, 500};
|
||||
bin_prec('>=') -> {400, 500, 500};
|
||||
bin_prec('==') -> {400, 500, 500};
|
||||
bin_prec('!=') -> {400, 500, 500};
|
||||
bin_prec('++') -> {500, 600, 500};
|
||||
bin_prec('::') -> {500, 600, 500};
|
||||
bin_prec('++') -> {500, 550, 500};
|
||||
bin_prec('::') -> {500, 550, 500};
|
||||
bin_prec('<<') -> {550, 600, 550};
|
||||
bin_prec('>>') -> {550, 600, 550};
|
||||
bin_prec('+') -> {600, 600, 650};
|
||||
bin_prec('-') -> {600, 600, 650};
|
||||
bin_prec('*') -> {700, 700, 750};
|
||||
@ -439,14 +462,15 @@ bin_prec('/') -> {700, 700, 750};
|
||||
bin_prec(mod) -> {700, 700, 750};
|
||||
bin_prec('^') -> {750, 750, 800}.
|
||||
|
||||
-spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}.
|
||||
-spec un_prec(so_syntax:un_op()) -> {integer(), integer()}.
|
||||
un_prec('-') -> {650, 650};
|
||||
un_prec('!') -> {800, 800}.
|
||||
un_prec('!') -> {800, 800};
|
||||
un_prec('bnot') -> {850, 850}.
|
||||
|
||||
equals(Ann, A, B) ->
|
||||
{app, [{format, infix} | Ann], {'=', Ann}, [A, B]}.
|
||||
|
||||
-spec infix(integer(), aeso_syntax:bin_op(), aeso_syntax:expr(), aeso_syntax:expr()) -> doc().
|
||||
-spec infix(integer(), so_syntax:bin_op(), so_syntax:expr(), so_syntax:expr()) -> doc().
|
||||
infix(P, Op, A, B) ->
|
||||
{Top, L, R} = bin_prec(Op),
|
||||
paren(P > Top,
|
||||
@ -480,8 +504,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));
|
||||
@ -498,9 +532,9 @@ statement(E) -> expr(E).
|
||||
get_elifs(Expr) -> get_elifs(Expr, []).
|
||||
|
||||
get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) ->
|
||||
case aeso_syntax:get_ann(format, If) of
|
||||
case so_syntax:get_ann(format, If) of
|
||||
elif -> get_elifs(Else, [{elif, Ann, Cond, Then} | Elifs]);
|
||||
_ -> {lists:reverse(Elifs), If}
|
||||
end;
|
||||
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
|
||||
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {'else', Else}}.
|
||||
|
@ -1,15 +1,17 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc The Sophia lexer.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_scan).
|
||||
-module(so_scan).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([scan/1, utf8_encode/1]).
|
||||
|
||||
-import(aeso_scan_lib, [token/1, token/2, symbol/0, skip/0,
|
||||
-import(so_scan_lib, [token/1, token/2, symbol/0, skip/0,
|
||||
override/2, push/2, pop/1]).
|
||||
|
||||
lexer() ->
|
||||
@ -45,7 +47,7 @@ lexer() ->
|
||||
|
||||
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
|
||||
"interface", "main"
|
||||
"interface", "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot"
|
||||
],
|
||||
KW = string:join(Keywords, "|"),
|
||||
|
||||
@ -79,8 +81,8 @@ lexer() ->
|
||||
[{code, Rules}, {comment, CommentRules}].
|
||||
|
||||
scan(String) ->
|
||||
Lexer = aeso_scan_lib:compile(lexer()),
|
||||
aeso_scan_lib:string(Lexer, code, String).
|
||||
Lexer = so_scan_lib:compile(lexer()),
|
||||
so_scan_lib:string(Lexer, code, String).
|
||||
|
||||
%% -- Helpers ----------------------------------------------------------------
|
||||
|
@ -1,10 +1,12 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc A customisable lexer.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_scan_lib).
|
||||
-module(so_scan_lib).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([compile/1, string/3,
|
||||
token/1, token/2, symbol/0, skip/0,
|
@ -1,17 +1,18 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @author Radosław Rowicki
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Standard library for Sophia
|
||||
%%% @end
|
||||
%%% Created : 6 July 2019
|
||||
%%%
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_stdlib).
|
||||
-module(so_stdlib).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([stdlib_include_path/0]).
|
||||
|
||||
stdlib_include_path() ->
|
||||
filename:join([code:priv_dir(aesophia), "stdlib"]).
|
||||
{file, BEAM} = code:is_loaded(?MODULE),
|
||||
filename:join(filename:dirname(filename:dirname(BEAM)), "priv/stdlib").
|
||||
|
@ -1,19 +1,21 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Sophia abstract syntax types.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_syntax).
|
||||
-module(so_syntax).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
|
||||
|
||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([ann_file/0, 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([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]).
|
||||
@ -24,9 +26,10 @@
|
||||
-type ann_col() :: integer().
|
||||
-type ann_origin() :: system | user.
|
||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||
-type ann_file() :: string() | no_file.
|
||||
|
||||
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface].
|
||||
-type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface | entrypoint].
|
||||
|
||||
-type name() :: string().
|
||||
-type id() :: {id, ann(), name()}.
|
||||
@ -35,15 +38,20 @@
|
||||
-type qcon() :: {qcon, ann(), [name()]}.
|
||||
-type tvar() :: {tvar, ann(), name()}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||
| {contract_child, ann(), con(), [decl()]}
|
||||
| {contract_interface, ann(), con(), [decl()]}
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
|
||||
| {contract_child, ann(), con(), [con()], [decl()]}
|
||||
| {contract_interface, ann(), con(), [con()], [decl()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {include, ann(), {string, ann(), string()}}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
|
||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
|
||||
| {block, ann(), [decl()]}
|
||||
| {using, ann(), con(), namespace_alias(), namespace_parts()}
|
||||
| fundecl()
|
||||
| letfun()
|
||||
| letval(). % Only for error msgs
|
||||
@ -52,9 +60,12 @@
|
||||
|
||||
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
|
||||
|
||||
-type guard() :: expr().
|
||||
-type guarded_expr() :: {guarded, ann(), [guard()], expr()}.
|
||||
|
||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}.
|
||||
-type letpat() :: {letpat, ann(), id(), pat()}.
|
||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||
|
||||
-type letbind()
|
||||
@ -91,6 +102,7 @@
|
||||
| {contract_pubkey, ann(), binary()}
|
||||
| {oracle_pubkey, ann(), binary()}
|
||||
| {oracle_query_id, ann(), binary()}
|
||||
| {signature, ann(), binary()}
|
||||
| {string, ann(), binary()}
|
||||
| {char, ann(), integer()}.
|
||||
|
||||
@ -98,8 +110,8 @@
|
||||
|
||||
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
|
||||
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '||' | '&&' | '..'.
|
||||
-type un_op() :: '-' | '!'.
|
||||
| '||' | '&&' | '..' | 'band' | 'bor' | 'bxor' | '>>' | '<<' | '|>'.
|
||||
-type un_op() :: '-' | '!' | 'bnot'.
|
||||
|
||||
-type expr()
|
||||
:: {lam, ann(), [arg()], expr()}
|
||||
@ -119,7 +131,8 @@
|
||||
| {block, ann(), [stmt()]}
|
||||
| {op(), ann()}
|
||||
| id() | qid() | con() | qcon()
|
||||
| constant().
|
||||
| constant()
|
||||
| letpat().
|
||||
|
||||
-type record_or_map() :: record | map | record_or_map_error.
|
||||
|
||||
@ -140,7 +153,7 @@
|
||||
-type stmt() :: letbind()
|
||||
| expr().
|
||||
|
||||
-type alt() :: {'case', ann(), pat(), expr()}.
|
||||
-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.
|
||||
|
||||
-type lvalue() :: nonempty_list(elim()).
|
||||
|
||||
@ -153,6 +166,7 @@
|
||||
| {list, ann(), [pat()]}
|
||||
| {typed, ann(), pat(), type()}
|
||||
| {record, ann(), [field(pat())]}
|
||||
| letpat()
|
||||
| constant()
|
||||
| con()
|
||||
| id().
|
@ -1,12 +1,14 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Sophia syntax utilities.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_syntax_utils).
|
||||
-module(so_syntax_utils).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([used_ids/1, used_types/2, used/1]).
|
||||
-export([used_ids/1, used_ids/2, used_types/2, used/1]).
|
||||
|
||||
-record(alg, {zero, plus, scoped}).
|
||||
|
||||
@ -17,25 +19,27 @@
|
||||
-type kind() :: decl | type | bind_type | expr | bind_expr.
|
||||
|
||||
-spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A
|
||||
when E :: aeso_syntax:decl()
|
||||
| aeso_syntax:typedef()
|
||||
| aeso_syntax:field_t()
|
||||
| aeso_syntax:constructor_t()
|
||||
| aeso_syntax:type()
|
||||
| aeso_syntax:expr()
|
||||
| aeso_syntax:pat()
|
||||
| aeso_syntax:arg()
|
||||
| aeso_syntax:alt()
|
||||
| aeso_syntax:elim()
|
||||
| aeso_syntax:arg_expr()
|
||||
| aeso_syntax:field(aeso_syntax:expr())
|
||||
| aeso_syntax:stmt().
|
||||
when E :: so_syntax:decl()
|
||||
| so_syntax:typedef()
|
||||
| so_syntax:field_t()
|
||||
| so_syntax:constructor_t()
|
||||
| so_syntax:type()
|
||||
| so_syntax:expr()
|
||||
| so_syntax:pat()
|
||||
| so_syntax:arg()
|
||||
| so_syntax:alt()
|
||||
| so_syntax:elim()
|
||||
| so_syntax:arg_expr()
|
||||
| so_syntax:field(so_syntax:expr())
|
||||
| so_syntax:stmt().
|
||||
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
ExprKind = if K == bind_expr -> bind_expr; true -> expr end,
|
||||
TypeKind = if K == bind_type -> bind_type; true -> type end,
|
||||
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
|
||||
Same = fun(A) -> fold(Alg, Fun, K, A) end,
|
||||
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
|
||||
Type = fun(T) -> fold(Alg, Fun, type, T) end,
|
||||
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
|
||||
Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end,
|
||||
Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end,
|
||||
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
||||
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
||||
Top = Fun(K, X),
|
||||
@ -48,7 +52,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
{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])]);
|
||||
{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);
|
||||
@ -88,13 +92,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);
|
||||
@ -106,8 +112,16 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
|
||||
%% Name dependencies
|
||||
|
||||
%% Used ids, top level
|
||||
used_ids(E) ->
|
||||
[ X || {{term, [X]}, _} <- used(E) ].
|
||||
used_ids([], E).
|
||||
|
||||
%% Used ids, top level or in (current) namespace
|
||||
used_ids(Ns, E) ->
|
||||
[ lists:last(Xs) || {{term, Xs}, _} <- used(E), in_ns(Xs, Ns) ].
|
||||
|
||||
in_ns([_], _) -> true;
|
||||
in_ns(Xs, Ns) -> lists:droplast(Xs) == Ns.
|
||||
|
||||
used_types([Top] = _CurrentNS, T) ->
|
||||
F = fun({{type, [X]}, _}) -> [X];
|
||||
@ -120,7 +134,7 @@ used_types([Top] = _CurrentNS, T) ->
|
||||
| {type, [string()]}
|
||||
| {namespace, [string()]}.
|
||||
|
||||
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
|
||||
-spec entity_alg() -> alg(#{entity() => so_syntax:ann()}).
|
||||
entity_alg() ->
|
||||
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
||||
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
||||
@ -135,7 +149,7 @@ entity_alg() ->
|
||||
, plus = fun maps:merge/2
|
||||
, scoped = Scoped }.
|
||||
|
||||
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
|
||||
-spec used(_) -> [{entity(), so_syntax:ann()}].
|
||||
used(D) ->
|
||||
Kind = fun(expr) -> term;
|
||||
(bind_expr) -> bound_term;
|
||||
@ -153,4 +167,3 @@ used(D) ->
|
||||
(_, _) -> #{}
|
||||
end, decl, D)),
|
||||
lists:filter(NotBound, Xs).
|
||||
|
@ -1,15 +1,29 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc
|
||||
%%% Sophia utility functions.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_utils).
|
||||
-module(so_utils).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([scc/1]).
|
||||
-export([scc/1, canonical_dir/1]).
|
||||
|
||||
-export_type([graph/1]).
|
||||
|
||||
%% -- Simplistic canonical directory
|
||||
%% Note: no attempts to be 100% complete
|
||||
|
||||
canonical_dir(Dir) ->
|
||||
{ok, Cwd} = file:get_cwd(),
|
||||
AbsName = filename:absname(Dir),
|
||||
RelAbsName = filename:join(tl(filename:split(AbsName))),
|
||||
case filelib:safe_relative_path(RelAbsName, Cwd) of
|
||||
unsafe -> AbsName;
|
||||
Simplified -> filename:absname(Simplified, "")
|
||||
end.
|
||||
|
||||
%% -- Topological sort
|
||||
|
||||
-type graph(Node) :: #{Node => [Node]}. %% List of incoming edges (dependencies).
|
194
src/so_vm_decode.erl
Normal file
194
src/so_vm_decode.erl
Normal file
@ -0,0 +1,194 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2025, QPQ AG
|
||||
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||
%%% @doc Decoding fate data to AST
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(so_vm_decode).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-export([ from_fate/2 ]).
|
||||
|
||||
-include_lib("gmbytecode/include/gmb_fate_data.hrl").
|
||||
|
||||
-spec from_fate(so_syntax:type(), gmb_fate_data:fate_type()) -> so_syntax:expr().
|
||||
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
|
||||
from_fate({id, _, "signature"}, ?FATE_BYTES(Bin)) -> {signature, [], Bin};
|
||||
from_fate({id, _, "hash"}, ?FATE_BYTES(Bin)) -> {bytes, [], Bin};
|
||||
from_fate({id, _, "unit"}, ?FATE_UNIT) -> {tuple, [], []};
|
||||
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
|
||||
from_fate({bytes_t, _, any}, ?FATE_BYTES(Bin)) -> make_any_bytes(Bin);
|
||||
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
|
||||
from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
|
||||
from_fate({id, _, "int"}, N) when is_integer(N) ->
|
||||
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
|
||||
true -> {int, [], N} end;
|
||||
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
|
||||
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
|
||||
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
|
||||
{list, [], [from_fate(Type, X) || X <- List]};
|
||||
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||
case Val of
|
||||
{variant, [0, 1], 0, {}} -> {con, [], "None"};
|
||||
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
|
||||
end;
|
||||
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
|
||||
{tuple, [], []};
|
||||
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)}
|
||||
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
|
||||
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||
when is_map(Map) ->
|
||||
{map, [], [ {from_fate(KeyType, Key),
|
||||
from_fate(ValType, Val)}
|
||||
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
|
||||
when length(Cons) > Tag ->
|
||||
ConType = lists:nth(Tag + 1, Cons),
|
||||
Arity = lists:nth(Tag + 1, Ar),
|
||||
case tuple_to_list(Args) of
|
||||
ArgList when length(ArgList) == Arity ->
|
||||
from_fate(ConType, ArgList);
|
||||
_ -> throw(cannot_translate_to_sophia)
|
||||
end;
|
||||
from_fate({constr_t, _, Con, []}, []) -> Con;
|
||||
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},
|
||||
Bts = {bytes_t, [], any},
|
||||
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)]);
|
||||
|
||||
{["AENSv2", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} ->
|
||||
App(["AENSv2","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL),
|
||||
Chk(Map(Str, Qid(["AENSv2", "pointee"])), Ptrs)]);
|
||||
|
||||
{["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 0, {Value}}} ->
|
||||
App(["AENSv2","AccountPt"], [Chk(Adr, Value)]);
|
||||
{["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 1, {Value}}} ->
|
||||
App(["AENSv2","OraclePt"], [Chk(Adr, Value)]);
|
||||
{["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 2, {Value}}} ->
|
||||
App(["AENSv2","ContractPt"], [Chk(Adr, Value)]);
|
||||
{["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 3, {Value}}} ->
|
||||
App(["AENSv2","ChannelPt"], [Chk(Adr, Value)]);
|
||||
{["AENSv2", "pointee"], {variant, [1, 1, 1, 1, 1], 4, {Value}}} ->
|
||||
App(["AENSv2","DataPt"], [Chk(Bts, Value)]);
|
||||
|
||||
{["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);
|
||||
true -> make_bits(Id("set"), Id("none"), 0, N) end.
|
||||
|
||||
make_bits(_Set, Zero, _I, 0) -> Zero;
|
||||
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
|
||||
make_bits(Set, Zero, I + 1, N div 2);
|
||||
make_bits(Set, Zero, I, N) ->
|
||||
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
|
||||
|
||||
make_any_bytes(Bin) ->
|
||||
{app, [], {qid, [], ["Bytes", "to_any_size"]}, [{bytes, [], Bin}]}.
|
32
src/so_warnings.erl
Normal file
32
src/so_warnings.erl
Normal file
@ -0,0 +1,32 @@
|
||||
-module(so_warnings).
|
||||
-vsn("9.0.0").
|
||||
|
||||
-record(warn, { pos :: so_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(so_errors:pos(0, 0), Msg).
|
||||
|
||||
new(Pos, Msg) ->
|
||||
#warn{ pos = Pos, message = Msg }.
|
||||
|
||||
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
|
||||
so_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", [so_errors:pp_pos(Pos), Msg])).
|
@ -1,14 +1,13 @@
|
||||
{application, aesophia,
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "6.0.1"},
|
||||
{application, sophia,
|
||||
[{description, "Compiler for Sophia language"},
|
||||
{vsn, "9.0.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
stdlib,
|
||||
jsx,
|
||||
syntax_tools,
|
||||
getopt,
|
||||
aebytecode,
|
||||
gmbytecode,
|
||||
eblake2
|
||||
]},
|
||||
{env,[]},
|
@ -1,254 +0,0 @@
|
||||
-module(aeso_abi_tests).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-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) ->
|
||||
Parent = self(),
|
||||
Tag = make_ref(),
|
||||
{Pid, Ref} = spawn_monitor(fun() -> Parent ! {Tag, Code()} end),
|
||||
receive
|
||||
{Tag, Res} -> erlang:demonitor(Ref, [flush]), {ok, Res};
|
||||
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
|
||||
after 100 ->
|
||||
exit(Pid, kill),
|
||||
{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 >>.
|
||||
|
||||
from_word(W) when is_integer(W) ->
|
||||
<<W:256>>;
|
||||
from_word(S) when is_list(S) ->
|
||||
Len = length(S),
|
||||
Bin = <<(list_to_binary(S))/binary, 0:(32 - Len)/unit:8>>,
|
||||
<<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),
|
||||
ok.
|
||||
|
||||
encode_decode_sophia_test() ->
|
||||
Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of
|
||||
{X, X} -> ok;
|
||||
Other -> Other
|
||||
end end,
|
||||
ok = Check("int", "42"),
|
||||
ok = Check("int", "- 42"),
|
||||
ok = Check("bool", "true"),
|
||||
ok = Check("bool", "false"),
|
||||
ok = Check("string", "\"Hello\""),
|
||||
ok = Check("string * list(int) * option(bool)",
|
||||
"(\"Hello\", [1, 2, 3], Some(true))"),
|
||||
ok = Check("variant", "Blue({[\"x\"] = 1})"),
|
||||
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
|
||||
ok.
|
||||
|
||||
to_sophia_value_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : 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, [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)),
|
||||
ok.
|
||||
|
||||
encode_calldata_neg_test() ->
|
||||
Code = [ "contract Foo =\n"
|
||||
" entrypoint x(y : 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"]),
|
||||
?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" ],
|
||||
Code2 = [ "contract Foo =\n"
|
||||
" entrypoint x(y : string) : int = 42\n" ],
|
||||
|
||||
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
|
||||
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
|
||||
|
||||
{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, [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)),
|
||||
ok.
|
||||
|
||||
|
||||
encode_decode_sophia_string(SophiaType, String) ->
|
||||
io:format("String ~p~n", [String]),
|
||||
Code = [ "contract MakeCall =\n"
|
||||
, " type arg_type = ", SophiaType, "\n"
|
||||
, " type an_alias('a) = string * 'a\n"
|
||||
, " record r = {x : an_alias(int), y : variant}\n"
|
||||
, " 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]),
|
||||
Data = encode(Arg),
|
||||
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
|
||||
{ok, Sophia} ->
|
||||
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
|
||||
{error, Err} ->
|
||||
io:format("~s\n", [Err]),
|
||||
{error, Err}
|
||||
end;
|
||||
{error, Err} ->
|
||||
io:format("~s\n", [Err]),
|
||||
{error, Err}
|
||||
end.
|
||||
|
||||
calldata_test() ->
|
||||
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
|
||||
Map = #{ <<"a">> => 4 },
|
||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 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] =
|
||||
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)]]),
|
||||
|
||||
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_init_test() ->
|
||||
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
|
||||
|
||||
Code = parameterized_contract("foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
|
||||
|
||||
calldata_indent_test() ->
|
||||
Test = fun(Extra) ->
|
||||
Code = parameterized_contract(Extra, "foo", ["int"]),
|
||||
encode_decode_calldata_(Code, "foo", ["42"], word)
|
||||
end,
|
||||
Test(" stateful entrypoint bla() = ()"),
|
||||
Test(" type x = int"),
|
||||
Test(" stateful entrypoint bla(x : int) =\n"
|
||||
" x + 1"),
|
||||
Test(" stateful entrypoint bla(x : int) : int =\n"
|
||||
" x + 1"),
|
||||
ok.
|
||||
|
||||
parameterized_contract(FunName, Types) ->
|
||||
parameterized_contract([], FunName, Types).
|
||||
|
||||
parameterized_contract(ExtraCode, FunName, Types) ->
|
||||
lists:flatten(
|
||||
["contract Remote =\n"
|
||||
" entrypoint bla : () => unit\n\n"
|
||||
"main contract Dummy =\n",
|
||||
ExtraCode, "\n",
|
||||
" type an_alias('a) = string * 'a\n"
|
||||
" record r = {x : an_alias(int), y : variant}\n"
|
||||
" datatype variant = Red | Blue(map(string, int))\n"
|
||||
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
|
||||
|
||||
oracle_test() ->
|
||||
Contract =
|
||||
"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]),
|
||||
|
||||
ok.
|
||||
|
||||
permissive_literals_fail_test() ->
|
||||
Contract =
|
||||
"contract OracleTest =\n"
|
||||
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
|
||||
" Chain.spend(o, 1000000)\n",
|
||||
{error, [Err]} =
|
||||
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
||||
?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)),
|
||||
?assertEqual(type_error, aeso_errors:type(Err)),
|
||||
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, RetVMType) ->
|
||||
{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),
|
||||
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).
|
||||
|
||||
encode_decode(T, D) ->
|
||||
?assertEqual(D, decode(T, encode(D))),
|
||||
D.
|
||||
|
||||
encode(D) ->
|
||||
aeb_heap:to_binary(D).
|
||||
|
||||
decode(T,B) ->
|
||||
{ok, D} = aeb_heap:from_binary(T, B),
|
||||
D.
|
@ -1,955 +0,0 @@
|
||||
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||
%%% @doc Test Sophia language compiler.
|
||||
%%%
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
-module(aeso_compiler_tests).
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
run_test(Test) ->
|
||||
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
|
||||
[ begin
|
||||
io:format("~s\n", [Label]),
|
||||
Fun()
|
||||
end || {Label, Fun} <- ?MODULE:TestFun() ],
|
||||
ok.
|
||||
|
||||
%% Very simply test compile the given contracts. Only basic checks
|
||||
%% are made on the output, just that it is a binary which indicates
|
||||
%% that the compilation worked.
|
||||
simple_compile_test_() ->
|
||||
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
|
||||
fun() ->
|
||||
case compile(Backend, ContractName) of
|
||||
#{byte_code := ByteCode,
|
||||
contract_source := _,
|
||||
type_info := _} when Backend == aevm ->
|
||||
?assertMatch(Code when is_binary(Code), ByteCode);
|
||||
#{fate_code := Code} when Backend == fate ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
Error -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
|
||||
not lists:member(ContractName, not_compilable_on(Backend))] ++
|
||||
[ {"Test file not found error",
|
||||
fun() ->
|
||||
{error, Errors} = aeso_compiler:file("does_not_exist.aes"),
|
||||
ExpErr = <<"File error:\ndoes_not_exist.aes: no such file or directory">>,
|
||||
check_errors([ExpErr], Errors)
|
||||
end} ] ++
|
||||
[ {"Testing error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
Errors = compile(aevm, ContractName),
|
||||
check_errors(ExpectedErrors, Errors)
|
||||
end} ||
|
||||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
|
||||
[ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
Errors = compile(Backend, ContractName),
|
||||
Expect =
|
||||
case is_binary(ExpectedError) of
|
||||
true -> [ExpectedError];
|
||||
false ->
|
||||
case proplists:get_value(Backend, ExpectedError, no_error) of
|
||||
no_error -> no_error;
|
||||
Err -> [Err]
|
||||
end
|
||||
end,
|
||||
check_errors(Expect, Errors)
|
||||
end} ||
|
||||
{ContractName, ExpectedError} <- failing_code_gen_contracts(),
|
||||
Backend <- [aevm, fate] ] ++
|
||||
[ {"Testing include with explicit files",
|
||||
fun() ->
|
||||
FileSystem = maps:from_list(
|
||||
[ begin
|
||||
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
|
||||
{File, Bin}
|
||||
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
|
||||
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
|
||||
#{byte_code := Code2} = compile(aevm, "include"),
|
||||
?assertMatch(true, Code1 == Code2)
|
||||
end} ] ++
|
||||
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend),
|
||||
fun() ->
|
||||
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"),
|
||||
#{ byte_code := DeadCode } = compile(Backend, "deadcode"),
|
||||
SizeNoDeadCode = byte_size(NoDeadCode),
|
||||
SizeDeadCode = byte_size(DeadCode),
|
||||
Delta = if Backend == aevm -> 40;
|
||||
Backend == fate -> 20 end,
|
||||
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
|
||||
ok
|
||||
end} || Backend <- [aevm, fate] ] ++
|
||||
[].
|
||||
|
||||
%% Check if all modules in the standard library compile
|
||||
stdlib_test_() ->
|
||||
{ok, Files} = file:list_dir(aeso_stdlib:stdlib_include_path()),
|
||||
[ { "Testing " ++ File ++ " from the stdlib",
|
||||
fun() ->
|
||||
String = "include \"" ++ File ++ "\"\nmain contract Test =\n entrypoint f(x) = x",
|
||||
Options = [{src_file, File}, {backend, fate}],
|
||||
case aeso_compiler:from_string(String, Options) of
|
||||
{ok, #{fate_code := Code}} ->
|
||||
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
|
||||
?assertMatch({X, X}, {Code1, Code});
|
||||
{error, Error} -> io:format("\n\n~p\n\n", [Error]), print_and_throw(Error)
|
||||
end
|
||||
end} || File <- Files,
|
||||
lists:suffix(".aes", File)
|
||||
].
|
||||
|
||||
check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
|
||||
check_errors(Expect, #{}) ->
|
||||
?assertEqual({error, Expect}, ok);
|
||||
check_errors(Expect0, Actual0) ->
|
||||
Expect = lists:sort(Expect0),
|
||||
Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ],
|
||||
case {Expect -- Actual, Actual -- Expect} of
|
||||
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
|
||||
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
|
||||
{Missing, Extra} -> ?assertEqual(Missing, Extra)
|
||||
end.
|
||||
|
||||
compile(Backend, Name) ->
|
||||
compile(Backend, Name,
|
||||
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||
|
||||
compile(Backend, Name, Options) ->
|
||||
String = aeso_test_utils:read_contract(Name),
|
||||
Options1 =
|
||||
case lists:member(Name, debug_mode_contracts()) of
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++
|
||||
[ {src_file, Name ++ ".aes"}, {backend, Backend}
|
||||
, {include, {file_system, [aeso_test_utils:contract_path()]}}
|
||||
] ++ Options,
|
||||
case aeso_compiler:from_string(String, Options1) of
|
||||
{ok, Map} -> Map;
|
||||
{error, ErrorString} when is_binary(ErrorString) -> ErrorString;
|
||||
{error, Errors} -> Errors
|
||||
end.
|
||||
|
||||
%% compilable_contracts() -> [ContractName].
|
||||
%% The currently compilable contracts.
|
||||
|
||||
compilable_contracts() ->
|
||||
["complex_types",
|
||||
"counter",
|
||||
"dutch_auction",
|
||||
"environment",
|
||||
"factorial",
|
||||
"functions",
|
||||
"fundme",
|
||||
"identity",
|
||||
"maps",
|
||||
"oracles",
|
||||
"remote_call",
|
||||
"simple",
|
||||
"simple_storage",
|
||||
"spend_test",
|
||||
"stack",
|
||||
"test",
|
||||
"builtin_bug",
|
||||
"builtin_map_get_bug",
|
||||
"lc_record_bug",
|
||||
"nodeadcode",
|
||||
"deadcode",
|
||||
"variant_types",
|
||||
"state_handling",
|
||||
"events",
|
||||
"include",
|
||||
"basic_auth",
|
||||
"basic_auth_tx",
|
||||
"bitcoin_auth",
|
||||
"address_literals",
|
||||
"bytes_equality",
|
||||
"address_chain",
|
||||
"namespace_bug",
|
||||
"bytes_to_x",
|
||||
"bytes_concat",
|
||||
"aens",
|
||||
"aens_update",
|
||||
"tuple_match",
|
||||
"cyclic_include",
|
||||
"stdlib_include",
|
||||
"double_include",
|
||||
"manual_stdlib_include",
|
||||
"list_comp",
|
||||
"payable",
|
||||
"unapplied_builtins",
|
||||
"underscore_number_literals",
|
||||
"pairing_crypto",
|
||||
"qualified_constructor",
|
||||
"let_patterns",
|
||||
"lhs_matching",
|
||||
"more_strings",
|
||||
"protected_call",
|
||||
"hermetization_turnoff",
|
||||
"multiple_contracts",
|
||||
"clone",
|
||||
"clone_simple",
|
||||
"create",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
not_compilable_on(fate) -> [];
|
||||
not_compilable_on(aevm) -> compilable_contracts().
|
||||
|
||||
debug_mode_contracts() ->
|
||||
["hermetization_turnoff"].
|
||||
|
||||
%% Contracts that should produce type errors
|
||||
|
||||
-define(Pos(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " error in '",
|
||||
(list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n").
|
||||
-define(Pos(Line, Col), ?Pos(__Kind, __File, Line, Col)).
|
||||
|
||||
-define(ERROR(Kind, Name, Errs),
|
||||
(fun() ->
|
||||
__Kind = Kind,
|
||||
__File = ??Name,
|
||||
{__File, Errs}
|
||||
end)()).
|
||||
|
||||
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
|
||||
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
||||
|
||||
failing_contracts() ->
|
||||
{ok, V} = aeso_compiler:numeric_version(),
|
||||
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
||||
%% Parse errors
|
||||
[ ?PARSE_ERROR(field_parse_error,
|
||||
[<<?Pos(5, 26)
|
||||
"Cannot use nested fields or keys in record construction: p.x">>])
|
||||
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
|
||||
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
|
||||
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
|
||||
|
||||
%% Type errors
|
||||
, ?TYPE_ERROR(name_clash,
|
||||
[<<?Pos(14, 3)
|
||||
"Duplicate definitions of abort at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 14, column 3">>,
|
||||
<<?Pos(15, 3)
|
||||
"Duplicate definitions of require at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 15, column 3">>,
|
||||
<<?Pos(11, 3)
|
||||
"Duplicate definitions of double_def at\n"
|
||||
" - line 10, column 3\n"
|
||||
" - line 11, column 3">>,
|
||||
<<?Pos(5, 3)
|
||||
"Duplicate definitions of double_proto at\n"
|
||||
" - line 4, column 3\n"
|
||||
" - line 5, column 3">>,
|
||||
<<?Pos(8, 3)
|
||||
"Duplicate definitions of proto_and_def at\n"
|
||||
" - line 7, column 3\n"
|
||||
" - line 8, column 3">>,
|
||||
<<?Pos(16, 3)
|
||||
"Duplicate definitions of put at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 16, column 3">>,
|
||||
<<?Pos(17, 3)
|
||||
"Duplicate definitions of state at\n"
|
||||
" - (builtin location)\n"
|
||||
" - line 17, column 3">>])
|
||||
, ?TYPE_ERROR(type_errors,
|
||||
[<<?Pos(17, 23)
|
||||
"Unbound variable zz at line 17, column 23">>,
|
||||
<<?Pos(26, 9)
|
||||
"Cannot unify int\n"
|
||||
" and list(int)\n"
|
||||
"when checking the application at line 26, column 9 of\n"
|
||||
" (::) : (int, list(int)) => list(int)\n"
|
||||
"to arguments\n"
|
||||
" x : int\n"
|
||||
" x : int">>,
|
||||
<<?Pos(9, 48)
|
||||
"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the assignment of the field\n"
|
||||
" x : map(string, string) (at line 9, column 48)\n"
|
||||
"to the old value __x and the new value\n"
|
||||
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
|
||||
<<?Pos(34, 47)
|
||||
"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when checking the type of the expression at line 34, column 47\n"
|
||||
" 1 : int\n"
|
||||
"against the expected type\n"
|
||||
" string">>,
|
||||
<<?Pos(34, 52)
|
||||
"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 34, column 52\n"
|
||||
" \"bla\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int">>,
|
||||
<<?Pos(32, 18)
|
||||
"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 32, column 18\n"
|
||||
" \"x\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int">>,
|
||||
<<?Pos(11, 58)
|
||||
"Cannot unify string\n"
|
||||
" and int\n"
|
||||
"when checking the type of the expression at line 11, column 58\n"
|
||||
" \"foo\" : string\n"
|
||||
"against the expected type\n"
|
||||
" int">>,
|
||||
<<?Pos(38, 13)
|
||||
"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when comparing the types of the if-branches\n"
|
||||
" - w : int (at line 38, column 13)\n"
|
||||
" - z : string (at line 39, column 10)">>,
|
||||
<<?Pos(22, 40)
|
||||
"Not a record type: string\n"
|
||||
"arising from the projection of the field y (at line 22, column 40)">>,
|
||||
<<?Pos(21, 44)
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 21, column 44)">>,
|
||||
<<?Pos(20, 40)
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 20, column 40)">>,
|
||||
<<?Pos(19, 37)
|
||||
"Not a record type: string\n"
|
||||
"arising from an assignment of the field y (at line 19, column 37)">>,
|
||||
<<?Pos(13, 27)
|
||||
"Ambiguous record type with field y (at line 13, column 27) could be one of\n"
|
||||
" - r (at line 4, column 10)\n"
|
||||
" - r' (at line 5, column 10)">>,
|
||||
<<?Pos(26, 7)
|
||||
"Repeated name x in pattern\n"
|
||||
" x :: x (at line 26, column 7)">>,
|
||||
<<?Pos(44, 14)
|
||||
"Repeated names x, y in pattern\n"
|
||||
" (x : int, y, x : string, y : bool) (at line 44, column 14)">>,
|
||||
<<?Pos(44, 39)
|
||||
"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when checking the type of the expression at line 44, column 39\n"
|
||||
" x : int\n"
|
||||
"against the expected type\n"
|
||||
" string">>,
|
||||
<<?Pos(44, 72)
|
||||
"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when checking the type of the expression at line 44, column 72\n"
|
||||
" x : int\n"
|
||||
"against the expected type\n"
|
||||
" string">>,
|
||||
<<?Pos(14, 24)
|
||||
"No record type with fields y, z (at line 14, column 24)">>,
|
||||
<<?Pos(15, 26)
|
||||
"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
|
||||
<<?Pos(15, 24)
|
||||
"Record type r2 does not have field y (at line 15, column 24)">>,
|
||||
<<?Pos(47, 5)
|
||||
"Let binding at line 47, column 5 must be followed by an expression">>,
|
||||
<<?Pos(50, 5)
|
||||
"Let binding at line 50, column 5 must be followed by an expression">>,
|
||||
<<?Pos(54, 5)
|
||||
"Let binding at line 54, column 5 must be followed by an expression">>,
|
||||
<<?Pos(58, 5)
|
||||
"Let binding at line 58, column 5 must be followed by an expression">>,
|
||||
<<?Pos(63, 5)
|
||||
"Cannot unify int\n"
|
||||
" and bool\n"
|
||||
"when checking the type of the expression at line 63, column 5\n"
|
||||
" id(n) : int\n"
|
||||
"against the expected type\n"
|
||||
" bool">>])
|
||||
, ?TYPE_ERROR(init_type_error,
|
||||
[<<?Pos(7, 3)
|
||||
"Cannot unify string\n"
|
||||
" and map(int, int)\n"
|
||||
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>])
|
||||
, ?TYPE_ERROR(missing_state_type,
|
||||
[<<?Pos(5, 3)
|
||||
"Cannot unify string\n"
|
||||
" and unit\n"
|
||||
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>])
|
||||
, ?TYPE_ERROR(missing_fields_in_record_expression,
|
||||
[<<?Pos(7, 42)
|
||||
"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>,
|
||||
<<?Pos(8, 42)
|
||||
"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>,
|
||||
<<?Pos(6, 42)
|
||||
"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>])
|
||||
, ?TYPE_ERROR(namespace_clash,
|
||||
[<<?Pos(4, 10)
|
||||
"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>])
|
||||
, ?TYPE_ERROR(bad_events,
|
||||
[<<?Pos(9, 25)
|
||||
"The indexed type string (at line 9, column 25) is not a word type">>,
|
||||
<<?Pos(10, 25)
|
||||
"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>])
|
||||
, ?TYPE_ERROR(bad_events2,
|
||||
[<<?Pos(9, 7)
|
||||
"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
|
||||
<<?Pos(10, 7)
|
||||
"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>])
|
||||
, ?TYPE_ERROR(type_clash,
|
||||
[<<?Pos(12, 42)
|
||||
"Cannot unify int\n"
|
||||
" and string\n"
|
||||
"when checking the type of the expression at line 12, column 42\n"
|
||||
" r.foo() : map(int, string)\n"
|
||||
"against the expected type\n"
|
||||
" map(string, int)">>])
|
||||
, ?TYPE_ERROR(not_toplevel_include,
|
||||
[<<?Pos(2, 11)
|
||||
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>])
|
||||
, ?TYPE_ERROR(not_toplevel_namespace,
|
||||
[<<?Pos(2, 13)
|
||||
"Nested namespaces are not allowed\nNamespace 'Foo' at line 2, column 13 not defined at top level.">>])
|
||||
, ?TYPE_ERROR(not_toplevel_contract,
|
||||
[<<?Pos(2, 12)
|
||||
"Nested contracts are not allowed\nContract 'Con' at line 2, column 12 not defined at top level.">>])
|
||||
, ?TYPE_ERROR(bad_address_literals,
|
||||
[<<?Pos(11, 5)
|
||||
"Cannot unify address\n"
|
||||
" and oracle(int, bool)\n"
|
||||
"when checking the type of the expression at line 11, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<?Pos(9, 5)
|
||||
"Cannot unify address\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 9, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<?Pos(7, 5)
|
||||
"Cannot unify address\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 7, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>,
|
||||
<<?Pos(14, 5)
|
||||
"Cannot unify oracle('a, 'b)\n"
|
||||
" and oracle_query(int, bool)\n"
|
||||
"when checking the type of the expression at line 14, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('a, 'b)\n"
|
||||
"against the expected type\n"
|
||||
" oracle_query(int, bool)">>,
|
||||
<<?Pos(16, 5)
|
||||
"Cannot unify oracle('c, 'd)\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 16, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('c, 'd)\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>,
|
||||
<<?Pos(18, 5)
|
||||
"Cannot unify oracle('e, 'f)\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 18, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('e, 'f)\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<?Pos(21, 5)
|
||||
"Cannot unify oracle_query('g, 'h)\n"
|
||||
" and oracle(int, bool)\n"
|
||||
"when checking the type of the expression at line 21, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('g, 'h)\n"
|
||||
"against the expected type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<?Pos(23, 5)
|
||||
"Cannot unify oracle_query('i, 'j)\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 23, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('i, 'j)\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>,
|
||||
<<?Pos(25, 5)
|
||||
"Cannot unify oracle_query('k, 'l)\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 25, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('k, 'l)\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<?Pos(28, 5)
|
||||
"The type address is not a contract type\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" address">>,
|
||||
<<?Pos(30, 5)
|
||||
"The type oracle(int, bool) is not a contract type\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<?Pos(32, 5)
|
||||
"The type bytes(32) is not a contract type\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" bytes(32)">>,
|
||||
<<?Pos(34, 5),
|
||||
"The type address is not a contract type\n"
|
||||
"when checking that the call to\n"
|
||||
" Address.to_contract\n"
|
||||
"has the type\n"
|
||||
" address">>])
|
||||
, ?TYPE_ERROR(stateful,
|
||||
[<<?Pos(13, 35)
|
||||
"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
|
||||
<<?Pos(14, 35)
|
||||
"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
|
||||
<<?Pos(16, 15)
|
||||
"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
|
||||
<<?Pos(20, 31)
|
||||
"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
|
||||
<<?Pos(35, 47)
|
||||
"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
|
||||
<<?Pos(48, 57)
|
||||
"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
|
||||
<<?Pos(49, 56)
|
||||
"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
|
||||
<<?Pos(52, 17)
|
||||
"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>])
|
||||
, ?TYPE_ERROR(bad_init_state_access,
|
||||
[<<?Pos(11, 5)
|
||||
"The init function should return the initial state as its result and cannot write the state,\n"
|
||||
"but it calls\n"
|
||||
" - set_state (at line 11, column 5), which calls\n"
|
||||
" - roundabout (at line 8, column 38), which calls\n"
|
||||
" - put (at line 7, column 39)">>,
|
||||
<<?Pos(12, 5)
|
||||
"The init function should return the initial state as its result and cannot read the state,\n"
|
||||
"but it calls\n"
|
||||
" - new_state (at line 12, column 5), which calls\n"
|
||||
" - state (at line 5, column 29)">>,
|
||||
<<?Pos(13, 13)
|
||||
"The init function should return the initial state as its result and cannot read the state,\n"
|
||||
"but it calls\n"
|
||||
" - state (at line 13, column 13)">>])
|
||||
, ?TYPE_ERROR(modifier_checks,
|
||||
[<<?Pos(11, 3)
|
||||
"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
|
||||
<<?Pos(3, 3)
|
||||
"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
|
||||
<<?Pos(5, 10)
|
||||
"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
|
||||
<<?Pos(12, 3)
|
||||
"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
|
||||
<<?Pos(6, 3)
|
||||
"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
|
||||
<<?Pos(10, 3)
|
||||
"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
|
||||
<<?Pos(6, 3)
|
||||
"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>])
|
||||
, ?TYPE_ERROR(list_comp_not_a_list,
|
||||
[<<?Pos(2, 36)
|
||||
"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
|
||||
])
|
||||
, ?TYPE_ERROR(list_comp_if_not_bool,
|
||||
[<<?Pos(2, 44)
|
||||
"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
|
||||
])
|
||||
, ?TYPE_ERROR(list_comp_bad_shadow,
|
||||
[<<?Pos(2, 53)
|
||||
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
|
||||
])
|
||||
, ?TYPE_ERROR(map_as_map_key,
|
||||
[<<?Pos(5, 47)
|
||||
"Invalid key type\n"
|
||||
" map(int, int)\n"
|
||||
"Map keys cannot contain other maps.">>,
|
||||
<<?Pos(6, 31)
|
||||
"Invalid key type\n"
|
||||
" list(map(int, int))\n"
|
||||
"Map keys cannot contain other maps.">>,
|
||||
<<?Pos(6, 31)
|
||||
"Invalid key type\n"
|
||||
" lm\n"
|
||||
"Map keys cannot contain other maps.">>])
|
||||
, ?TYPE_ERROR(calling_init_function,
|
||||
[<<?Pos(7, 28)
|
||||
"The 'init' function is called exclusively by the create contract transaction\n"
|
||||
"and cannot be called from the contract code.">>])
|
||||
, ?TYPE_ERROR(bad_top_level_decl,
|
||||
[<<?Pos(1, 1) "The definition of 'square' must appear inside a contract or namespace.">>])
|
||||
, ?TYPE_ERROR(missing_event_type,
|
||||
[<<?Pos(3, 5)
|
||||
"Unbound variable Chain.event at line 3, column 5\n"
|
||||
"Did you forget to define the event type?">>])
|
||||
, ?TYPE_ERROR(bad_bytes_concat,
|
||||
[<<?Pos(12, 40)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - 'g (at line 12, column 20)\n"
|
||||
" - 'h (at line 12, column 23)\n"
|
||||
"and result type\n"
|
||||
" - bytes(10) (at line 12, column 28)">>,
|
||||
<<?Pos(13, 28)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - 'd (at line 13, column 20)\n"
|
||||
" - 'e (at line 13, column 23)\n"
|
||||
"and result type\n"
|
||||
" - 'f (at line 13, column 14)">>,
|
||||
<<?Pos(15, 5)
|
||||
"Cannot unify bytes(26)\n"
|
||||
" and bytes(25)\n"
|
||||
"at line 15, column 5">>,
|
||||
<<?Pos(17, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
|
||||
" - bytes(6) (at line 16, column 24)\n"
|
||||
" - 'b (at line 16, column 34)\n"
|
||||
"and result type\n"
|
||||
" - 'c (at line 16, column 39)">>,
|
||||
<<?Pos(19, 25)
|
||||
"Cannot resolve length of byte array.">>])
|
||||
, ?TYPE_ERROR(bad_bytes_split,
|
||||
[<<?Pos(13, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - 'f (at line 12, column 20)\n"
|
||||
"and result types\n"
|
||||
" - 'e (at line 12, column 25)\n"
|
||||
" - bytes(20) (at line 12, column 29)">>,
|
||||
<<?Pos(16, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - bytes(15) (at line 15, column 24)\n"
|
||||
"and result types\n"
|
||||
" - 'c (at line 16, column 5)\n"
|
||||
" - 'd (at line 16, column 5)">>,
|
||||
<<?Pos(19, 5)
|
||||
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
|
||||
" - 'b (at line 18, column 20)\n"
|
||||
"and result types\n"
|
||||
" - bytes(20) (at line 18, column 25)\n"
|
||||
" - 'a (at line 18, column 37)">>])
|
||||
, ?TYPE_ERROR(wrong_compiler_version,
|
||||
[<<?Pos(1, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " < 1.0">>,
|
||||
<<?Pos(2, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
||||
, ?TYPE_ERROR(interface_with_defs,
|
||||
[<<?Pos(2, 3)
|
||||
"Contract interfaces cannot contain defined functions or entrypoints.\n"
|
||||
"Fix: replace the definition of 'foo' by a type signature.">>])
|
||||
, ?TYPE_ERROR(contract_as_namespace,
|
||||
[<<?Pos(5, 28)
|
||||
"Invalid call to contract entrypoint 'Foo.foo'.\n"
|
||||
"It must be called as 'c.foo' for some c : Foo.">>])
|
||||
, ?TYPE_ERROR(toplevel_let,
|
||||
[<<?Pos(2, 7)
|
||||
"Toplevel \"let\" definitions are not supported\n"
|
||||
"Value this_is_illegal at line 2, column 7 could be replaced by 0-argument function">>])
|
||||
, ?TYPE_ERROR(empty_typedecl,
|
||||
[<<?Pos(2, 8)
|
||||
"Empty type declarations are not supported\n"
|
||||
"Type t at line 2, column 8 lacks a definition">>])
|
||||
, ?TYPE_ERROR(higher_kinded_type,
|
||||
[<<?Pos(2, 35)
|
||||
"Type 'm is a higher kinded type variable\n"
|
||||
"(takes another type as an argument)">>])
|
||||
, ?TYPE_ERROR(bad_arity,
|
||||
[<<?Pos(3, 20)
|
||||
"Arity for id doesn't match. Expected 1, got 0">>,
|
||||
<<?Pos(3, 25)
|
||||
"Cannot unify int\n"
|
||||
" and id\n"
|
||||
"when checking the type of the expression at line 3, column 25\n"
|
||||
" 123 : int\n"
|
||||
"against the expected type\n"
|
||||
" id">>,
|
||||
<<?Pos(4, 20)
|
||||
"Arity for id doesn't match. Expected 1, got 2">>,
|
||||
<<?Pos(4, 35)
|
||||
"Cannot unify int\n"
|
||||
" and id(int, int)\n"
|
||||
"when checking the type of the expression at line 4, column 35\n"
|
||||
" 123 : int\n"
|
||||
"against the expected type\n"
|
||||
" id(int, int)">>])
|
||||
, ?TYPE_ERROR(bad_unnamed_map_update_default,
|
||||
[<<?Pos(4, 17)
|
||||
"Invalid map update with default">>])
|
||||
, ?TYPE_ERROR(non_functional_entrypoint,
|
||||
[<<?Pos(2, 14)
|
||||
"f at line 2, column 14 was declared with an invalid type int.\n"
|
||||
"Entrypoints and functions must have functional types">>])
|
||||
, ?TYPE_ERROR(bad_records,
|
||||
[<<?Pos(3, 16)
|
||||
"Mixed record fields and map keys in\n"
|
||||
" {x = 0, [0] = 1}">>,
|
||||
<<?Pos(4, 6)
|
||||
"Mixed record fields and map keys in\n"
|
||||
" r {x = 0, [0] = 1}">>,
|
||||
<<?Pos(5, 6)
|
||||
"Empty record/map update\n"
|
||||
" r {}">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_protected_call,
|
||||
[<<?Pos(6, 22)
|
||||
"Invalid 'protected' argument\n"
|
||||
" (0 : int) == (1 : int) : bool\n"
|
||||
"It must be either 'true' or 'false'.">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_function_block,
|
||||
[<<?Pos(4, 5)
|
||||
"Mismatch in the function block. Expected implementation/type declaration of g function">>,
|
||||
<<?Pos(5, 5)
|
||||
"Mismatch in the function block. Expected implementation/type declaration of g function">>
|
||||
])
|
||||
, ?TYPE_ERROR(just_an_empty_file,
|
||||
[<<?Pos(0, 0)
|
||||
"Empty contract">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_number_of_args,
|
||||
[<<?Pos(3, 39)
|
||||
"Cannot unify () => unit\n"
|
||||
" and (int) => 'a\n",
|
||||
"when checking the application at line 3, column 39 of\n"
|
||||
" f : () => unit\n"
|
||||
"to arguments\n"
|
||||
" 1 : int">>,
|
||||
<<?Pos(4, 20)
|
||||
"Cannot unify (int, string) => 'e\n"
|
||||
" and (int) => 'd\n"
|
||||
"when checking the application at line 4, column 20 of\n"
|
||||
" g : (int, string) => 'e\n"
|
||||
"to arguments\n"
|
||||
" 1 : int">>,
|
||||
<<?Pos(5, 20)
|
||||
"Cannot unify (int, string) => 'c\n"
|
||||
" and (string) => 'b\n"
|
||||
"when checking the application at line 5, column 20 of\n"
|
||||
" g : (int, string) => 'c\nto arguments\n"
|
||||
" \"Litwo, ojczyzno moja\" : string">>
|
||||
])
|
||||
, ?TYPE_ERROR(bad_state,
|
||||
[<<?Pos(4, 16)
|
||||
"Conflicting updates for field 'foo'">>])
|
||||
, ?TYPE_ERROR(factories_type_errors,
|
||||
[<<?Pos(10,18)
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify (gas : int, value : int, protected : bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool, int, bool) => 'b\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
|
||||
<<?Pos(12,37)
|
||||
"Cannot unify int\n and bool\n"
|
||||
"when checking named argument\n gas : int\nagainst inferred type\n bool">>,
|
||||
<<?Pos(13,18),
|
||||
"Kaboom is not implemented.\n"
|
||||
"when resolving arguments of variadic function\n Chain.create">>,
|
||||
<<?Pos(18,18)
|
||||
"Cannot unify (gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)\n and (gas : int, value : int, protected : bool) => 'a\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool, int, bool) =>\n if(protected, option(void), void) (at line 18, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool) => 'a">>,
|
||||
<<?Pos(19,42),
|
||||
"Named argument protected (at line 19, column 42) is not one of the expected named arguments\n - value : int">>,
|
||||
<<?Pos(20,42),
|
||||
"Cannot unify int\n and bool\n"
|
||||
"when checking named argument\n value : int\nagainst inferred type\n bool">>
|
||||
])
|
||||
, ?TYPE_ERROR(ambiguous_main,
|
||||
[<<?Pos(1,1)
|
||||
"Could not deduce the main contract. You can point it out manually with the `main` keyword.">>
|
||||
])
|
||||
, ?TYPE_ERROR(no_main_contract,
|
||||
[<<?Pos(0,0)
|
||||
"No contract defined.">>
|
||||
])
|
||||
, ?TYPE_ERROR(multiple_main_contracts,
|
||||
[<<?Pos(1,6)
|
||||
"Only one main contract can be defined.">>
|
||||
])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
|
||||
|
||||
-define(SAME(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
|
||||
-define(AEVM(File, Line, Col, Err), {?Path(File), [{aevm, ?Msg(File, Line, Col, Err)}]}).
|
||||
-define(FATE(File, Line, Col, Err), {?Path(File), [{fate, ?Msg(File, Line, Col, Err)}]}).
|
||||
-define(BOTH(File, Line, Col, ErrAEVM, ErrFATE),
|
||||
{?Path(File), [{aevm, ?Msg(File, Line, Col, ErrAEVM)},
|
||||
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
|
||||
|
||||
failing_code_gen_contracts() ->
|
||||
[ ?SAME(missing_definition, 2, 14,
|
||||
"Missing definition of function 'foo'.")
|
||||
, ?AEVM(polymorphic_entrypoint, 2, 17,
|
||||
"The argument\n"
|
||||
" x : 'a\n"
|
||||
"of entrypoint 'id' has a polymorphic (contains type variables) type.\n"
|
||||
"Use the FATE backend if you want polymorphic entrypoints.")
|
||||
, ?AEVM(polymorphic_entrypoint_return, 2, 3,
|
||||
"The return type\n"
|
||||
" 'a\n"
|
||||
"of entrypoint 'fail' is polymorphic (contains type variables).\n"
|
||||
"Use the FATE backend if you want polymorphic entrypoints.")
|
||||
, ?SAME(higher_order_entrypoint, 2, 20,
|
||||
"The argument\n"
|
||||
" f : (int) => int\n"
|
||||
"of entrypoint 'apply' has a higher-order (contains function types) type.")
|
||||
, ?SAME(higher_order_entrypoint_return, 2, 3,
|
||||
"The return type\n"
|
||||
" (int) => int\n"
|
||||
"of entrypoint 'add' is higher-order (contains function types).")
|
||||
, ?SAME(missing_init_function, 1, 10,
|
||||
"Missing init function for the contract 'MissingInitFunction'.\n"
|
||||
"The 'init' function can only be omitted if the state type is 'unit'.")
|
||||
, ?SAME(parameterised_state, 3, 8,
|
||||
"The state type cannot be parameterized.")
|
||||
, ?SAME(parameterised_event, 3, 12,
|
||||
"The event type cannot be parameterized.")
|
||||
, ?SAME(polymorphic_aens_resolve, 4, 5,
|
||||
"Invalid return type of AENS.resolve:\n"
|
||||
" 'a\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
||||
, ?SAME(bad_aens_resolve, 6, 5,
|
||||
"Invalid return type of AENS.resolve:\n"
|
||||
" list(int)\n"
|
||||
"It must be a string or a pubkey type (address, oracle, etc).")
|
||||
, ?AEVM(polymorphic_compare, 4, 5,
|
||||
"Cannot compare values of type\n"
|
||||
" 'a\n"
|
||||
"The AEVM only supports '==' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"- type string\n"
|
||||
"- tuple or record of word type\n"
|
||||
"Use FATE if you need to compare arbitrary types.")
|
||||
, ?AEVM(complex_compare, 4, 5,
|
||||
"Cannot compare values of type\n"
|
||||
" (string * int)\n"
|
||||
"The AEVM only supports '!=' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"- type string\n"
|
||||
"- tuple or record of word type\n"
|
||||
"Use FATE if you need to compare arbitrary types.")
|
||||
, ?AEVM(complex_compare_leq, 4, 5,
|
||||
"Cannot compare values of type\n"
|
||||
" (int * int)\n"
|
||||
"The AEVM only supports '=<' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"Use FATE if you need to compare arbitrary types.")
|
||||
, ?AEVM(higher_order_compare, 4, 5,
|
||||
"Cannot compare values of type\n"
|
||||
" (int) => int\n"
|
||||
"The AEVM only supports '<' on values of\n"
|
||||
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
|
||||
"Use FATE if you need to compare arbitrary types.")
|
||||
, ?AEVM(unapplied_contract_call, 6, 19,
|
||||
"The AEVM does not support unapplied contract call to\n"
|
||||
" r : Remote\n"
|
||||
"Use FATE if you need this.")
|
||||
, ?AEVM(unapplied_named_arg_builtin, 4, 15,
|
||||
"The AEVM does not support unapplied use of Oracle.register.\n"
|
||||
"Use FATE if you need this.")
|
||||
, ?AEVM(polymorphic_map_keys, 4, 34,
|
||||
"Invalid map key type\n"
|
||||
" 'a\n"
|
||||
"Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.")
|
||||
, ?AEVM(higher_order_map_keys, 4, 42,
|
||||
"Invalid map key type\n"
|
||||
" (int) => int\n"
|
||||
"Map keys cannot be higher-order.")
|
||||
, ?SAME(polymorphic_query_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle('a, 'b)\n"
|
||||
"The query type must not be polymorphic (contain type variables).")
|
||||
, ?SAME(polymorphic_response_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle(string, 'r)\n"
|
||||
"The response type must not be polymorphic (contain type variables).")
|
||||
, ?SAME(higher_order_query_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle((int) => int, string)\n"
|
||||
"The query type must not be higher-order (contain function types).")
|
||||
, ?SAME(higher_order_response_type, 3, 5,
|
||||
"Invalid oracle type\n"
|
||||
" oracle(string, (int) => int)\n"
|
||||
"The response type must not be higher-order (contain function types).")
|
||||
, ?AEVM(higher_order_state, 3, 3,
|
||||
"Invalid state type\n"
|
||||
" {f : (int) => int}\n"
|
||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
||||
, ?FATE(child_with_decls, 2, 14,
|
||||
"Missing definition of function 'f'.")
|
||||
].
|
||||
|
||||
validation_test_() ->
|
||||
[{"Validation fail: " ++ C1 ++ " /= " ++ C2,
|
||||
fun() ->
|
||||
Actual = case validate(C1, C2) of
|
||||
{error, Errs} -> Errs;
|
||||
ok -> #{}
|
||||
end,
|
||||
check_errors(Expect, Actual)
|
||||
end} || {C1, C2, Expect} <- validation_fails()] ++
|
||||
[{"Validation of " ++ C,
|
||||
fun() ->
|
||||
?assertEqual(ok, validate(C, C))
|
||||
end} || C <- compilable_contracts()].
|
||||
|
||||
validation_fails() ->
|
||||
[{"deadcode", "nodeadcode",
|
||||
[<<"Data error:\n"
|
||||
"Byte code does not match source code.\n"
|
||||
"- Functions in the source code but not in the byte code:\n"
|
||||
" .MyList.map2">>]},
|
||||
{"validation_test1", "validation_test2",
|
||||
[<<"Data error:\n"
|
||||
"Byte code does not match source code.\n"
|
||||
"- The implementation of the function code_fail is different.\n"
|
||||
"- The attributes of the function attr_fail differ:\n"
|
||||
" Byte code: payable\n"
|
||||
" Source code: \n"
|
||||
"- The type of the function type_fail differs:\n"
|
||||
" Byte code: integer => integer\n"
|
||||
" Source code: {tvar,0} => {tvar,0}">>]},
|
||||
{"validation_test1", "validation_test3",
|
||||
[<<"Data error:\n"
|
||||
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||
|
||||
validate(Contract1, Contract2) ->
|
||||
case compile(fate, Contract1) of
|
||||
ByteCode = #{ fate_code := FCode } ->
|
||||
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||
Source = aeso_test_utils:read_contract(Contract2),
|
||||
aeso_compiler:validate_byte_code(
|
||||
ByteCode#{ byte_code := FCode1 }, Source,
|
||||
case lists:member(Contract2, debug_mode_contracts()) of
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++
|
||||
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]);
|
||||
Error -> print_and_throw(Error)
|
||||
end.
|
||||
|
||||
print_and_throw(Err) ->
|
||||
case Err of
|
||||
ErrBin when is_binary(ErrBin) ->
|
||||
io:format("\n~s", [ErrBin]),
|
||||
error(ErrBin);
|
||||
Errors ->
|
||||
io:format("Compilation error:\n~s", [string:join([aeso_errors:pp(E) || E <- Errors], "\n\n")]),
|
||||
error(compilation_error)
|
||||
end.
|
@ -1,22 +0,0 @@
|
||||
-module(aeso_eunit_SUITE).
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("common_test/include/ct.hrl").
|
||||
|
||||
all() ->
|
||||
[{group, eunit}].
|
||||
|
||||
groups() ->
|
||||
[{eunit, [], [ aeso_scan_tests
|
||||
, aeso_parser_tests
|
||||
, aeso_compiler_tests
|
||||
, aeso_abi_tests
|
||||
, aeso_aci_tests
|
||||
]}].
|
||||
|
||||
aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests).
|
||||
aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests).
|
||||
aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests).
|
||||
aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests).
|
||||
aeso_aci_tests(_Config) -> ok = eunit:test(aeso_aci_tests).
|
@ -1,15 +0,0 @@
|
||||
|
||||
## Requires ocaml >= 4.02, < 4.06
|
||||
## and reason-3.0.0 (opam install reason).
|
||||
|
||||
default : voting_test
|
||||
|
||||
%.ml : %.re
|
||||
refmt -p ml $< > $@
|
||||
|
||||
|
||||
voting_test : rte.ml voting.ml voting_test.ml
|
||||
ocamlopt -o $@ $^
|
||||
|
||||
clean :
|
||||
rm -f *.cmi *.cmx *.ml *.o voting_test
|
@ -1,31 +0,0 @@
|
||||
// A simple test of the abort built-in function.
|
||||
|
||||
contract AbortTest =
|
||||
|
||||
record state = { value : int }
|
||||
|
||||
public function init(v : int) =
|
||||
{ value = v }
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) : unit =
|
||||
put_value(v)
|
||||
revert_abort(s)
|
||||
|
||||
// Accessing the value
|
||||
public function get_value() = state.value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_values() : list(int) = [state.value]
|
||||
public function put_values(v : int) = put(state{value = v})
|
||||
|
||||
// Some basic statistics
|
||||
public function get_stats(acct : address) =
|
||||
( Contract.balance, Chain.balance(acct) )
|
||||
|
||||
// Abort functions.
|
||||
private function revert_abort(s : string) =
|
||||
abort(s)
|
||||
|
||||
// This is still legal but will be stripped out.
|
||||
// TODO: This function confuses the type inference, so it cannot be present.
|
||||
//private function abort(s : string) = 42
|
@ -1,27 +0,0 @@
|
||||
contract Interface =
|
||||
function do_abort : (int, string) => unit
|
||||
function get_value : () => int
|
||||
function put_value : (int) => unit
|
||||
function get_values : () => list(int)
|
||||
function put_values : (int) => unit
|
||||
|
||||
contract AbortTestInt =
|
||||
|
||||
record state = {r : Interface, value : int}
|
||||
|
||||
public function init(r : Interface, value : int) =
|
||||
{r = r, value = value}
|
||||
|
||||
// Aborting
|
||||
public function do_abort(v : int, s : string) =
|
||||
put_value(v)
|
||||
state.r.do_abort(v + 100, s)
|
||||
|
||||
// Accessing the value
|
||||
public function put_value(v : int) = put(state{value = v})
|
||||
public function get_value() = state.value
|
||||
public function get_values() : list(int) =
|
||||
state.value :: state.r.get_values()
|
||||
public function put_values(v : int) =
|
||||
put_value(v)
|
||||
state.r.put_values(v + 1000)
|
@ -2,27 +2,12 @@ contract interface Remote =
|
||||
entrypoint main_fun : (int) => unit
|
||||
|
||||
contract AddrChain =
|
||||
type o_type = oracle(string, map(string, int))
|
||||
type oq_type = oracle_query(string, map(string, int))
|
||||
|
||||
entrypoint is_o(a : address) =
|
||||
Address.is_oracle(a)
|
||||
|
||||
entrypoint is_c(a : address) =
|
||||
Address.is_contract(a)
|
||||
|
||||
// entrypoint get_o(a : address) : option(o_type) =
|
||||
// Address.get_oracle(a)
|
||||
|
||||
// entrypoint get_c(a : address) : option(Remote) =
|
||||
// Address.get_contract(a)
|
||||
|
||||
entrypoint check_o(o : o_type) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint check_oq(o : o_type, oq : oq_type) =
|
||||
Oracle.check_query(o, oq)
|
||||
|
||||
// entrypoint h_to_i(h : hash) : int =
|
||||
// Hash.to_int(h)
|
||||
|
||||
|
@ -5,10 +5,6 @@ contract interface Remote =
|
||||
contract AddressLiterals =
|
||||
entrypoint addr() : address =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
entrypoint oracle() : oracle(int, bool) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
entrypoint query() : oracle_query(int, bool) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
entrypoint contr() : Remote =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr_addr() : Remote =
|
||||
|
@ -1,71 +1,76 @@
|
||||
contract C = entrypoint init() = ()
|
||||
|
||||
// AENS tests
|
||||
contract AENSTest =
|
||||
main contract AENSTest =
|
||||
|
||||
// Name resolution
|
||||
|
||||
stateful entrypoint resolve_word(name : string, key : string) : option(address) =
|
||||
AENS.resolve(name, key)
|
||||
AENSv2.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
|
||||
AENS.resolve(name, key)
|
||||
AENSv2.resolve(name, key)
|
||||
|
||||
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
|
||||
AENSv2.resolve(name, key)
|
||||
|
||||
// Transactions
|
||||
|
||||
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash) : unit = // Commitment hash
|
||||
AENS.preclaim(addr, chash)
|
||||
AENSv2.preclaim(addr, chash)
|
||||
|
||||
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
|
||||
chash : hash, // Commitment hash
|
||||
sign : signature) : unit = // Signed by addr (if not Contract.address)
|
||||
AENS.preclaim(addr, chash, signature = sign)
|
||||
AENSv2.preclaim(addr, chash, signature = sign)
|
||||
|
||||
stateful entrypoint claim(addr : address,
|
||||
name : string,
|
||||
salt : int,
|
||||
name_fee : int) : unit =
|
||||
AENS.claim(addr, name, salt, name_fee)
|
||||
AENSv2.claim(addr, name, salt, name_fee)
|
||||
|
||||
stateful entrypoint signedClaim(addr : address,
|
||||
name : string,
|
||||
salt : int,
|
||||
name_fee : int,
|
||||
sign : signature) : unit =
|
||||
AENS.claim(addr, name, salt, name_fee, signature = sign)
|
||||
AENSv2.claim(addr, name, salt, name_fee, signature = sign)
|
||||
|
||||
|
||||
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)
|
||||
pointers : option(map(string, AENSv2.pointee))) : unit =
|
||||
AENSv2.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)),
|
||||
pointers : option(map(string, AENSv2.pointee)),
|
||||
sign : signature) : unit =
|
||||
AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign)
|
||||
AENSv2.update(owner, name, ttl, client_ttl, pointers, signature = sign)
|
||||
|
||||
|
||||
stateful entrypoint transfer(owner : address,
|
||||
new_owner : address,
|
||||
name : string) : unit =
|
||||
AENS.transfer(owner, new_owner, name)
|
||||
AENSv2.transfer(owner, new_owner, name)
|
||||
|
||||
stateful entrypoint signedTransfer(owner : address,
|
||||
new_owner : address,
|
||||
name : string,
|
||||
sign : signature) : unit =
|
||||
AENS.transfer(owner, new_owner, name, signature = sign)
|
||||
AENSv2.transfer(owner, new_owner, name, signature = sign)
|
||||
|
||||
stateful entrypoint revoke(owner : address,
|
||||
name : string) : unit =
|
||||
AENS.revoke(owner, name)
|
||||
AENSv2.revoke(owner, name)
|
||||
|
||||
stateful entrypoint signedRevoke(owner : address,
|
||||
name : string,
|
||||
sign : signature) : unit =
|
||||
AENS.revoke(owner, name, signature = sign)
|
||||
AENSv2.revoke(owner, name, signature = sign)
|
||||
|
@ -1,17 +1,29 @@
|
||||
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 }))
|
||||
include "Option.aes"
|
||||
include "String.aes"
|
||||
include "AENSCompat.aes"
|
||||
contract interface OldAENSContract =
|
||||
entrypoint set : (string, string, AENS.pointee) => unit
|
||||
entrypoint lookup : (string, string) => AENS.pointee
|
||||
|
||||
main contract AENSUpdate =
|
||||
stateful entrypoint update_name(owner : address, name : string, b : bytes(2)) =
|
||||
let p1 : AENSv2.pointee = AENSv2.AccountPt(Call.caller)
|
||||
let p2 : AENSv2.pointee = AENSv2.OraclePt(Call.caller)
|
||||
let p3 : AENSv2.pointee = AENSv2.ContractPt(Call.caller)
|
||||
let p4 : AENSv2.pointee = AENSv2.ChannelPt(Call.caller)
|
||||
let p5 : AENSv2.pointee = AENSv2.DataPt(String.to_bytes("any something will do"))
|
||||
let p6 : AENSv2.pointee = AENSv2.DataPt(Int.to_bytes(1345, 4))
|
||||
AENSv2.update(owner, name, None, None,
|
||||
Some({ ["account_pubkey"] = p1,
|
||||
["contract_pubkey"] = p3, ["misc"] = p4, ["data"] = p5, ["data2"] = p6 }))
|
||||
|
||||
stateful entrypoint old_interaction(c : OldAENSContract, owner : address, name : string) =
|
||||
let p : AENS.pointee = c.lookup(name, "key1")
|
||||
AENSv2.update(owner, name, None, None, Some({ ["key1"] = AENSCompat.pointee_to_V2(p) }))
|
||||
switch(AENSv2.lookup(name))
|
||||
Some(AENSv2.Name(_, _, pt_map)) =>
|
||||
c.set(name, "key2", Option.force(AENSCompat.pointee_from_V2(pt_map["key1"])))
|
||||
|
||||
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)
|
||||
|
||||
switch(AENSv2.lookup(name))
|
||||
Some(AENSv2.Name(_, FixedTTL(ttl), _)) => ttl
|
||||
|
@ -6,6 +6,7 @@
|
||||
namespace Ns =
|
||||
datatype d('a) = D | S(int) | M('a, list('a), int)
|
||||
private function fff() = 123
|
||||
let const = 1
|
||||
|
||||
stateful entrypoint
|
||||
f (1, x) = (_) => x
|
||||
@ -33,6 +34,8 @@ contract AllSyntax =
|
||||
|
||||
type state = shakespeare(int)
|
||||
|
||||
let cc = "str"
|
||||
|
||||
entrypoint init() = {
|
||||
johann = 1000,
|
||||
wolfgang = -10,
|
||||
@ -80,3 +83,4 @@ contract AllSyntax =
|
||||
let sh : shakespeare(shakespeare(int)) =
|
||||
{wolfgang = state}
|
||||
sh{wolfgang.wolfgang = sh.wolfgang} // comment
|
||||
exit("hope you had fun reading this")
|
||||
|
4
test/contracts/assign_pattern_to_pattern.aes
Normal file
4
test/contracts/assign_pattern_to_pattern.aes
Normal file
@ -0,0 +1,4 @@
|
||||
contract AssignPatternToPattern =
|
||||
entrypoint f() =
|
||||
let x::(t::z = y) = [1, 2, 3]
|
||||
(x + t)::y
|
16
test/contracts/assign_patterns.aes
Normal file
16
test/contracts/assign_patterns.aes
Normal file
@ -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
|
@ -7,27 +7,9 @@ contract AddressLiterals =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
entrypoint addr2() : Remote =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
entrypoint addr3() : oracle(int, bool) =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
|
||||
entrypoint oracle1() : oracle_query(int, bool) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
entrypoint oracle2() : bytes(32) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
entrypoint oracle3() : Remote =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
|
||||
entrypoint query1() : oracle(int, bool) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
entrypoint query2() : bytes(32) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
entrypoint query3() : Remote =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
|
||||
entrypoint contr1() : address =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr2() : oracle(int, bool) =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr3() : bytes(32) =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr4() : address =
|
||||
|
@ -3,7 +3,7 @@ contract BadAENSresolve =
|
||||
type t('a) = option(list('a))
|
||||
|
||||
function fail() : t(int) =
|
||||
AENS.resolve("foo.aet", "whatever")
|
||||
AENSv2.resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main_fun() = ()
|
||||
|
9
test/contracts/bad_aens_resolve_using.aes
Normal file
9
test/contracts/bad_aens_resolve_using.aes
Normal file
@ -0,0 +1,9 @@
|
||||
contract BadAENSresolve =
|
||||
using AENSv2
|
||||
|
||||
type t('a) = option(list('a))
|
||||
|
||||
function fail() : t(int) =
|
||||
resolve("foo.aet", "whatever")
|
||||
|
||||
entrypoint main_fun() = ()
|
5
test/contracts/bad_bytes_to_x.aes
Normal file
5
test/contracts/bad_bytes_to_x.aes
Normal file
@ -0,0 +1,5 @@
|
||||
// include "String.aes"
|
||||
contract BytesToX =
|
||||
entrypoint fail1(b : bytes()) = Bytes.to_fixed_size(b)
|
||||
entrypoint fail2(b : bytes(4)) = Bytes.to_fixed_size(b)
|
||||
entrypoint fail3(b : bytes()) = Bytes.to_any_size(b)
|
27
test/contracts/bytes_misc.aes
Normal file
27
test/contracts/bytes_misc.aes
Normal file
@ -0,0 +1,27 @@
|
||||
include "String.aes"
|
||||
contract BytesMisc =
|
||||
entrypoint sizeFixed(b : bytes(4)) : int = Bytes.size(b)
|
||||
entrypoint sizeAny(b : bytes()) : int = Bytes.size(b)
|
||||
entrypoint int_to_bytes(i : int) : bytes() = Int.to_bytes(i, 16)
|
||||
|
||||
entrypoint test(b3 : bytes(3), b7 : bytes(7), bX : bytes, i : int, s : string) =
|
||||
let bi = Int.to_bytes(i, 8)
|
||||
let bs = String.to_bytes(s)
|
||||
|
||||
let b10 = Bytes.concat(b3, b7)
|
||||
|
||||
let (b4, b6 : bytes(6)) = Bytes.split(b10)
|
||||
|
||||
let Some((b8, b2)) = Bytes.split_any(bX, 8)
|
||||
|
||||
let bX7 = Bytes.concat(bX, b7)
|
||||
|
||||
let Some((b5, bX2)) = Bytes.split_any(bX7, 5)
|
||||
|
||||
let Some((b7b, b0)) = Bytes.split_any(bX, Bytes.size(b7))
|
||||
|
||||
let Some(b5b : bytes(5)) = Bytes.to_fixed_size(b5)
|
||||
|
||||
let (b1 : bytes(1), _) = Bytes.split(b5b)
|
||||
|
||||
[bi, bs, b0, Bytes.to_any_size(b1), b2, Bytes.to_any_size(b4), Bytes.to_any_size(b6), b7b, b8, bX2]
|
@ -6,3 +6,5 @@ contract BytesToX =
|
||||
String.concat(Bytes.to_str(b), Bytes.to_str(#ffff))
|
||||
entrypoint to_str_big(b : bytes(65)) : string =
|
||||
Bytes.to_str(b)
|
||||
entrypoint to_fixed(b : bytes()) : option(bytes(4)) = Bytes.to_fixed_size(b)
|
||||
entrypoint to_any(b : bytes(4)) = Bytes.to_any_size(b)
|
||||
|
5
test/contracts/calling_child_contract_entrypoint.aes
Normal file
5
test/contracts/calling_child_contract_entrypoint.aes
Normal file
@ -0,0 +1,5 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
entrypoint f() = F.g()
|
15
test/contracts/ceres.aes
Normal file
15
test/contracts/ceres.aes
Normal file
@ -0,0 +1,15 @@
|
||||
contract C =
|
||||
entrypoint test() =
|
||||
let a : int = 23
|
||||
let b : int = 52
|
||||
let c = a bor b
|
||||
let d = c bxor b
|
||||
let e = d band b
|
||||
let f = bnot a
|
||||
let g = f << 2
|
||||
let h = g >> 2
|
||||
let i = Int.mulmod(a, b, h)
|
||||
let j = Crypto.poseidon(i, a)
|
||||
let k : bytes(32) = Address.to_bytes(Call.origin)
|
||||
let l = sg_MhibzTP1wWzGCTjtPFr1TiPqRJrrJqw7auvEuF5i3FdoALWqXLBDY6xxRRNUSPHK3EQTnTzF12EyspkxrSMxVHKsZeSMj
|
||||
(a bor b band c bxor a << bnot b >> a, k, l)
|
@ -2,7 +2,8 @@
|
||||
|
||||
contract ChainTest =
|
||||
|
||||
record state = { last_bf : address }
|
||||
record state = { last_bf : address
|
||||
, nw_id : string }
|
||||
|
||||
function init() : state =
|
||||
{last_bf = Contract.address}
|
||||
@ -11,3 +12,6 @@ contract ChainTest =
|
||||
|
||||
function save_coinbase() =
|
||||
put(state{last_bf = Chain.coinbase})
|
||||
|
||||
function save_network_id() =
|
||||
put(state{nw_id = Chain.network_id})
|
||||
|
@ -1,8 +0,0 @@
|
||||
contract ChannelEnv =
|
||||
public function coinbase() : address = Chain.coinbase
|
||||
|
||||
public function timestamp() : int = Chain.timestamp
|
||||
|
||||
public function block_height() : int = Chain.block_height
|
||||
|
||||
public function difficulty() : int = Chain.difficulty
|
@ -1,7 +0,0 @@
|
||||
contract ChannelOnChainContractNameResolution =
|
||||
|
||||
public function can_resolve(name: string, key: string) : bool =
|
||||
switch(AENS.resolve(name, key) : option(string))
|
||||
None => false
|
||||
Some(_address) => true
|
||||
|
@ -1,51 +0,0 @@
|
||||
contract ChannelOnChainContractOracle =
|
||||
|
||||
type query_t = string
|
||||
type answer_t = string
|
||||
type oracle_id = oracle(query_t, answer_t)
|
||||
type query_id = oracle_query(query_t, answer_t)
|
||||
|
||||
record state = { oracle : oracle_id,
|
||||
question : string,
|
||||
bets : map(string, address)
|
||||
}
|
||||
|
||||
|
||||
public function init(oracle: oracle_id, question: string) : state =
|
||||
{ oracle = oracle,
|
||||
question = question,
|
||||
bets = {}
|
||||
}
|
||||
|
||||
public stateful function place_bet(answer: string) =
|
||||
switch(Map.lookup(answer, state.bets))
|
||||
None =>
|
||||
put(state{ bets = state.bets{[answer] = Call.caller}})
|
||||
"ok"
|
||||
Some(_value) =>
|
||||
"bet_already_taken"
|
||||
|
||||
public function expiry() =
|
||||
Oracle.expiry(state.oracle)
|
||||
|
||||
public function query_fee() =
|
||||
Oracle.query_fee(state.oracle)
|
||||
|
||||
public function get_question(q: query_id) =
|
||||
Oracle.get_question(state.oracle, q)
|
||||
|
||||
public stateful function resolve(q: query_id) =
|
||||
switch(Oracle.get_answer(state.oracle, q))
|
||||
None =>
|
||||
"no response"
|
||||
Some(result) =>
|
||||
if(state.question == Oracle.get_question(state.oracle, q))
|
||||
switch(Map.lookup(result, state.bets))
|
||||
None =>
|
||||
"no winning bet"
|
||||
Some(winner) =>
|
||||
Chain.spend(winner, Contract.balance)
|
||||
"ok"
|
||||
else
|
||||
"different question"
|
||||
|
@ -1,9 +0,0 @@
|
||||
contract Remote =
|
||||
function get : () => int
|
||||
function can_resolve : (string, string) => bool
|
||||
|
||||
contract RemoteCall =
|
||||
|
||||
function remote_resolve(r : Remote, name: string, key: string) : bool =
|
||||
r.can_resolve(name, key)
|
||||
|
@ -1,51 +0,0 @@
|
||||
|
||||
contract Chess =
|
||||
|
||||
type board = map(int, map(int, string))
|
||||
type state = board
|
||||
|
||||
private function get_row(r, m : board) =
|
||||
Map.lookup_default(r, m, {})
|
||||
|
||||
private function set_piece(r, c, p, m : board) =
|
||||
m { [r] = get_row(r, m) { [c] = p } }
|
||||
|
||||
private function get_piece(r, c, m : board) =
|
||||
Map.lookup(c, get_row(r, m))
|
||||
|
||||
private function from_list(xs, m : board) =
|
||||
switch(xs)
|
||||
[] => m
|
||||
(r, c, p) :: xs => from_list(xs, set_piece(r, c, p, m))
|
||||
|
||||
function init() =
|
||||
from_list([ (2, 1, "white pawn"), (7, 1, "black pawn")
|
||||
, (2, 2, "white pawn"), (7, 2, "black pawn")
|
||||
, (2, 3, "white pawn"), (7, 3, "black pawn")
|
||||
, (2, 4, "white pawn"), (7, 4, "black pawn")
|
||||
, (2, 5, "white pawn"), (7, 5, "black pawn")
|
||||
, (2, 6, "white pawn"), (7, 6, "black pawn")
|
||||
, (2, 7, "white pawn"), (7, 7, "black pawn")
|
||||
, (2, 8, "white pawn"), (7, 8, "black pawn")
|
||||
, (1, 1, "white rook"), (8, 1, "black rook")
|
||||
, (1, 2, "white knight"), (8, 2, "black knight")
|
||||
, (1, 3, "white bishop"), (8, 3, "black bishop")
|
||||
, (1, 4, "white queen"), (8, 4, "black queen")
|
||||
, (1, 5, "white king"), (8, 5, "black king")
|
||||
, (1, 6, "white bishop"), (8, 6, "black bishop")
|
||||
, (1, 7, "white knight"), (8, 7, "black knight")
|
||||
, (1, 8, "white rook"), (8, 8, "black rook")
|
||||
], {})
|
||||
|
||||
function piece(r, c) = get_piece(r, c, state)
|
||||
|
||||
function move_piece(r, c, r1, c1) =
|
||||
switch(piece(r, c))
|
||||
Some(p) => put(set_piece(r1, c1, p, state))
|
||||
|
||||
function destroy_piece(r, c) =
|
||||
put(state{ [r] = Map.delete(c, get_row(r, state)) })
|
||||
|
||||
function delete_row(r) =
|
||||
put(Map.delete(r, state))
|
||||
|
8
test/contracts/child_contract_init_bug.aes
Normal file
8
test/contracts/child_contract_init_bug.aes
Normal file
@ -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()
|
@ -1,5 +0,0 @@
|
||||
contract HigherOrderQueryType =
|
||||
stateful function foo(o) : oracle_query(_, string ) =
|
||||
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
|
||||
|
||||
entrypoint main_fun() = ()
|
@ -1,5 +0,0 @@
|
||||
contract HigherOrderResponseType =
|
||||
stateful function foo(o, q : oracle_query(string, _)) =
|
||||
Oracle.respond(o, q, (x) => x + 1)
|
||||
|
||||
entrypoint main_fun() = ()
|
@ -1,3 +0,0 @@
|
||||
contract MissingInitFunction =
|
||||
type state = int * int
|
||||
|
@ -1,5 +0,0 @@
|
||||
contract PolymorphicQueryType =
|
||||
stateful function is_oracle(o) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main_fun() = ()
|
@ -1,5 +0,0 @@
|
||||
contract PolymorphicResponseType =
|
||||
function is_oracle(o : oracle(string, 'r)) =
|
||||
Oracle.check(o)
|
||||
|
||||
entrypoint main_fun(o : oracle(string, int)) = is_oracle(o)
|
@ -1,5 +0,0 @@
|
||||
contract UnappliedNamedArgBuiltin =
|
||||
// Allowed in FATE, but not AEVM
|
||||
stateful entrypoint main_fun(s) =
|
||||
let reg = Oracle.register
|
||||
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
|
@ -1,16 +1,7 @@
|
||||
|
||||
contract interface Remote =
|
||||
entrypoint up_to : (int) => list(int)
|
||||
entrypoint sum : (list(int)) => int
|
||||
entrypoint some_string : () => string
|
||||
entrypoint pair : (int, string) => int * string
|
||||
entrypoint squares : (int) => list(int * int)
|
||||
entrypoint filter_some : (list(option(int))) => list(int)
|
||||
entrypoint all_some : (list(option(int))) => option(list(int))
|
||||
|
||||
contract ComplexTypes =
|
||||
|
||||
record state = { worker : Remote }
|
||||
record state = { worker : ComplexTypes }
|
||||
|
||||
entrypoint init(worker) = {worker = worker}
|
||||
|
||||
|
@ -1,20 +0,0 @@
|
||||
|
||||
contract OtherContract =
|
||||
|
||||
function multiply : (int, int) => int
|
||||
|
||||
contract ThisContract =
|
||||
|
||||
record state = { server : OtherContract, n : int }
|
||||
|
||||
function init(server : OtherContract) =
|
||||
{ server = server, n = 2 }
|
||||
|
||||
function square() =
|
||||
put(state{ n @ n = state.server.multiply(value = 100, n, n) })
|
||||
|
||||
function get_n() = state.n
|
||||
|
||||
function tip_server() =
|
||||
Chain.spend(state.server.address, Call.value)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user