Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ad5270e38 | |||
| a982f25262 | |||
| 20cab3ae57 | |||
| 1ffb20178c | |||
| 6d79d2d558 | |||
| 24c579a5d3 | |||
| 1be24c94c5 | |||
| ebb1f9ecf9 | |||
| 9cb3158dfd | |||
| becafe4001 | |||
| e8a171dc45 | |||
| a7b7aafced | |||
| 262452fb70 | |||
| 3029bf31cb | |||
| 4896ad3b36 | |||
| b20b9c5df5 | |||
| d793660545 | |||
| 4957d01e9e | |||
| 9d76e6186a | |||
| ae3edac53e | |||
| acec32e744 | |||
| 5784f074a6 | |||
| d07b321b25 | |||
| 2e6c01cb75 | |||
| b22eeffc3d | |||
| b366bed24b |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,7 @@
|
||||
import glob
|
||||
import shutil
|
||||
|
||||
def pre_build(**kwargs):
|
||||
for file in glob.glob('../docs/*.md'):
|
||||
shutil.copy(file, 'docs')
|
||||
shutil.copy('../CHANGELOG.md', 'docs')
|
||||
@@ -0,0 +1,55 @@
|
||||
site_name: æternity Sophia Language
|
||||
plugins:
|
||||
- search
|
||||
- mkdocs-simple-hooks:
|
||||
hooks:
|
||||
on_pre_build: 'hook:pre_build'
|
||||
repo_url: 'https://github.com/aeternity/aesophia'
|
||||
edit_uri: ''
|
||||
|
||||
extra:
|
||||
version:
|
||||
provider: mike
|
||||
|
||||
theme:
|
||||
favicon: favicon.png
|
||||
name: material
|
||||
custom_dir: overrides
|
||||
language: en
|
||||
palette:
|
||||
- scheme: default
|
||||
primary: pink
|
||||
accent: pink
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- scheme: slate
|
||||
primary: pink
|
||||
accent: pink
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to light mode
|
||||
features:
|
||||
- content.tabs.link
|
||||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
|
||||
# Don't include MkDocs' JavaScript
|
||||
include_search_page: false
|
||||
search_index_only: true
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight
|
||||
- pymdownx.superfences
|
||||
- toc:
|
||||
toc_depth: 3
|
||||
|
||||
nav:
|
||||
- Introduction: index.md
|
||||
- Syntax: sophia_syntax.md
|
||||
- Features: sophia_features.md
|
||||
- Standard library: sophia_stdlib.md
|
||||
- Contract examples: sophia_examples.md
|
||||
- Changelog: CHANGELOG.md
|
||||
@@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block outdated %}
|
||||
You're not viewing the latest version.
|
||||
<a href="{{ '../' ~ base_url }}">
|
||||
<strong>Click here to go to latest.</strong>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Publish development docs
|
||||
on:
|
||||
push:
|
||||
branches: ['master']
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push master
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Publish release docs
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip3
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('.github/workflows/requirements.txt') }}
|
||||
- run: pip3 install -r .github/workflows/requirements.txt
|
||||
- run: git config --global user.email "github-action@users.noreply.github.com"
|
||||
- run: git config --global user.name "GitHub Action"
|
||||
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
|
||||
- run: |
|
||||
cd .docssite
|
||||
mike deploy --push --update-aliases $RELEASE_VERSION latest
|
||||
@@ -0,0 +1,4 @@
|
||||
mkdocs==1.2.3
|
||||
mkdocs-simple-hooks==0.1.3
|
||||
mkdocs-material==7.1.9
|
||||
mike==1.0.1
|
||||
@@ -22,3 +22,5 @@ aesophia
|
||||
.qcci
|
||||
current_counterexample.eqc
|
||||
test/contracts/test.aes
|
||||
__pycache__
|
||||
.docssite/docs/*.md
|
||||
|
||||
+32
-1
@@ -9,6 +9,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [6.1.0] - 2021-10-20
|
||||
### Added
|
||||
- `Bitwise` stdlib
|
||||
- `Set` stdlib
|
||||
- `Option.force_msg`
|
||||
- Loading namespaces into the current scope (e.g. `using Pair`)
|
||||
- Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`)
|
||||
- Add builtin types (`AENS.name, AENS.pointee, Chain.ttl, Chain.base_tx, Chain.ga_meta_tx, Chain.paying_for_tx`) to
|
||||
the calldata and result decoder
|
||||
- Patterns guards
|
||||
```
|
||||
switch(x)
|
||||
a::[] | a > 10 => 1
|
||||
_ => 2
|
||||
```
|
||||
```
|
||||
function
|
||||
f(a::[]) | a > 10 = 1
|
||||
f(_) = 2
|
||||
```
|
||||
### Changed
|
||||
- Fixed the ACI renderer, it shouldn't drop the `stateful` modifier
|
||||
|
||||
## [6.0.2] 2021-07-05
|
||||
### Changed
|
||||
- `List.from_to_step` now forbids non-positive step (this change does
|
||||
*not* alter the behavior of the previously deployed contracts)
|
||||
- Fixed leaking state between contracts
|
||||
|
||||
## [6.0.1] 2021-06-24
|
||||
### Changed
|
||||
- Fixed a bug in calldata encoding for contracts containing multiple contracts
|
||||
@@ -303,7 +332,9 @@ 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/v6.1.0...HEAD
|
||||
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
|
||||
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
|
||||
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
|
||||
[6.0.0]: https://github.com/aeternity/aesophia/compare/v5.0.0...v6.0.0
|
||||
[5.0.0]: https://github.com/aeternity/aesophia/compare/v4.3.0...v5.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2017, aeternity developers
|
||||
Copyright (c) 2017, æternity developers
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
|
||||
@@ -5,13 +5,17 @@ This is the __sophia__ compiler for the æternity system which compiles contract
|
||||
The compiler is currently being used three places
|
||||
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
|
||||
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
|
||||
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
|
||||
- In [æternity node](https://github.com/aeternity/aeternity) tests
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md).
|
||||
* [Sophia Documentation](docs/sophia.md).
|
||||
* [Sophia Standard Library](docs/sophia_stdlib.md).
|
||||
* [Introduction](docs/index.md)
|
||||
* [Syntax](docs/sophia_syntax.md)
|
||||
* [Features](docs/sophia_features.md)
|
||||
* [Standard library](docs/sophia_stdlib.md)
|
||||
* [Contract examples](docs/sophia_examples.md)
|
||||
|
||||
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
|
||||
|
||||
## Versioning
|
||||
|
||||
@@ -26,5 +30,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)
|
||||
* [aeso_compiler: the Sophia compiler](docs/aeso_compiler.md)
|
||||
* [aeso_aci: the ACI interface](docs/aeso_aci.md)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Introduction
|
||||
Sophia is a functional language designed for smart contract development. It is strongly typed and has
|
||||
restricted mutable state.
|
||||
|
||||
Sophia is customized for smart contracts, which can be published
|
||||
to a blockchain. Thus some features of conventional
|
||||
languages, such as floating point arithmetic, are not present in Sophia, and
|
||||
some [æternity blockchain](https://aeternity.com) specific primitives, constructions and types have been added.
|
||||
|
||||
!!! Note
|
||||
- For rapid prototyping of smart contracts check out [AEstudio](https://studio.aepps.com/)!
|
||||
- For playing around and diving deeper into the language itself check out the [REPL](https://repl.aeternity.io/)!
|
||||
+1
-1195
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
# Contract examples
|
||||
|
||||
## Crowdfunding
|
||||
```sophia
|
||||
/*
|
||||
* A simple crowd-funding example
|
||||
*/
|
||||
contract FundMe =
|
||||
|
||||
record spend_args = { recipient : address,
|
||||
amount : int }
|
||||
|
||||
record state = { contributions : map(address, int),
|
||||
total : int,
|
||||
beneficiary : address,
|
||||
deadline : int,
|
||||
goal : int }
|
||||
|
||||
stateful function spend(args : spend_args) =
|
||||
Chain.spend(args.recipient, args.amount)
|
||||
|
||||
entrypoint init(beneficiary, deadline, goal) : state =
|
||||
{ contributions = {},
|
||||
beneficiary = beneficiary,
|
||||
deadline = deadline,
|
||||
total = 0,
|
||||
goal = goal }
|
||||
|
||||
function is_contributor(addr) =
|
||||
Map.member(addr, state.contributions)
|
||||
|
||||
stateful entrypoint contribute() =
|
||||
if(Chain.block_height >= state.deadline)
|
||||
spend({ recipient = Call.caller, amount = Call.value }) // Refund money
|
||||
false
|
||||
else
|
||||
let amount =
|
||||
switch(Map.lookup(Call.caller, state.contributions))
|
||||
None => Call.value
|
||||
Some(n) => n + Call.value
|
||||
put(state{ contributions[Call.caller] = amount,
|
||||
total @ tot = tot + Call.value })
|
||||
true
|
||||
|
||||
stateful entrypoint withdraw() =
|
||||
if(Chain.block_height < state.deadline)
|
||||
abort("Cannot withdraw before deadline")
|
||||
if(Call.caller == state.beneficiary)
|
||||
withdraw_beneficiary()
|
||||
elif(is_contributor(Call.caller))
|
||||
withdraw_contributor()
|
||||
else
|
||||
abort("Not a contributor or beneficiary")
|
||||
|
||||
stateful function withdraw_beneficiary() =
|
||||
require(state.total >= state.goal, "Project was not funded")
|
||||
spend({recipient = state.beneficiary,
|
||||
amount = Contract.balance })
|
||||
|
||||
stateful function withdraw_contributor() =
|
||||
if(state.total >= state.goal)
|
||||
abort("Project was funded")
|
||||
let to = Call.caller
|
||||
spend({recipient = to,
|
||||
amount = state.contributions[to]})
|
||||
put(state{ contributions @ c = Map.delete(to, c) })
|
||||
```
|
||||
|
||||
## Repositories
|
||||
This is a list with repositories that include smart contracts written in Sophia:
|
||||
|
||||
- [aepp-sophia-examples](https://github.com/aeternity/aepp-sophia-examples)
|
||||
- A repository that contains lots of different examples. The functionality of these examples is - to some extent - also covered by tests written in JavaScript.
|
||||
@@ -0,0 +1,877 @@
|
||||
# Features
|
||||
## Contracts
|
||||
|
||||
The main unit of code in Sophia is the *contract*.
|
||||
|
||||
- A contract implementation, or simply a contract, is the code for a
|
||||
smart contract and consists of a list of types, entrypoints and local
|
||||
functions. Only the entrypoints can be called from outside the contract.
|
||||
- A contract instance is an entity living on the block chain (or in a state
|
||||
channel). Each instance has an address that can be used to call its
|
||||
entrypoints, either from another contract or in a call transaction.
|
||||
- A contract may define a type `state` encapsulating its local
|
||||
state. When creating a new contract the `init` entrypoint is executed and the
|
||||
state is initialized to its return value.
|
||||
|
||||
The language offers some primitive functions to interact with the blockchain and contracts.
|
||||
Please refer to the [Chain](sophia_stdlib.md#chain), [Contract](sophia_stdlib.md#contract)
|
||||
and the [Call](sophia_stdlib.md#call) namespaces in the documentation.
|
||||
|
||||
### Calling other contracts
|
||||
|
||||
To call a function in another contract you need the address to an instance of
|
||||
the contract. The type of the address must be a contract type, which consists
|
||||
of a number of type definitions and entrypoint declarations. For instance,
|
||||
|
||||
```sophia
|
||||
// A contract type
|
||||
contract interface VotingType =
|
||||
entrypoint vote : string => unit
|
||||
```
|
||||
|
||||
Now given contract address of type `VotingType` you can call the `vote`
|
||||
entrypoint of that contract:
|
||||
|
||||
```sophia
|
||||
contract VoteTwice =
|
||||
entrypoint voteTwice(v : VotingType, alt : string) =
|
||||
v.vote(alt)
|
||||
v.vote(alt)
|
||||
```
|
||||
|
||||
Contract calls take two optional named arguments `gas : int` and `value : int`
|
||||
that lets you set a gas limit and provide tokens to a contract call. If omitted
|
||||
the defaults are no gas limit and no tokens. Suppose there is a fee for voting:
|
||||
|
||||
```sophia
|
||||
entrypoint voteTwice(v : VotingType, fee : int, alt : string) =
|
||||
v.vote(value = fee, alt)
|
||||
v.vote(value = fee, alt)
|
||||
```
|
||||
|
||||
Named arguments can be given in any order.
|
||||
|
||||
Note that reentrant calls are not permitted. In other words, when calling
|
||||
another contract it cannot call you back (directly or indirectly).
|
||||
|
||||
To construct a value of a contract type you can give a contract address literal
|
||||
(for instance `ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay`), or
|
||||
convert an account address to a contract address using `Address.to_contract`.
|
||||
Note that if the contract does not exist, or it doesn't have the entrypoint, or
|
||||
the type of the entrypoint does not match the stated contract type, the call
|
||||
fails.
|
||||
|
||||
To recover the underlying `address` of a contract instance there is a field
|
||||
`address : address`. For instance, to send tokens to the voting contract (given that it is payable)
|
||||
without calling it you can write
|
||||
|
||||
```sophia
|
||||
entrypoint pay(v : VotingType, amount : int) =
|
||||
Chain.spend(v.address, amount)
|
||||
```
|
||||
|
||||
### Protected contract calls
|
||||
|
||||
If a contract call fails for any reason (for instance, the remote contract
|
||||
crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong
|
||||
type) the parent call also fails. To make it possible to recover from failures,
|
||||
contract calls takes a named argument `protected : bool` (default `false`).
|
||||
|
||||
The protected argument must be a literal boolean, and when set to `true`
|
||||
changes the type of the contract call, wrapping the result in an `option` type.
|
||||
If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is
|
||||
the return value of the call.
|
||||
|
||||
```sophia
|
||||
contract interface VotingType =
|
||||
entrypoint : vote : string => unit
|
||||
|
||||
contract Voter =
|
||||
entrypoint tryVote(v : VotingType, alt : string) =
|
||||
switch(v.vote(alt, protected = true) : option(unit))
|
||||
None => "Voting failed"
|
||||
Some(_) => "Voting successful"
|
||||
```
|
||||
|
||||
Any gas that was consumed by the contract call before the failure stays
|
||||
consumed, which means that in order to protect against the remote contract
|
||||
running out of gas it is necessary to set a gas limit using the `gas` argument.
|
||||
However, note that errors that would normally consume all the gas in the
|
||||
transaction still only uses up the gas spent running the contract.
|
||||
|
||||
|
||||
### Contract factories and child contracts
|
||||
|
||||
Since the version 6.0.0 Sophia supports deploying contracts by other
|
||||
contracts. This can be done in two ways:
|
||||
|
||||
- Contract cloning via [`Chain.clone`](sophia_stdlib.md#clone)
|
||||
- Direct deploy via [`Chain.create`](sophia_stdlib.md#create)
|
||||
|
||||
These functions take variable number of arguments that must match the created
|
||||
contract's `init` function. Beside that they take some additional named
|
||||
arguments – please refer to their documentation for the details.
|
||||
|
||||
While `Chain.clone` requires only a `contract interface` and a living instance
|
||||
of a given contract on the chain, `Chain.create` needs a full definition of a
|
||||
to-create contract defined by the standard `contract` syntax, for example
|
||||
|
||||
```sophia
|
||||
contract IntHolder =
|
||||
type state = int
|
||||
entrypoint init(x) = x
|
||||
entrypoint get() = state
|
||||
|
||||
main contract IntHolderFactory =
|
||||
stateful entrypoint new(x : int) : IntHolder =
|
||||
let ih = Chain.create(x) : IntHolder
|
||||
ih
|
||||
```
|
||||
|
||||
In case of a presence of child contracts (`IntHolder` in this case), the main
|
||||
contract must be pointed out with the `main` keyword as shown in the example.
|
||||
|
||||
|
||||
## Mutable state
|
||||
|
||||
Sophia does not have arbitrary mutable state, but only a limited form of state
|
||||
associated with each contract instance.
|
||||
|
||||
- Each contract defines a type `state` encapsulating its mutable state.
|
||||
The type `state` defaults to the `unit`.
|
||||
- The initial state of a contract is computed by the contract's `init`
|
||||
function. The `init` function is *pure* and returns the initial state as its
|
||||
return value.
|
||||
If the type `state` is `unit`, the `init` function defaults to returning the value `()`.
|
||||
At contract creation time, the `init` function is executed and
|
||||
its result is stored as the contract state.
|
||||
- The value of the state is accessible from inside the contract
|
||||
through an implicitly bound variable `state`.
|
||||
- State updates are performed by calling a function `put : state => unit`.
|
||||
- Aside from the `put` function (and similar functions for transactions
|
||||
and events), the language is purely functional.
|
||||
- Functions modifying the state need to be annotated with the `stateful` keyword (see below).
|
||||
|
||||
To make it convenient to update parts of a deeply nested state Sophia
|
||||
provides special syntax for map/record updates.
|
||||
|
||||
### Stateful functions
|
||||
|
||||
Top-level functions and entrypoints must be annotated with the
|
||||
`stateful` keyword to be allowed to affect the state of the running contract.
|
||||
For instance,
|
||||
|
||||
```sophia
|
||||
stateful entrypoint set_state(s : state) =
|
||||
put(s)
|
||||
```
|
||||
|
||||
Without the `stateful` annotation the compiler does not allow the call to
|
||||
`put`. A `stateful` annotation is required to
|
||||
|
||||
* Use a stateful primitive function. These are
|
||||
- `put`
|
||||
- `Chain.spend`
|
||||
- `Oracle.register`
|
||||
- `Oracle.query`
|
||||
- `Oracle.respond`
|
||||
- `Oracle.extend`
|
||||
- `AENS.preclaim`
|
||||
- `AENS.claim`
|
||||
- `AENS.transfer`
|
||||
- `AENS.revoke`
|
||||
- `AENS.update`
|
||||
* Call a `stateful` function in the current contract
|
||||
* Call another contract with a non-zero `value` argument.
|
||||
|
||||
A `stateful` annotation *is not* required to
|
||||
|
||||
* Read the contract state.
|
||||
* Issue an event using the `event` function.
|
||||
* Call another contract with `value = 0`, even if the called function is stateful.
|
||||
|
||||
## Payable
|
||||
|
||||
### Payable contracts
|
||||
|
||||
A concrete contract is by default *not* payable. Any attempt at spending to such
|
||||
a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a
|
||||
contract shall be able to receive funds in this way it has to be declared `payable`:
|
||||
|
||||
```sophia
|
||||
// A payable contract
|
||||
payable contract ExampleContract =
|
||||
stateful entrypoint do_stuff() = ...
|
||||
```
|
||||
|
||||
If in doubt, it is possible to check if an address is payable using
|
||||
`Address.is_payable(addr)`.
|
||||
|
||||
### Payable entrypoints
|
||||
|
||||
A contract entrypoint is by default *not* payable. Any call to such a function
|
||||
(either a [Remote call](#calling-other-contracts) or a contract call transaction)
|
||||
that has a non-zero `value` will fail. Contract entrypoints that should be called
|
||||
with a non-zero value should be declared `payable`.
|
||||
|
||||
```sophia
|
||||
payable stateful entrypoint buy(to : address) =
|
||||
if(Call.value > 42)
|
||||
transfer_item(to)
|
||||
else
|
||||
abort("Value too low")
|
||||
```
|
||||
|
||||
Note: In the æternity VM (AEVM) contracts and entrypoints were by default
|
||||
payable until the Lima release.
|
||||
|
||||
## Namespaces
|
||||
|
||||
Code can be split into libraries using the `namespace` construct. Namespaces
|
||||
can appear at the top-level and can contain type and function definitions, but
|
||||
not entrypoints. Outside the namespace you can refer to the (non-private) names
|
||||
by qualifying them with the namespace (`Namespace.name`).
|
||||
For example,
|
||||
|
||||
```sophia
|
||||
namespace Library =
|
||||
type number = int
|
||||
function inc(x : number) : number = x + 1
|
||||
|
||||
contract MyContract =
|
||||
entrypoint plus2(x) : Library.number =
|
||||
Library.inc(Library.inc(x))
|
||||
```
|
||||
|
||||
Functions in namespaces have access to the same environment (including the
|
||||
`Chain`, `Call`, and `Contract`, builtin namespaces) as function in a contract,
|
||||
with the exception of `state`, `put` and `Chain.event` since these are
|
||||
dependent on the specific state and event types of the contract.
|
||||
|
||||
To avoid mentioning the namespace every time it is used, Sophia allows
|
||||
including the namespace in the current scope with the `using` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
using Pair
|
||||
contract C =
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
It is also possible to make an alias for the namespace with the `as` keyword:
|
||||
```
|
||||
include "Pair.aes"
|
||||
contract C =
|
||||
using Pair as P
|
||||
type state = int
|
||||
entrypoint init() =
|
||||
let p = (1, 2)
|
||||
P.fst(p) // this is the same as Pair.fst(p)
|
||||
```
|
||||
|
||||
Having the same alias for multiple namespaces is possible and it allows
|
||||
referening functions that are defined in different namespaces and have
|
||||
different names with the same alias:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function g() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
entrypoint init() = A.f() + A.g()
|
||||
```
|
||||
|
||||
Note that using functions with the same name would result in an ambiguous name
|
||||
error:
|
||||
```
|
||||
namespace Xa = function f() = 1
|
||||
namespace Xb = function f() = 2
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
type state = int
|
||||
|
||||
// the next line has an error because f is defined in both Xa and Xb
|
||||
entrypoint init() = A.f()
|
||||
```
|
||||
|
||||
Importing specific parts of a namespace or hiding these parts can also be
|
||||
done like this:
|
||||
```
|
||||
using Pair for [fst, snd] // this will only import fst and snd
|
||||
using Triple hiding [fst, snd] // this will import everything except for fst and snd
|
||||
```
|
||||
|
||||
Note that it is possible to use a namespace in the top level of the file, in the
|
||||
contract level, namespace level, or in the function level.
|
||||
|
||||
## Splitting code over multiple files
|
||||
|
||||
Code from another file can be included in a contract using an `include`
|
||||
statement. These must appear at the top-level (outside the main contract). The
|
||||
included file can contain one or more namespaces and abstract contracts. For
|
||||
example, if the file `library.aes` contains
|
||||
|
||||
```sophia
|
||||
namespace Library =
|
||||
function inc(x) = x + 1
|
||||
```
|
||||
|
||||
you can use it from another file using an `include`:
|
||||
|
||||
```sophia
|
||||
include "library.aes"
|
||||
contract MyContract =
|
||||
entrypoint plus2(x) = Library.inc(Library.inc(x))
|
||||
```
|
||||
|
||||
This behaves as if the contents of `library.aes` was textually inserted into
|
||||
the file, except that error messages will refer to the original source
|
||||
locations. The language will try to include each file at most one time automatically,
|
||||
so even cyclic includes should be working without any special tinkering.
|
||||
|
||||
## Standard library
|
||||
|
||||
Sophia offers [standard library](sophia_stdlib.md) which exposes some
|
||||
primitive operations and some higher level utilities. The builtin
|
||||
namespaces like `Chain`, `Contract`, `Map`
|
||||
are included by default and are supported internally by the compiler.
|
||||
Others like `List`, `Frac`, `Option` need to be manually included using the
|
||||
`include` directive. For example
|
||||
```sophia
|
||||
include "List.aes"
|
||||
include "Pair.aes"
|
||||
-- Map is already there!
|
||||
|
||||
namespace C =
|
||||
entrypoint keys(m : map('a, 'b)) : list('a) =
|
||||
List.map(Pair.fst, (Map.to_list(m)))
|
||||
```
|
||||
|
||||
## Types
|
||||
Sophia has the following types:
|
||||
|
||||
| Type | Description | Example |
|
||||
|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| int | A 2-complement integer | ```-1``` |
|
||||
| address | æternity address, 32 bytes | ```Call.origin``` |
|
||||
| bool | A Boolean | ```true``` |
|
||||
| bits | A bit field | ```Bits.none``` |
|
||||
| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` |
|
||||
| string | An array of bytes | ```"Foo"``` |
|
||||
| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` |
|
||||
| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` |
|
||||
| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` |
|
||||
| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` |
|
||||
| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` |
|
||||
| option('a) | An optional value either None or Some('a) | ```Some(42)``` |
|
||||
| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` |
|
||||
| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` |
|
||||
| hash | A 32-byte hash - equivalent to `bytes(32)` | |
|
||||
| signature | A signature - equivalent to `bytes(64)` | |
|
||||
| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` |
|
||||
| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` |
|
||||
| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` |
|
||||
| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` |
|
||||
|
||||
## Literals
|
||||
| Type | Constant/Literal example(s) |
|
||||
| ---------- | ------------------------------- |
|
||||
| int | `-1`, `2425`, `4598275923475723498573485768` |
|
||||
| address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` |
|
||||
| bool | `true`, `false` |
|
||||
| bits | `Bits.none`, `Bits.all` |
|
||||
| bytes(8) | `#fedcba9876543210` |
|
||||
| string | `"This is a string"` |
|
||||
| list | `[1, 2, 3]`, `[(true, 24), (false, 19), (false, -42)]` |
|
||||
| tuple | `(42, "Foo", true)` |
|
||||
| record | `{ owner = Call.origin, value = 100000000 }` |
|
||||
| map | `{["foo"] = 19, ["bar"] = 42}`, `{}` |
|
||||
| option(int) | `Some(42)`, `None` |
|
||||
| state | `state{ owner = Call.origin, magic_key = #a298105f }` |
|
||||
| event | `EventX(0, "Hello")` |
|
||||
| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
|
||||
| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` |
|
||||
| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` |
|
||||
| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` |
|
||||
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
|
||||
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
|
||||
|
||||
## Arithmetic
|
||||
|
||||
Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following
|
||||
arithmetic operations:
|
||||
- addition (`x + y`)
|
||||
- subtraction (`x - y`)
|
||||
- multiplication (`x * y`)
|
||||
- division (`x / y`), truncated towards zero
|
||||
- remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y`
|
||||
- exponentiation (`x ^ y`)
|
||||
|
||||
All operations are *safe* with respect to overflow and underflow. On AEVM they behave as the corresponding
|
||||
operations on arbitrary-size integers and fail with `arithmetic_error` if the
|
||||
result cannot be represented by a 256-bit signed word. For example, `2 ^ 255`
|
||||
fails rather than wrapping around to -2²⁵⁵.
|
||||
|
||||
The division and modulo operations also throw an arithmetic error if the
|
||||
second argument is zero.
|
||||
|
||||
## Bit fields
|
||||
|
||||
Sophia integers do not support bit arithmetic. Instead there is a separate
|
||||
type `bits`. See the standard library [documentation](sophia_stdlib.md#bits).
|
||||
|
||||
On the AEVM a bit field is represented by a 256-bit word and reading or writing
|
||||
a bit outside the 0..255 range fails with an `arithmetic_error`. On FATE a bit
|
||||
field can be of arbitrary size (but it is still represented by the
|
||||
corresponding integer, so setting very high bits can be expensive).
|
||||
|
||||
## Type aliases
|
||||
|
||||
Type aliases can be introduced with the `type` keyword and can be
|
||||
parameterized. For instance
|
||||
|
||||
```sophia
|
||||
type number = int
|
||||
type string_map('a) = map(string, 'a)
|
||||
```
|
||||
|
||||
A type alias and its definition can be used interchangeably. Sophia does not support
|
||||
higher-kinded types, meaning that following type alias is invalid: `type wrap('f, 'a) = 'f('a)`
|
||||
|
||||
## Algebraic data types
|
||||
|
||||
Sophia supports algebraic data types (variant types) and pattern matching. Data
|
||||
types are declared by giving a list of constructors with
|
||||
their respective arguments. For instance,
|
||||
|
||||
```sophia
|
||||
datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
|
||||
```
|
||||
|
||||
Elements of data types can be pattern matched against, using the `switch` construct:
|
||||
|
||||
```sophia
|
||||
function get_left(x : one_or_both('a, 'b)) : option('a) =
|
||||
switch(x)
|
||||
Left(x) => Some(x)
|
||||
Right(_) => None
|
||||
Both(x, _) => Some(x)
|
||||
```
|
||||
|
||||
or directly in the left-hand side:
|
||||
```sophia
|
||||
function
|
||||
get_left : one_or_both('a, 'b) => option('a)
|
||||
get_left(Left(x)) = Some(x)
|
||||
get_left(Right(_)) = None
|
||||
get_left(Both(x, _)) = Some(x)
|
||||
```
|
||||
|
||||
*NOTE: Data types cannot currently be recursive.*
|
||||
|
||||
Sophia also supports the assignment of patterns to variables:
|
||||
```sophia
|
||||
function f(x) = switch(x)
|
||||
h1::(t = h2::_) => (h1 + h2)::t // same as `h1::h2::k => (h1 + h2)::h2::k`
|
||||
_ => x
|
||||
|
||||
function g(p : int * option(int)) : int =
|
||||
let (a, (o = Some(b))) = p // o is equal to Pair.snd(p)
|
||||
b
|
||||
```
|
||||
|
||||
Guards are boolean expressions that can be used on patterns in both switch
|
||||
statements and functions definitions. If a guard expression evaluates to
|
||||
`true`, then the corresponding body will be used. Otherwise, the next pattern
|
||||
will be checked:
|
||||
|
||||
```sophia
|
||||
function get_left_if_positive(x : one_or_both(int, 'b)) : option(int) =
|
||||
switch(x)
|
||||
Left(x) | x > 0 => Some(x)
|
||||
Both(x, _) | x > 0 => Some(x)
|
||||
_ => None
|
||||
```
|
||||
|
||||
```sophia
|
||||
function
|
||||
get_left_if_positive : one_or_both(int, 'b) => option(int)
|
||||
get_left_if_positive(Left(x)) | x > 0 = Some(x)
|
||||
get_left_if_positive(Both(x, _)) | x > 0 = Some(x)
|
||||
get_left_if_positive(_) = None
|
||||
```
|
||||
|
||||
Guards cannot be stateful even when used inside a stateful function.
|
||||
|
||||
## Lists
|
||||
|
||||
A Sophia list is a dynamically sized, homogenous, immutable, singly
|
||||
linked list. A list is constructed with the syntax `[1, 2, 3]`. The
|
||||
elements of a list can be any of datatype but they must have the same
|
||||
type. The type of lists with elements of type `'e` is written
|
||||
`list('e)`. For example we can have the following lists:
|
||||
|
||||
```sophia
|
||||
[1, 33, 2, 666] : list(int)
|
||||
[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string)
|
||||
[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string))
|
||||
```
|
||||
|
||||
New elements can be prepended to the front of a list with the `::`
|
||||
operator. So `42 :: [1, 2, 3]` returns the list `[42, 1, 2, 3]`. The
|
||||
concatenation operator `++` appends its second argument to its first
|
||||
and returns the resulting list. So concatenating two lists
|
||||
`[1, 22, 33] ++ [10, 18, 55]` returns the list `[1, 22, 33, 10, 18, 55]`.
|
||||
|
||||
Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
|
||||
Example syntax:
|
||||
```sophia
|
||||
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
||||
// yields [12,13,14,20,21,22,30,31,32]
|
||||
```
|
||||
|
||||
Lists can be constructed using the range syntax using special `..` operator:
|
||||
```sophia
|
||||
[1..4] == [1,2,3,4]
|
||||
```
|
||||
The ranges are always ascending and have step equal to 1.
|
||||
|
||||
|
||||
Please refer to the [standard library](sophia_stdlib.md#list) for the predefined functionalities.
|
||||
|
||||
## Maps and records
|
||||
|
||||
A Sophia record type is given by a fixed set of fields with associated,
|
||||
possibly different, types. For instance
|
||||
```sophia
|
||||
record account = { name : string,
|
||||
balance : int,
|
||||
history : list(transaction) }
|
||||
```
|
||||
|
||||
Maps, on the other hand, can contain an arbitrary number of key-value bindings,
|
||||
but of a fixed type. The type of maps with keys of type `'k` and values of type
|
||||
`'v` is written `map('k, 'v)`. The key type can be any type that does not
|
||||
contain a map or a function type.
|
||||
|
||||
Please refer to the [standard library](sophia_stdlib.md#map) for the predefined functionalities.
|
||||
|
||||
### Constructing maps and records
|
||||
|
||||
A value of record type is constructed by giving a value for each of the fields.
|
||||
For the example above,
|
||||
```sophia
|
||||
function new_account(name) =
|
||||
{name = name, balance = 0, history = []}
|
||||
```
|
||||
Maps are constructed similarly, with keys enclosed in square brackets
|
||||
```sophia
|
||||
function example_map() : map(string, int) =
|
||||
{["key1"] = 1, ["key2"] = 2}
|
||||
```
|
||||
The empty map is written `{}`.
|
||||
|
||||
### Accessing values
|
||||
|
||||
Record fields access is written `r.f` and map lookup `m[k]`. For instance,
|
||||
```sophia
|
||||
function get_balance(a : address, accounts : map(address, account)) =
|
||||
accounts[a].balance
|
||||
```
|
||||
Looking up a non-existing key in a map results in contract execution failing. A
|
||||
default value to return for non-existing keys can be provided using the syntax
|
||||
`m[k = default]`. See also `Map.member` and `Map.lookup` below.
|
||||
|
||||
### Updating a value
|
||||
|
||||
Record field updates are written `r{f = v}`. This creates a new record value
|
||||
which is the same as `r`, but with the value of the field `f` replaced by `v`.
|
||||
Similarly, `m{[k] = v}` constructs a map with the same values as `m` except
|
||||
that `k` maps to `v`. It makes no difference if `m` has a mapping for `k` or
|
||||
not.
|
||||
|
||||
It is possible to give a name to the old value of a field or mapping in an
|
||||
update: instead of `acc{ balance = acc.balance + 100 }` it is possible to write
|
||||
`acc{ balance @ b = b + 100 }`, binding `b` to `acc.balance`. When giving a
|
||||
name to a map value (`m{ [k] @ x = v }`), the corresponding key must be present
|
||||
in the map or execution fails, but a default value can be provided:
|
||||
`m{ [k = default] @ x = v }`. In this case `x` is bound to `default` if
|
||||
`k` is not in the map.
|
||||
|
||||
Updates can be nested:
|
||||
```sophia
|
||||
function clear_history(a : address, accounts : map(address, account)) : map(address, account) =
|
||||
accounts{ [a].history = [] }
|
||||
```
|
||||
This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus
|
||||
requires `a` to be present in the accounts map. To have `clear_history` create
|
||||
an account if `a` is not in the map you can write (given a function `empty_account`):
|
||||
```sophia
|
||||
accounts{ [a = empty_account()].history = [] }
|
||||
```
|
||||
|
||||
### Map implementation
|
||||
|
||||
Internally in the VM maps are implemented as hash maps and support fast lookup
|
||||
and update. Large maps can be stored in the contract state and the size of the
|
||||
map does not contribute to the gas costs of a contract call reading or updating
|
||||
it.
|
||||
|
||||
## Strings
|
||||
|
||||
There is a builtin type `string`, which can be seen as an array of bytes.
|
||||
Strings can be compared for equality (`==`, `!=`), used as keys in maps and
|
||||
records, and used in builtin functions `String.length`, `String.concat` and
|
||||
the hash functions described below.
|
||||
|
||||
Please refer to the `String` [library documentation](sophia_stdlib.md#string).
|
||||
|
||||
## Chars
|
||||
|
||||
There is a builtin type `char` (the underlying representation being an integer),
|
||||
mainly used to manipulate strings via `String.to_list`/`String.from_list`.
|
||||
|
||||
Characters can also be introduced as character literals (`'x', '+', ...).
|
||||
|
||||
Please refer to the `Char` [library documentation](sophia_stdlib.md#char).
|
||||
|
||||
## Byte arrays
|
||||
|
||||
Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system,
|
||||
for example the literal `#cafe` creates a two-element array of bytes `ca` (202) and `fe` (254)
|
||||
and thus is a value of type `bytes(2)`.
|
||||
|
||||
Please refer to the `Bytes` [library documentation](sophia_stdlib.md#bytes).
|
||||
|
||||
## Cryptographic builtins
|
||||
|
||||
Libraries [Crypto](sophia_stdlib.md#crypto) and [String](sophia_stdlib.md#string) provide functions to
|
||||
hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`.
|
||||
|
||||
## Authorization interface
|
||||
|
||||
When a Generalized account is authorized, the authorization function needs
|
||||
access to the transaction and the transaction hash for the wrapped transaction. (A `GAMetaTx`
|
||||
wrapping a transaction.) The transaction and the transaction hash is available in the primitive
|
||||
`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a
|
||||
normal contract call they return `None`.
|
||||
|
||||
## Oracle interface
|
||||
You can attach an oracle to the current contract and you can interact with oracles
|
||||
through the Oracle interface.
|
||||
|
||||
For a full description of how Oracle works see
|
||||
[Oracles](https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#oracles).
|
||||
For a functionality documentation refer to the [standard library](sophia_stdlib.md#oracle).
|
||||
|
||||
### Example
|
||||
|
||||
Example for an oracle answering questions of type `string` with answers of type `int`:
|
||||
```sophia
|
||||
contract Oracles =
|
||||
|
||||
stateful entrypoint registerOracle(acct : address,
|
||||
sign : signature, // Signed network id + oracle address + contract address
|
||||
qfee : int,
|
||||
ttl : Chain.ttl) : oracle(string, int) =
|
||||
Oracle.register(acct, signature = sign, qfee, ttl)
|
||||
|
||||
entrypoint queryFee(o : oracle(string, int)) : int =
|
||||
Oracle.query_fee(o)
|
||||
|
||||
payable stateful entrypoint createQuery(o : oracle_query(string, int),
|
||||
q : string,
|
||||
qfee : int,
|
||||
qttl : Chain.ttl,
|
||||
rttl : int) : oracle_query(string, int) =
|
||||
require(qfee =< Call.value, "insufficient value for qfee")
|
||||
Oracle.query(o, q, qfee, qttl, RelativeTTL(rttl))
|
||||
|
||||
stateful entrypoint extendOracle(o : oracle(string, int),
|
||||
ttl : Chain.ttl) : unit =
|
||||
Oracle.extend(o, ttl)
|
||||
|
||||
stateful entrypoint signExtendOracle(o : oracle(string, int),
|
||||
sign : signature, // Signed network id + oracle address + contract address
|
||||
ttl : Chain.ttl) : unit =
|
||||
Oracle.extend(o, signature = sign, ttl)
|
||||
|
||||
stateful entrypoint respond(o : oracle(string, int),
|
||||
q : oracle_query(string, int),
|
||||
sign : signature, // Signed network id + oracle query id + contract address
|
||||
r : int) =
|
||||
Oracle.respond(o, q, signature = sign, r)
|
||||
|
||||
entrypoint getQuestion(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) : string =
|
||||
Oracle.get_question(o, q)
|
||||
|
||||
entrypoint hasAnswer(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) =
|
||||
switch(Oracle.get_answer(o, q))
|
||||
None => false
|
||||
Some(_) => true
|
||||
|
||||
entrypoint getAnswer(o : oracle(string, int),
|
||||
q : oracle_query(string, int)) : option(int) =
|
||||
Oracle.get_answer(o, q)
|
||||
```
|
||||
|
||||
### Sanity checks
|
||||
|
||||
When an Oracle literal is passed to a contract, no deep checks are performed.
|
||||
For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query)
|
||||
functions are provided.
|
||||
|
||||
## AENS interface
|
||||
|
||||
Contracts can interact with the
|
||||
[æternity naming system](https://github.com/aeternity/protocol/blob/master/AENS.md).
|
||||
For this purpose the [AENS](sophia_stdlib.md#aens) library was exposed.
|
||||
|
||||
### Example
|
||||
|
||||
In this example we assume that the name `name` already exists, and is owned by
|
||||
an account with address `addr`. In order to allow a contract `ct` to handle
|
||||
`name` the account holder needs to create a
|
||||
[signature](#delegation-signature) `sig` of `addr | name.hash | ct.address`.
|
||||
|
||||
Armed with this information we can for example write a function that extends
|
||||
the name if it expires within 1000 blocks:
|
||||
```sophia
|
||||
stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, FixedTTL(expiry), _)) =>
|
||||
if(Chain.block_height + 1000 > expiry)
|
||||
AENS.update(addr, name, Some(RelativeTTL(50000)), None, None, signature = sig)
|
||||
```
|
||||
|
||||
And we can write functions that adds and removes keys from the pointers of the
|
||||
name:
|
||||
```sophia
|
||||
stateful entrypoint add_key(addr : address, name : string, key : string,
|
||||
pt : AENS.pointee, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, _, ptrs)) =>
|
||||
AENS.update(addr, name, None, None, Some(ptrs{[key] = pt}), signature = sig)
|
||||
|
||||
stateful entrypoint delete_key(addr : address, name : string,
|
||||
key : string, sig : signature) =
|
||||
switch(AENS.lookup(name))
|
||||
None => ()
|
||||
Some(AENS.Name(_, _, ptrs)) =>
|
||||
let ptrs = Map.delete(key, ptrs)
|
||||
AENS.update(addr, name, None, None, Some(ptrs), signature = sig)
|
||||
```
|
||||
|
||||
*Note:* From the Iris hardfork more strict rules apply for AENS pointers, when
|
||||
a Sophia contract lookup or update (bad) legacy pointers, the bad keys are
|
||||
automatically removed so they will not appear in the pointers map.
|
||||
|
||||
## Events
|
||||
|
||||
Sophia contracts log structured messages to an event log in the resulting
|
||||
blockchain transaction. The event log is quite similar to [Events in
|
||||
Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events).
|
||||
Events are further discussed in the [protocol](https://github.com/aeternity/protocol/blob/master/contracts/events.md).
|
||||
|
||||
|
||||
To use events a contract must declare a datatype `event`, and events are then
|
||||
logged using the `Chain.event` function:
|
||||
|
||||
```sophia
|
||||
datatype event
|
||||
= Event1(int, int, string)
|
||||
| Event2(string, address)
|
||||
|
||||
Chain.event(e : event) : unit
|
||||
```
|
||||
|
||||
The event can have 0-3 *indexed* fields, and an optional *payload* field. A
|
||||
field is indexed if it fits in a 32-byte word, i.e.
|
||||
- `bool`
|
||||
- `int`
|
||||
- `bits`
|
||||
- `address`
|
||||
- `oracle(_, _)`
|
||||
- `oracle_query(_, _)`
|
||||
- contract types
|
||||
- `bytes(n)` for `n` ≤ 32, in particular `hash`
|
||||
|
||||
The payload field must be either a string or a byte array of more than 32 bytes.
|
||||
The fields can appear in any order.
|
||||
|
||||
*NOTE:* Indexing is not part of the core æternity node.
|
||||
|
||||
Events are emitted by using the `Chain.event` function. The following function
|
||||
will emit one Event of each kind in the example.
|
||||
|
||||
```sophia
|
||||
entrypoint emit_events() : () =
|
||||
Chain.event(Event1(42, 34, "foo"))
|
||||
Chain.event(Event2("This is not indexed", Contract.address))
|
||||
```
|
||||
|
||||
### Argument order
|
||||
|
||||
It is only possible to have one (1) `string` parameter in the event, but it can
|
||||
be placed in any position (and its value will end up in the `data` field), i.e.
|
||||
```sophia
|
||||
AnotherEvent(string, indexed address)
|
||||
|
||||
...
|
||||
|
||||
Chain.event(AnotherEvent("This is not indexed", Contract.address))
|
||||
```
|
||||
would yield exactly the same result in the example above!
|
||||
|
||||
## Compiler pragmas
|
||||
|
||||
To enforce that a contract is only compiled with specific versions of the
|
||||
Sophia compiler, you can give one or more `@compiler` pragmas at the
|
||||
top-level (typically at the beginning) of a file. For instance, to enforce that
|
||||
a contract is compiled with version 4.3 of the compiler you write
|
||||
|
||||
```sophia
|
||||
@compiler >= 4.3
|
||||
@compiler < 4.4
|
||||
```
|
||||
|
||||
Valid operators in compiler pragmas are `<`, `=<`, `==`, `>=`, and `>`. Version
|
||||
numbers are given as a sequence of non-negative integers separated by dots.
|
||||
Trailing zeros are ignored, so `4.0.0 == 4`. If a constraint is violated an
|
||||
error is reported and compilation fails.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Contracts can fail with an (uncatchable) exception using the built-in function
|
||||
|
||||
```sophia
|
||||
abort(reason : string) : 'a
|
||||
```
|
||||
|
||||
Calling abort causes the top-level call transaction to return an error result
|
||||
containing the `reason` string. Only the gas used up to and including the abort
|
||||
call is charged. This is different from termination due to a crash which
|
||||
consumes all available gas.
|
||||
|
||||
For convenience the following function is also built-in:
|
||||
|
||||
```sophia
|
||||
function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
```
|
||||
|
||||
## Delegation signature
|
||||
|
||||
Some chain operations (`Oracle.<operation>` and `AENS.<operation>`) have an
|
||||
optional delegation signature. This is typically used when a user/accounts
|
||||
would like to allow a contract to act on it's behalf. The exact data to be
|
||||
signed varies for the different operations, but in all cases you should prepend
|
||||
the signature data with the `network_id` (`ae_mainnet` for the æternity mainnet, etc.).
|
||||
+374
-156
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
# Syntax
|
||||
|
||||
## Lexical syntax
|
||||
|
||||
### Comments
|
||||
|
||||
Single line comments start with `//` and block comments are enclosed in `/*`
|
||||
and `*/` and can be nested.
|
||||
|
||||
### Keywords
|
||||
|
||||
```
|
||||
contract elif else entrypoint false function if import include let mod namespace
|
||||
private payable stateful switch true type record datatype main interface
|
||||
```
|
||||
|
||||
### Tokens
|
||||
|
||||
- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter.
|
||||
- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter.
|
||||
- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`)
|
||||
- `QCon = (Con\.)+Con` qualified constructor
|
||||
- `TVar = 'Id` type variable (e.g `'a`, `'b`)
|
||||
- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators
|
||||
- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators
|
||||
- `String` string literal enclosed in `"` with escape character `\`
|
||||
- `Char` character literal enclosed in `'` with escape character `\`
|
||||
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
|
||||
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
|
||||
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
|
||||
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix
|
||||
|
||||
Valid string escape codes are
|
||||
|
||||
| Escape | ASCII | |
|
||||
|---------------|-------------|---|
|
||||
| `\b` | 8 | |
|
||||
| `\t` | 9 | |
|
||||
| `\n` | 10 | |
|
||||
| `\v` | 11 | |
|
||||
| `\f` | 12 | |
|
||||
| `\r` | 13 | |
|
||||
| `\e` | 27 | |
|
||||
| `\xHexDigits` | *HexDigits* | |
|
||||
|
||||
|
||||
See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the
|
||||
details on the base58 literals.
|
||||
|
||||
## Layout blocks
|
||||
|
||||
Sophia uses Python-style layout rules to group declarations and statements. A
|
||||
layout block with more than one element must start on a separate line and be
|
||||
indented more than the currently enclosing layout block. Blocks with a single
|
||||
element can be written on the same line as the previous token.
|
||||
|
||||
Each element of the block must share the same indentation and no part of an
|
||||
element may be indented less than the indentation of the block. For instance
|
||||
|
||||
```sophia
|
||||
contract Layout =
|
||||
function foo() = 0 // no layout
|
||||
function bar() = // layout block starts on next line
|
||||
let x = foo() // indented more than 2 spaces
|
||||
x
|
||||
+ 1 // the '+' is indented more than the 'x'
|
||||
```
|
||||
|
||||
## Notation
|
||||
|
||||
In describing the syntax below, we use the following conventions:
|
||||
|
||||
- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with
|
||||
some associated value (like `Id`).
|
||||
- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`.
|
||||
- Choices are separated by vertical bars: `|`.
|
||||
- Optional elements are enclosed in `[` square brackets `]`.
|
||||
- `(` Parentheses `)` are used for grouping.
|
||||
- Zero or more repetitions are denoted by a postfix `*`, and one or more
|
||||
repetitions by a `+`.
|
||||
- `Block(X)` denotes a layout block of `X`s.
|
||||
- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s
|
||||
separated by `S`s.
|
||||
- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty.
|
||||
|
||||
|
||||
## Declarations
|
||||
|
||||
A Sophia file consists of a sequence of *declarations* in a layout block.
|
||||
|
||||
```c
|
||||
File ::= Block(TopDecl)
|
||||
|
||||
TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl)
|
||||
| 'namespace' Con '=' Block(Decl)
|
||||
| '@compiler' PragmaOp Version
|
||||
| 'include' String
|
||||
|
||||
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||
|
||||
FunDecl ::= Id ':' Type // Type signature
|
||||
| Id Args [':' Type] '=' Block(Stmt) // Definition
|
||||
|
||||
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
|
||||
Version ::= Sep1(Int, '.')
|
||||
|
||||
EModifier ::= 'payable' | 'stateful'
|
||||
FModifier ::= 'stateful' | 'private'
|
||||
|
||||
Args ::= '(' Sep(Pattern, ',') ')'
|
||||
```
|
||||
|
||||
Contract declarations must appear at the top-level.
|
||||
|
||||
For example,
|
||||
```sophia
|
||||
contract Test =
|
||||
type t = int
|
||||
entrypoint add (x : t, y : t) = x + y
|
||||
```
|
||||
|
||||
There are three forms of type declarations: type aliases (declared with the
|
||||
`type` keyword), record type definitions (`record`) and data type definitions
|
||||
(`datatype`):
|
||||
|
||||
```c
|
||||
TypeAlias ::= Type
|
||||
RecordType ::= '{' Sep(FieldType, ',') '}'
|
||||
DataType ::= Sep1(ConDecl, '|')
|
||||
|
||||
FieldType ::= Id ':' Type
|
||||
ConDecl ::= Con ['(' Sep1(Type, ',') ')']
|
||||
```
|
||||
|
||||
For example,
|
||||
```sophia
|
||||
record point('a) = {x : 'a, y : 'a}
|
||||
datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))
|
||||
type int_shape = shape(int)
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```c
|
||||
Type ::= Domain '=>' Type // Function type
|
||||
| Type '(' Sep(Type, ',') ')' // Type application
|
||||
| '(' Type ')' // Parens
|
||||
| 'unit' | Sep(Type, '*') // Tuples
|
||||
| Id | QId | TVar
|
||||
|
||||
Domain ::= Type // Single argument
|
||||
| '(' Sep(Type, ',') ')' // Multiple arguments
|
||||
```
|
||||
|
||||
The function type arrow associates to the right.
|
||||
|
||||
Example,
|
||||
```sophia
|
||||
'a => list('a) => (int * list('a))
|
||||
```
|
||||
|
||||
## Statements
|
||||
|
||||
Function bodies are blocks of *statements*, where a statement is one of the following
|
||||
|
||||
```c
|
||||
Stmt ::= 'switch' '(' Expr ')' Block(Case)
|
||||
| 'if' '(' Expr ')' Block(Stmt)
|
||||
| 'elif' '(' Expr ')' Block(Stmt)
|
||||
| 'else' Block(Stmt)
|
||||
| 'let' LetDef
|
||||
| Expr
|
||||
|
||||
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
|
||||
| Pattern '=' Block(Stmt) // Value definition
|
||||
|
||||
Case ::= Pattern '=>' Block(Stmt)
|
||||
Pattern ::= Expr
|
||||
```
|
||||
|
||||
`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,
|
||||
|
||||
```sophia
|
||||
let x : int = 4
|
||||
switch(f(x))
|
||||
None => 0
|
||||
Some(y) =>
|
||||
if(y > 10)
|
||||
"too big"
|
||||
elif(y < 3)
|
||||
"too small"
|
||||
else
|
||||
"just right"
|
||||
```
|
||||
|
||||
## Expressions
|
||||
|
||||
```c
|
||||
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
|
||||
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
|
||||
| Expr ':' Type // Type annotation 5 : int
|
||||
| Expr BinOp Expr // Binary operator x + y
|
||||
| UnOp Expr // Unary operator ! b
|
||||
| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)
|
||||
| Expr '.' Id // Projection state.x
|
||||
| Expr '[' Expr ']' // Map lookup map[key]
|
||||
| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }
|
||||
| '[' Sep(Expr, ',') ']' // List [1, 2, 3]
|
||||
| '[' Expr '|' Sep(Generator, ',') ']'
|
||||
// List comprehension [k | x <- [1], if (f(x)), let k = x+1]
|
||||
| '[' Expr '..' Expr ']' // List range [1..n]
|
||||
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
|
||||
| '(' Expr ')' // Parens (1 + 2) * 3
|
||||
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
|
||||
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
||||
| AccountAddress | ContractAddress // Chain identifiers
|
||||
| OracleAddress | OracleQueryId // Chain identifiers
|
||||
|
||||
Generator ::= Pattern '<-' Expr // Generator
|
||||
| 'if' '(' Expr ')' // Guard
|
||||
| LetDef // Definition
|
||||
|
||||
LamArgs ::= '(' Sep(LamArg, ',') ')'
|
||||
LamArg ::= Id [':' Type]
|
||||
|
||||
FieldUpdate ::= Path '=' Expr
|
||||
Path ::= Id // Record field
|
||||
| '[' Expr ']' // Map key
|
||||
| Path '.' Id // Nested record field
|
||||
| Path '[' Expr ']' // Nested map key
|
||||
|
||||
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
|
||||
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
|
||||
UnOp ::= '-' | '!'
|
||||
```
|
||||
|
||||
## Operators types
|
||||
|
||||
| Operators | Type
|
||||
| --- | ---
|
||||
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
|
||||
| `!` `&&` `\|\|` | logical operators
|
||||
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
|
||||
| `::` `++` | list operators
|
||||
|
||||
## Operator precendences
|
||||
|
||||
In order of highest to lowest precedence.
|
||||
|
||||
| Operators | Associativity
|
||||
| --- | ---
|
||||
| `!` | right
|
||||
| `^` | left
|
||||
| `*` `/` `mod` | left
|
||||
| `-` (unary) | right
|
||||
| `+` `-` | left
|
||||
| `::` `++` | right
|
||||
| `<` `>` `=<` `>=` `==` `!=` | none
|
||||
| `&&` | right
|
||||
| `\|\|` | right
|
||||
@@ -0,0 +1,136 @@
|
||||
@compiler >= 4.3
|
||||
|
||||
namespace Bitwise =
|
||||
|
||||
// bit shift 'x' right 'n' postions
|
||||
function bsr(n : int, x : int) : int =
|
||||
let step = 2^n
|
||||
let res = x / step
|
||||
if (x >= 0 || x mod step == 0)
|
||||
res
|
||||
else
|
||||
res - 1
|
||||
|
||||
// bit shift 'x' left 'n' positions
|
||||
function bsl(n : int, x : int) : int =
|
||||
x * 2^n
|
||||
|
||||
// bit shift 'x' left 'n' positions, limit at 'lim' bits
|
||||
function bsli(n : int, x : int, lim : int) : int =
|
||||
(x * 2^n) mod (2^lim)
|
||||
|
||||
// bitwise 'and' for arbitrary precision integers
|
||||
function band(a : int, b : int) : int =
|
||||
if (a >= 0 && b >= 0)
|
||||
uband_(a, b)
|
||||
elif (b >= 0)
|
||||
ubnand_(b, -1 - a)
|
||||
elif (a >= 0)
|
||||
ubnand_(a, -1 - b)
|
||||
else
|
||||
-1 - ubor_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'or' for arbitrary precision integers
|
||||
function
|
||||
bor : (int, int) => int
|
||||
bor(0, b) = b
|
||||
bor(a, 0) = a
|
||||
bor(a : int, b : int) : int =
|
||||
if (a >= 0 && b >= 0)
|
||||
ubor_(a, b)
|
||||
elif (b >= 0)
|
||||
-1 - ubnand_(-1 - a, b)
|
||||
elif (a >= 0)
|
||||
-1 - ubnand_(-1 - b, a)
|
||||
else
|
||||
-1 - uband_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'xor' for arbitrary precision integers
|
||||
function
|
||||
bxor : (int, int) => int
|
||||
bxor(0, b) = b
|
||||
bxor(a, 0) = a
|
||||
bxor(a, b) =
|
||||
if (a >= 0 && b >= 0)
|
||||
ubxor_(a, b)
|
||||
elif (b >= 0)
|
||||
-1 - ubxor_(-1 - a, b)
|
||||
elif (a >= 0)
|
||||
-1 - ubxor_(a, -1 - b)
|
||||
else
|
||||
ubxor_(-1 - a, -1 - b)
|
||||
|
||||
// bitwise 'not' for arbitrary precision integers
|
||||
function bnot(a : int) = bxor(a, -1)
|
||||
|
||||
// Bitwise 'and' for non-negative integers
|
||||
function uband(a : int, b : int) : int =
|
||||
require(a >= 0 && b >= 0, "uband is only defined for non-negative integers")
|
||||
switch((a, b))
|
||||
(0, _) => 0
|
||||
(_, 0) => 0
|
||||
_ => uband__(a, b, 1, 0)
|
||||
|
||||
private function uband_(a, b) = uband__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
uband__(0, b, val, acc) = acc
|
||||
uband__(a, 0, val, acc) = acc
|
||||
uband__(a, b, val, acc) =
|
||||
switch (a mod 2 + b mod 2)
|
||||
2 => uband__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => uband__(a / 2, b / 2, val * 2, acc)
|
||||
|
||||
// Bitwise 'or' for non-negative integers
|
||||
function ubor(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers")
|
||||
switch((a, b))
|
||||
(0, _) => b
|
||||
(_, 0) => a
|
||||
_ => ubor__(a, b, 1, 0)
|
||||
|
||||
private function ubor_(a, b) = ubor__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
ubor__(0, 0, val, acc) = acc
|
||||
ubor__(a, b, val, acc) =
|
||||
switch (a mod 2 + b mod 2)
|
||||
0 => ubor__(a / 2, b / 2, val * 2, acc)
|
||||
_ => ubor__(a / 2, b / 2, val * 2, acc + val)
|
||||
|
||||
//Bitwise 'xor' for non-negative integers
|
||||
function
|
||||
ubxor : (int, int) => int
|
||||
ubxor(0, b) = b
|
||||
ubxor(a, 0) = a
|
||||
ubxor(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
|
||||
ubxor__(a, b, 1, 0)
|
||||
|
||||
private function ubxor_(a, b) = ubxor__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
ubxor__(0, 0, val, acc) = acc
|
||||
ubxor__(a, b, val, acc) =
|
||||
switch(a mod 2 + b mod 2)
|
||||
1 => ubxor__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => ubxor__(a / 2, b / 2, val * 2, acc)
|
||||
|
||||
// Bitwise combined 'and' and 'not' of second argument for positive integers
|
||||
// x 'bnand' y = x 'band' ('bnot' y)
|
||||
// The tricky bit is that after negation the second argument has an infinite number of 1's
|
||||
// use as many as needed!
|
||||
//
|
||||
// NOTE: this function is not symmetric!
|
||||
private function ubnand(a, b) =
|
||||
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
|
||||
ubnand__(a, b, 1, 0)
|
||||
|
||||
private function ubnand_(a, b) = ubnand__(a, b, 1, 0)
|
||||
|
||||
private function
|
||||
ubnand__(0, b, val, acc) = acc
|
||||
ubnand__(a, b, val, acc) =
|
||||
switch((a mod 2, b mod 2))
|
||||
(1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val)
|
||||
_ => ubnand__(a / 2, b / 2, val * 2, acc)
|
||||
@@ -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
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace Option =
|
||||
None => abort("Forced None value")
|
||||
Some(x) => x
|
||||
|
||||
/** Assume it is `Some` with custom error message
|
||||
*/
|
||||
function force_msg(o : option('a), err : string) : 'a = switch(o)
|
||||
None => abort(err)
|
||||
Some(x) => x
|
||||
|
||||
function contains(e : 'a, o : option('a)) = o == Some(e)
|
||||
|
||||
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
include "List.aes"
|
||||
include "Option.aes"
|
||||
include "Pair.aes"
|
||||
|
||||
namespace Set =
|
||||
record set('a) = { to_map : map('a, unit) }
|
||||
|
||||
function new() : set('a) =
|
||||
{ to_map = {} }
|
||||
|
||||
function member(e : 'a, s : set('a)) : bool =
|
||||
Map.member(e, s.to_map)
|
||||
|
||||
function insert(e : 'a, s : set('a)) : set('a) =
|
||||
{ to_map = s.to_map{[e] = ()} }
|
||||
|
||||
function delete(e : 'a, s : set('a)) : set('a) =
|
||||
{ to_map = Map.delete(e, s.to_map) }
|
||||
|
||||
function size(s : set('a)) : int =
|
||||
Map.size(s.to_map)
|
||||
|
||||
function to_list(s : set('a)) : list('a) =
|
||||
List.map(Pair.fst, Map.to_list(s.to_map))
|
||||
|
||||
function from_list(l : list('a)) : set('a) =
|
||||
{ to_map = Map.from_list(List.map((x) => (x, ()), l)) }
|
||||
|
||||
function filter(p : 'a => bool, s : set('a)) : set('a) =
|
||||
from_list(List.filter(p, to_list(s)))
|
||||
|
||||
function fold(f : ('a, 'b) => 'b, acc : 'b, s : set('a)) : 'b =
|
||||
List.foldr(f, acc, to_list(s))
|
||||
|
||||
function subtract(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
filter((x) => !member(x, s2), s1)
|
||||
|
||||
function intersection(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
filter((x) => member(x, s2), s1)
|
||||
|
||||
function intersection_list(sets : list(set('a))) : set('a) =
|
||||
List.foldr(
|
||||
intersection,
|
||||
Option.default(new(), List.first(sets)),
|
||||
Option.default([], List.tail(sets)))
|
||||
|
||||
function union(s1 : set('a), s2 : set('a)) : set('a) =
|
||||
from_list(to_list(s1) ++ to_list(s2))
|
||||
|
||||
function union_list(sets : list(set('a))) : set('a) =
|
||||
List.foldr(union, new(), sets)
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "6.0.1"},
|
||||
{relx, [{release, {aesophia, "6.1.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+5
-2
@@ -254,8 +254,8 @@ decode_contract(_) -> [].
|
||||
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
||||
|
||||
%% decode_func(#{name := init}) -> [];
|
||||
decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) ->
|
||||
[" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ",
|
||||
decode_func(#{name := Name, stateful:= Stateful, payable := Payable, arguments := As, returns := T}) ->
|
||||
[" ", payable(Payable), stateful(Stateful), "entrypoint ", io_lib:format("~s", [Name]), " : ",
|
||||
decode_args(As), " => ", decode_type(T), $\n].
|
||||
|
||||
decode_args(As) ->
|
||||
@@ -336,6 +336,9 @@ 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 ->
|
||||
|
||||
+166
-41
@@ -102,6 +102,10 @@
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type namespace_alias() :: none | name().
|
||||
-type namespace_parts() :: none | {for, [name()]} | {hiding, [name()]}.
|
||||
-type used_namespaces() :: [{qname(), namespace_alias(), namespace_parts()}].
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
@@ -121,15 +125,17 @@
|
||||
-type scope() :: #scope{}.
|
||||
|
||||
-record(env,
|
||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||
, vars = [] :: [{name(), var_info()}]
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, namespace = [] :: qname()
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | contract_interface
|
||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||
, vars = [] :: [{name(), var_info()}]
|
||||
, typevars = unrestricted :: unrestricted | [name()]
|
||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||
, namespace = [] :: qname()
|
||||
, used_namespaces = [] :: used_namespaces()
|
||||
, in_pattern = false :: boolean()
|
||||
, in_guard = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract | contract_interface
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
@@ -279,7 +285,7 @@ bind_contract({Contract, Ann, Id, Contents}, Env)
|
||||
contract_call_type(
|
||||
{fun_t, AnnF, [], [ArgT || {typed, _, _, ArgT} <- Args], RetT})
|
||||
}
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, {typed, _, _, RetT}} <- Contents,
|
||||
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], {typed, _, _, RetT}}]} <- Contents,
|
||||
Name =/= "init"
|
||||
] ++
|
||||
%% Predefined fields
|
||||
@@ -312,9 +318,38 @@ bind_contract({Contract, Ann, Id, Contents}, Env)
|
||||
|
||||
%% What scopes could a given name come from?
|
||||
-spec possible_scopes(env(), qname()) -> [qname()].
|
||||
possible_scopes(#env{ namespace = Current}, Name) ->
|
||||
possible_scopes(#env{ namespace = Current, used_namespaces = UsedNamespaces }, Name) ->
|
||||
Qual = lists:droplast(Name),
|
||||
[ lists:sublist(Current, I) ++ Qual || I <- lists:seq(0, length(Current)) ].
|
||||
NewQuals = case lists:filter(fun(X) -> element(2, X) == Qual end, UsedNamespaces) of
|
||||
[] ->
|
||||
[Qual];
|
||||
Namespaces ->
|
||||
lists:map(fun(X) -> element(1, X) end, Namespaces)
|
||||
end,
|
||||
Ret1 = [ lists:sublist(Current, I) ++ Q || I <- lists:seq(0, length(Current)), Q <- NewQuals ],
|
||||
Ret2 = [ Namespace ++ Q || {Namespace, none, _} <- UsedNamespaces, Q <- NewQuals ],
|
||||
lists:usort(Ret1 ++ Ret2).
|
||||
|
||||
-spec visible_in_used_namespaces(used_namespaces(), qname()) -> boolean().
|
||||
visible_in_used_namespaces(UsedNamespaces, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
case lists:filter(fun({Ns, _, _}) -> Qual == Ns end, UsedNamespaces) of
|
||||
[] ->
|
||||
true;
|
||||
Namespaces ->
|
||||
IsVisible = fun(Namespace) ->
|
||||
case Namespace of
|
||||
{_, _, {for, Names}} ->
|
||||
lists:member(Name, Names);
|
||||
{_, _, {hiding, Names}} ->
|
||||
not lists:member(Name, Names);
|
||||
_ ->
|
||||
true
|
||||
end
|
||||
end,
|
||||
lists:any(IsVisible, Namespaces)
|
||||
end.
|
||||
|
||||
-spec lookup_type(env(), type_id()) -> false | {qname(), type_info()}.
|
||||
lookup_type(Env, Id) ->
|
||||
@@ -341,7 +376,7 @@ lookup_env(Env, Kind, Ann, Name) ->
|
||||
end.
|
||||
|
||||
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()}.
|
||||
lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
Qual = lists:droplast(QName),
|
||||
Name = lists:last(QName),
|
||||
AllowPrivate = lists:prefix(Qual, Current),
|
||||
@@ -365,7 +400,11 @@ lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
{Ann1, _} = E ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
case not is_private(Ann1) orelse AllowPrivate of
|
||||
true -> {QName, E};
|
||||
true ->
|
||||
case visible_in_used_namespaces(UsedNamespaces, QName) of
|
||||
true -> {QName, E};
|
||||
false -> false
|
||||
end;
|
||||
false -> false
|
||||
end
|
||||
end
|
||||
@@ -803,6 +842,8 @@ infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
|
||||
Namespace1 = {namespace, Ann, Name, Code1},
|
||||
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options);
|
||||
infer1(Env, [Using = {using, _, _, _, _} | Rest], Acc, Options) ->
|
||||
infer1(check_usings(Env, Using), Rest, Acc, Options);
|
||||
infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
||||
%% Pragmas are checked in check_modifiers
|
||||
infer1(Env, Rest, Acc, Options).
|
||||
@@ -859,10 +900,13 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
({letfun, _, _, _, _, _}) -> function;
|
||||
({fun_clauses, _, _, _, _}) -> function;
|
||||
({fun_decl, _, _, _}) -> prototype;
|
||||
({using, _, _, _, _}) -> using;
|
||||
(_) -> unexpected
|
||||
end,
|
||||
Get = fun(K, In) -> [ Def || Def <- In, Kind(Def) == K ] end,
|
||||
{Env1, TypeDefs} = check_typedefs(Env, Get(type, Defs)),
|
||||
OldUsedNamespaces = Env#env.used_namespaces,
|
||||
Env01 = check_usings(Env, Get(using, Defs)),
|
||||
{Env1, TypeDefs} = check_typedefs(Env01, Get(type, Defs)),
|
||||
create_type_errors(),
|
||||
check_unexpected(Get(unexpected, Defs)),
|
||||
Env2 =
|
||||
@@ -884,11 +928,13 @@ infer_contract(Env0, What, Defs0, Options) ->
|
||||
DepGraph = maps:map(fun(_, Def) -> aeso_syntax_utils:used_ids(Def) end, FunMap),
|
||||
SCCs = aeso_utils:scc(DepGraph),
|
||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||
%% Remove namespaces used in the current namespace
|
||||
Env5 = Env4#env{ used_namespaces = OldUsedNamespaces },
|
||||
%% Check that `init` doesn't read or write the state
|
||||
check_state_dependencies(Env4, Defs1),
|
||||
destroy_and_report_type_errors(Env4),
|
||||
%% Add inferred types of definitions
|
||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||
{Env5, TypeDefs ++ Decls ++ Defs1}.
|
||||
|
||||
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
|
||||
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
|
||||
@@ -988,6 +1034,43 @@ check_typedef(Env, {variant_t, Cons}) ->
|
||||
{variant_t, [ {constr_t, Ann, Con, [ check_type(Env, Arg) || Arg <- Args ]}
|
||||
|| {constr_t, Ann, Con, Args} <- Cons ]}.
|
||||
|
||||
check_usings(Env, []) ->
|
||||
Env;
|
||||
check_usings(Env = #env{ used_namespaces = UsedNamespaces }, [{using, Ann, Con, Alias, Parts} | Rest]) ->
|
||||
AliasName = case Alias of
|
||||
none ->
|
||||
none;
|
||||
_ ->
|
||||
qname(Alias)
|
||||
end,
|
||||
case get_scope(Env, qname(Con)) of
|
||||
false ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace, Ann, qname(Con)}),
|
||||
destroy_and_report_type_errors(Env);
|
||||
Scope ->
|
||||
Nsp = case Parts of
|
||||
none ->
|
||||
{qname(Con), AliasName, none};
|
||||
{ForOrHiding, Ids} ->
|
||||
IsUndefined = fun(Id) ->
|
||||
proplists:lookup(name(Id), Scope#scope.funs) == none
|
||||
end,
|
||||
UndefinedIds = lists:filter(IsUndefined, Ids),
|
||||
case UndefinedIds of
|
||||
[] ->
|
||||
{qname(Con), AliasName, {ForOrHiding, lists:map(fun name/1, Ids)}};
|
||||
_ ->
|
||||
create_type_errors(),
|
||||
type_error({using_undefined_namespace_parts, Ann, qname(Con), lists:map(fun qname/1, UndefinedIds)}),
|
||||
destroy_and_report_type_errors(Env)
|
||||
end
|
||||
end,
|
||||
check_usings(Env#env{ used_namespaces = UsedNamespaces ++ [Nsp] }, Rest)
|
||||
end;
|
||||
check_usings(Env, Using = {using, _, _, _, _}) ->
|
||||
check_usings(Env, [Using]).
|
||||
|
||||
check_unexpected(Xs) ->
|
||||
[ type_error(X) || X <- Xs ].
|
||||
|
||||
@@ -1017,6 +1100,8 @@ check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
|
||||
check_modifiers_(Env, [{pragma, Ann, Pragma} | Rest]) ->
|
||||
check_pragma(Env, Ann, Pragma),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{using, _, _, _, _} | Rest]) ->
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [Decl | Rest]) ->
|
||||
type_error({bad_top_level_decl, Decl}),
|
||||
check_modifiers_(Env, Rest);
|
||||
@@ -1272,23 +1357,29 @@ infer_letfun(Env, {fun_clauses, Ann, Fun = {id, _, Name}, Type, Clauses}) ->
|
||||
infer_letfun(Env, LetFun = {letfun, Ann, Fun, _, _, _}) ->
|
||||
{{Name, Sig}, Clause} = infer_letfun1(Env, LetFun),
|
||||
{{Name, Sig}, desugar_clauses(Ann, Fun, Sig, [Clause])}.
|
||||
|
||||
infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
|
||||
infer_letfun1(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, GuardedBodies}) ->
|
||||
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||
current_function = Fun },
|
||||
{NewEnv, {typed, _, {tuple, _, TypedArgs}, {tuple_t, _, ArgTypes}}} = infer_pattern(Env, {tuple, [{origin, system} | NameAttrib], Args}),
|
||||
ExpectedType = check_type(Env, arg_type(NameAttrib, What)),
|
||||
NewBody={typed, _, _, ResultType} = check_expr(NewEnv, Body, ExpectedType),
|
||||
InferGuardedBodies = fun({guarded, Ann, Guards, Body}) ->
|
||||
NewGuards = lists:map(fun(Guard) ->
|
||||
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrib, "bool"})
|
||||
end, Guards),
|
||||
NewBody = check_expr(NewEnv, Body, ExpectedType),
|
||||
{guarded, Ann, NewGuards, NewBody}
|
||||
end,
|
||||
NewGuardedBodies = [{guarded, _, _, {typed, _, _, ResultType}} | _] = lists:map(InferGuardedBodies, GuardedBodies),
|
||||
NamedArgs = [],
|
||||
TypeSig = {type_sig, Attrib, none, NamedArgs, ArgTypes, ResultType},
|
||||
{{Name, TypeSig},
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewBody}}.
|
||||
{letfun, Attrib, {id, NameAttrib, Name}, TypedArgs, ResultType, NewGuardedBodies}}.
|
||||
|
||||
desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
||||
NeedDesugar =
|
||||
case Clauses of
|
||||
[{letfun, _, _, As, _, _}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As);
|
||||
_ -> true
|
||||
[{letfun, _, _, As, _, [{guarded, _, [], _}]}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As);
|
||||
_ -> true
|
||||
end,
|
||||
case NeedDesugar of
|
||||
false -> [Clause] = Clauses, Clause;
|
||||
@@ -1299,11 +1390,10 @@ desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
|
||||
Tuple = fun([X]) -> X;
|
||||
(As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}}
|
||||
end,
|
||||
{letfun, Ann, Fun, Args, RetType,
|
||||
{typed, NoAnn,
|
||||
{switch, NoAnn, Tuple(Args),
|
||||
[ {'case', AnnC, Tuple(ArgsC), Body}
|
||||
|| {letfun, AnnC, _, ArgsC, _, Body} <- Clauses ]}, RetType}}
|
||||
{letfun, Ann, Fun, Args, RetType, [{guarded, NoAnn, [], {typed, NoAnn,
|
||||
{switch, NoAnn, Tuple(Args),
|
||||
[ {'case', AnnC, Tuple(ArgsC), GuardedBodies}
|
||||
|| {letfun, AnnC, _, ArgsC, _, GuardedBodies} <- Clauses ]}, RetType}}]}
|
||||
end.
|
||||
|
||||
print_typesig({Name, TypeSig}) ->
|
||||
@@ -1341,6 +1431,12 @@ lookup_name(Env, As, Id, Options) ->
|
||||
{set_qname(QId, Id), Ty1}
|
||||
end.
|
||||
|
||||
check_stateful(#env{ in_guard = true }, Id, Type = {type_sig, _, _, _, _, _}) ->
|
||||
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||
false -> ok;
|
||||
true ->
|
||||
type_error({stateful_not_allowed_in_guards, Id})
|
||||
end;
|
||||
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _, _}) ->
|
||||
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||
false -> ok;
|
||||
@@ -1365,7 +1461,7 @@ check_state_dependencies(Env, Defs) ->
|
||||
SetState = Top ++ ["put"],
|
||||
Init = Top ++ ["init"],
|
||||
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
|
||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
|
||||
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _GuardedBodies} <- Defs ],
|
||||
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||
case maps:get(Init, Deps, false) of
|
||||
false -> ok; %% No init, so nothing to check
|
||||
@@ -1469,12 +1565,12 @@ infer_expr(Env, {list_comp, AttrsL, Yield, [{comprehension_if, AttrsIF, Cond}|Re
|
||||
infer_expr(Env, {list_comp, AsLC, Yield, [{letval, AsLV, Pattern, E}|Rest]}) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, E),
|
||||
BlockType = fresh_uvar(AsLV),
|
||||
{'case', _, NewPattern, NewRest} =
|
||||
{'case', _, NewPattern, [{guarded, _, [], NewRest}]} =
|
||||
infer_case( Env
|
||||
, AsLC
|
||||
, Pattern
|
||||
, PatType
|
||||
, {list_comp, AsLC, Yield, Rest}
|
||||
, [{guarded, AsLC, [], {list_comp, AsLC, Yield, Rest}}]
|
||||
, BlockType),
|
||||
{typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = NewRest,
|
||||
{ typed
|
||||
@@ -1532,8 +1628,8 @@ infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
|
||||
infer_expr(Env, {switch, Attrs, Expr, Cases}) ->
|
||||
NewExpr = {typed, _, _, ExprType} = infer_expr(Env, Expr),
|
||||
SwitchType = fresh_uvar(Attrs),
|
||||
NewCases = [infer_case(Env, As, Pattern, ExprType, Branch, SwitchType)
|
||||
|| {'case', As, Pattern, Branch} <- Cases],
|
||||
NewCases = [infer_case(Env, As, Pattern, ExprType, GuardedBranches, SwitchType)
|
||||
|| {'case', As, Pattern, GuardedBranches} <- Cases],
|
||||
{typed, Attrs, {switch, Attrs, NewExpr, NewCases}, SwitchType};
|
||||
infer_expr(Env, {record, Attrs, Fields}) ->
|
||||
RecordType = fresh_uvar(Attrs),
|
||||
@@ -1615,10 +1711,13 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
|
||||
ArgTypes = [fresh_uvar(As) || {arg, As, _, _} <- Args],
|
||||
ArgPatterns = [{typed, As, Pat, check_type(Env, T)} || {arg, As, Pat, T} <- Args],
|
||||
ResultType = fresh_uvar(Attrs),
|
||||
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, NewBody} =
|
||||
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, Body, ResultType),
|
||||
{'case', _, {typed, _, {tuple, _, NewArgPatterns}, _}, [{guarded, _, [], NewBody}]} =
|
||||
infer_case(Env, Attrs, {tuple, Attrs, ArgPatterns}, {tuple_t, Attrs, ArgTypes}, [{guarded, Attrs, [], Body}], ResultType),
|
||||
NewArgs = [{arg, As, NewPat, NewT} || {typed, As, NewPat, NewT} <- NewArgPatterns],
|
||||
{typed, Attrs, {lam, Attrs, NewArgs, NewBody}, {fun_t, Attrs, [], ArgTypes, ResultType}};
|
||||
infer_expr(Env, {letpat, Attrs, Id, Pattern}) ->
|
||||
NewPattern = {typed, _, _, PatType} = infer_expr(Env, Pattern),
|
||||
{typed, Attrs, {letpat, Attrs, check_expr(Env, Id, PatType), NewPattern}, PatType};
|
||||
infer_expr(Env, Let = {letval, Attrs, _, _}) ->
|
||||
type_error({missing_body_for_let, Attrs}),
|
||||
infer_expr(Env, {block, Attrs, [Let, abort_expr(Attrs, "missing body")]});
|
||||
@@ -1749,11 +1848,18 @@ infer_pattern(Env, Pattern) ->
|
||||
NewPattern = infer_expr(NewEnv, Pattern),
|
||||
{NewEnv#env{ in_pattern = Env#env.in_pattern }, NewPattern}.
|
||||
|
||||
infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
||||
infer_case(Env, Attrs, Pattern, ExprType, GuardedBranches, SwitchType) ->
|
||||
{NewEnv, NewPattern = {typed, _, _, PatType}} = infer_pattern(Env, Pattern),
|
||||
NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType),
|
||||
InferGuardedBranches = fun({guarded, Ann, Guards, Branch}) ->
|
||||
NewGuards = lists:map(fun(Guard) ->
|
||||
check_expr(NewEnv#env{ in_guard = true }, Guard, {id, Attrs, "bool"})
|
||||
end, Guards),
|
||||
NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType),
|
||||
{guarded, Ann, NewGuards, NewBranch}
|
||||
end,
|
||||
NewGuardedBranches = lists:map(InferGuardedBranches, GuardedBranches),
|
||||
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
||||
{'case', Attrs, NewPattern, NewBranch}.
|
||||
{'case', Attrs, NewPattern, NewGuardedBranches}.
|
||||
|
||||
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
||||
infer_block(_Env, Attrs, [], BlockType) ->
|
||||
@@ -1767,9 +1873,11 @@ infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
|
||||
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
||||
infer_block(Env, _, [{letval, Attrs, Pattern, E}|Rest], BlockType) ->
|
||||
NewE = {typed, _, _, PatType} = infer_expr(Env, E),
|
||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, {block, Attrs, Rest}, BlockType),
|
||||
{'case', _, NewPattern, [{guarded, _, [], {typed, _, {block, _, NewRest}, _}}]} =
|
||||
infer_case(Env, Attrs, Pattern, PatType, [{guarded, Attrs, [], {block, Attrs, Rest}}], BlockType),
|
||||
[{letval, Attrs, NewPattern, NewE}|NewRest];
|
||||
infer_block(Env, Attrs, [Using = {using, _, _, _, _} | Rest], BlockType) ->
|
||||
infer_block(check_usings(Env, Using), Attrs, Rest, BlockType);
|
||||
infer_block(Env, Attrs, [E|Rest], BlockType) ->
|
||||
[infer_expr(Env, E)|infer_block(Env, Attrs, Rest, BlockType)].
|
||||
|
||||
@@ -1832,6 +1940,8 @@ free_vars({record, _, Fields}) ->
|
||||
free_vars([E || {field, _, _, E} <- Fields]);
|
||||
free_vars({typed, _, A, _}) ->
|
||||
free_vars(A);
|
||||
free_vars({letpat, _, Id, Pat}) ->
|
||||
free_vars(Id) ++ free_vars(Pat);
|
||||
free_vars(L) when is_list(L) ->
|
||||
[V || Elem <- L,
|
||||
V <- free_vars(Elem)].
|
||||
@@ -2333,8 +2443,8 @@ unfold_types(Env, {type_def, Ann, Name, Args, Def}, Options) ->
|
||||
{type_def, Ann, Name, Args, unfold_types_in_type(Env, Def, Options)};
|
||||
unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) ->
|
||||
{fun_decl, Ann, Name, unfold_types(Env, Type, Options)};
|
||||
unfold_types(Env, {letfun, Ann, Name, Args, Type, Body}, Options) ->
|
||||
{letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), unfold_types(Env, Body, Options)};
|
||||
unfold_types(Env, {letfun, Ann, Name, Args, Type, [{guarded, AnnG, [], Body}]}, Options) ->
|
||||
{letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [{guarded, AnnG, [], unfold_types(Env, Body, Options)}]};
|
||||
unfold_types(Env, T, Options) when is_tuple(T) ->
|
||||
list_to_tuple(unfold_types(Env, tuple_to_list(T), Options));
|
||||
unfold_types(Env, [H|T], Options) ->
|
||||
@@ -2835,6 +2945,10 @@ mk_error({stateful_not_allowed, Id, Fun}) ->
|
||||
Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp(Id), pp_loc(Id), pp(Fun)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({stateful_not_allowed_in_guards, Id}) ->
|
||||
Msg = io_lib:format("Cannot reference stateful function ~s (at ~s) in a pattern guard.\n",
|
||||
[pp(Id), pp_loc(Id)]),
|
||||
mk_t_err(pos(Id), Msg);
|
||||
mk_error({value_arg_not_allowed, Value, Fun}) ->
|
||||
Msg = io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||
[pp_expr("", Value), pp_loc(Value), pp(Fun)]),
|
||||
@@ -2987,6 +3101,17 @@ mk_error({contract_lacks_definition, Type, When}) ->
|
||||
),
|
||||
{Pos, Ctxt} = pp_when(When),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({ambiguous_name, QIds = [{qid, Ann, _} | _]}) ->
|
||||
Names = lists:map(fun(QId) -> io_lib:format("~s at ~s\n", [pp(QId), pp_loc(QId)]) end, QIds),
|
||||
Msg = "Ambiguous name: " ++ lists:concat(Names),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace, Ann, Namespace}) ->
|
||||
Msg = io_lib:format("Cannot use undefined namespace ~s", [Namespace]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({using_undefined_namespace_parts, Ann, Namespace, Parts}) ->
|
||||
PartsStr = lists:concat(lists:join(", ", Parts)),
|
||||
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
+73
-17
@@ -96,7 +96,8 @@
|
||||
| nil
|
||||
| {'::', var_name(), var_name()}
|
||||
| {con, arities(), tag(), [var_name()]}
|
||||
| {tuple, [var_name()]}.
|
||||
| {tuple, [var_name()]}
|
||||
| {assign, var_name(), var_name()}.
|
||||
|
||||
-type ftype() :: integer
|
||||
| boolean
|
||||
@@ -286,7 +287,7 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}).
|
||||
-spec init_type_env() -> type_env().
|
||||
init_type_env() ->
|
||||
BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string],
|
||||
[hash], [hash], [address, hash], [address],
|
||||
[{bytes, 32}], [{bytes, 32}], [address, {bytes, 32}], [address],
|
||||
[address, integer], [address, integer], [address],
|
||||
[address], [address], [address], [address], [address],
|
||||
[integer], [address, integer], []]},
|
||||
@@ -330,10 +331,11 @@ to_fcode(Env, [{Contract, Attrs, Con = {con, _, Name}, Decls}|Rest])
|
||||
case Contract =:= contract_interface of
|
||||
false ->
|
||||
#{ builtins := Builtins } = Env,
|
||||
ConEnv = Env#{ context => {contract_def, Name},
|
||||
ConEnv = maps:remove(state_layout,
|
||||
Env#{ context => {contract_def, Name},
|
||||
builtins => Builtins#{[Name, "state"] => {get_state, none},
|
||||
[Name, "put"] => {set_state, 1},
|
||||
[Name, "Chain", "event"] => {chain_event, 1}} },
|
||||
[Name, "Chain", "event"] => {chain_event, 1}} }),
|
||||
#{ functions := PrevFuns } = ConEnv,
|
||||
#{ functions := Funs } = Env1 =
|
||||
decls_to_fcode(ConEnv, Decls),
|
||||
@@ -383,7 +385,7 @@ decl_to_fcode(Env = #{context := {contract_def, _}}, {fun_decl, _, Id, _}) ->
|
||||
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
|
||||
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
|
||||
typedef_to_fcode(Env, Name, Args, Def);
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
|
||||
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
|
||||
Attrs = get_attributes(Ann),
|
||||
FName = lookup_fun(Env, qname(Env, Name)),
|
||||
FArgs = args_to_fcode(Env, Args),
|
||||
@@ -660,8 +662,8 @@ expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {ty
|
||||
Arg = fresh_name(),
|
||||
Env1 = bind_var(Env, Arg),
|
||||
Bind = {lam, [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType},
|
||||
[{'case', As, Pat, {list_comp, As, Yield, Rest}},
|
||||
{'case', As, {id, As, "_"}, {list, As, []}}]})},
|
||||
[{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]},
|
||||
{'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})},
|
||||
{def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]),
|
||||
{def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]};
|
||||
expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) ->
|
||||
@@ -681,9 +683,9 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) ->
|
||||
expr_to_fcode(Env, Else));
|
||||
|
||||
%% Switch
|
||||
expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
|
||||
expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
|
||||
Switch = fun(X) ->
|
||||
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}
|
||||
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)}
|
||||
end,
|
||||
case E of
|
||||
{id, _, X} -> Switch(X);
|
||||
@@ -796,6 +798,13 @@ make_if(Cond, Then, Else) ->
|
||||
X = fresh_name(),
|
||||
{'let', X, Cond, make_if({var, X}, Then, Else)}.
|
||||
|
||||
make_if_no_else({var, X}, Then) ->
|
||||
{switch, {split, boolean, X,
|
||||
[{'case', {bool, true}, {nosplit, Then}}]}};
|
||||
make_if_no_else(Cond, Then) ->
|
||||
X = fresh_name(),
|
||||
{'let', X, Cond, make_if_no_else({var, X}, Then)}.
|
||||
|
||||
-spec make_tuple([fexpr()]) -> fexpr().
|
||||
make_tuple([E]) -> E;
|
||||
make_tuple(Es) -> {tuple, Es}.
|
||||
@@ -861,9 +870,9 @@ is_first_order(_) -> true.
|
||||
|
||||
%% -- Pattern matching --
|
||||
|
||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit().
|
||||
alts_to_fcode(Env, Type, X, Alts) ->
|
||||
FAlts = [alt_to_fcode(Env, Alt) || Alt <- Alts],
|
||||
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
|
||||
alts_to_fcode(Env, Type, X, Alts, Switch) ->
|
||||
FAlts = remove_guards(Env, Alts, Switch),
|
||||
split_tree(Env, [{X, Type}], FAlts).
|
||||
|
||||
%% Intermediate format before case trees (fcase() and fsplit()).
|
||||
@@ -874,7 +883,43 @@ alts_to_fcode(Env, Type, X, Alts) ->
|
||||
| {string, binary()}
|
||||
| nil | {'::', fpat(), fpat()}
|
||||
| {tuple, [fpat()]}
|
||||
| {con, arities(), tag(), [fpat()]}.
|
||||
| {con, arities(), tag(), [fpat()]}
|
||||
| {assign, fpat(), fpat()}.
|
||||
|
||||
remove_guards(_Env, [], _Switch) ->
|
||||
[];
|
||||
remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) ->
|
||||
[alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)];
|
||||
remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], Switch = {switch, Ann, Expr, _}) ->
|
||||
FPat = pat_to_fcode(Env, Pat),
|
||||
FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard),
|
||||
FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body),
|
||||
case Guards of
|
||||
[] ->
|
||||
R = case GuardedBodies of
|
||||
[] -> Rest;
|
||||
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
|
||||
end,
|
||||
case R of
|
||||
[] ->
|
||||
[{'case', [FPat], make_if_no_else(FGuard, FBody)} | remove_guards(Env, Rest, Switch)];
|
||||
_ ->
|
||||
FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}),
|
||||
[{'case', [FPat], make_if(FGuard, FBody, FSwitch)} | remove_guards(Env, Rest, Switch)]
|
||||
end;
|
||||
_ ->
|
||||
R1 = case GuardedBodies of
|
||||
[] -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body}]} | Rest];
|
||||
_ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest]
|
||||
end,
|
||||
R2 = case GuardedBodies of
|
||||
[] -> Rest;
|
||||
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
|
||||
end,
|
||||
FSwitch1 = expr_to_fcode(Env, {switch, Ann, Expr, R1}),
|
||||
FSwitch2 = expr_to_fcode(Env, {switch, Ann, Expr, R2}),
|
||||
[{'case', [FPat], make_if(FGuard, FSwitch1, FSwitch2)} | remove_guards(Env, Rest, Switch)]
|
||||
end.
|
||||
|
||||
%% %% Invariant: the number of variables matches the number of patterns in each falt.
|
||||
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
|
||||
@@ -974,6 +1019,8 @@ split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]};
|
||||
split_pat({con, As, I, Pats}) ->
|
||||
Xs = [fresh_name() || _ <- Pats],
|
||||
{{con, As, I, Xs}, Pats};
|
||||
split_pat({assign, X = {var, _}, P}) ->
|
||||
{{assign, fresh_name(), fresh_name()}, [X, P]};
|
||||
split_pat({tuple, Pats}) ->
|
||||
Xs = [fresh_name() || _ <- Pats],
|
||||
{{tuple, Xs}, Pats}.
|
||||
@@ -984,6 +1031,7 @@ split_vars({int, _}, integer) -> [];
|
||||
split_vars({string, _}, string) -> [];
|
||||
split_vars(nil, {list, _}) -> [];
|
||||
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
|
||||
split_vars({assign, X, P}, T) -> [{X, T}, {P, T}];
|
||||
split_vars({con, _, I, Xs}, {variant, Cons}) ->
|
||||
lists:zip(Xs, lists:nth(I + 1, Cons));
|
||||
split_vars({tuple, Xs}, {tuple, Ts}) ->
|
||||
@@ -999,7 +1047,7 @@ next_split(Pats) ->
|
||||
end.
|
||||
|
||||
-spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt().
|
||||
alt_to_fcode(Env, {'case', _, Pat, Expr}) ->
|
||||
alt_to_fcode(Env, {'case', _, Pat, [{guarded, _, [], Expr}]}) ->
|
||||
FPat = pat_to_fcode(Env, Pat),
|
||||
FExpr = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Expr),
|
||||
{'case', [FPat], FExpr}.
|
||||
@@ -1039,6 +1087,8 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) ->
|
||||
end end,
|
||||
make_tuple([pat_to_fcode(Env, FieldPat(Field))
|
||||
|| Field <- Fields]);
|
||||
pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) ->
|
||||
{assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)};
|
||||
|
||||
pat_to_fcode(_Env, Type, Pat) ->
|
||||
error({todo, Pat, ':', Type}).
|
||||
@@ -1075,8 +1125,8 @@ decision_tree_to_fcode({'if', A, Then, Else}) ->
|
||||
stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, Expr} | Stmts]) ->
|
||||
{'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)};
|
||||
stmts_to_fcode(Env, [{letval, Ann, Pat, Expr} | Stmts]) ->
|
||||
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, {block, Ann, Stmts}}]});
|
||||
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, Expr} | Stmts]) ->
|
||||
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [{guarded, Ann, [], {block, Ann, Stmts}}]}]});
|
||||
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Expr}]} | Stmts]) ->
|
||||
LamArgs = [ case Arg of
|
||||
{typed, Ann1, Id, T} -> {arg, Ann1, Id, T};
|
||||
_ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared
|
||||
@@ -1529,6 +1579,7 @@ match_pat(L, {lit, L}) -> [];
|
||||
match_pat(nil, nil) -> [];
|
||||
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
|
||||
match_pat({var, X}, E) -> [{X, E}];
|
||||
match_pat({assign, X, P}, E) -> [{X, E}, {P, E}];
|
||||
match_pat(_, _) -> false.
|
||||
|
||||
constructor_form(Env, Expr) ->
|
||||
@@ -1764,6 +1815,7 @@ pat_vars(nil) -> [];
|
||||
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
|
||||
pat_vars({tuple, Ps}) -> pat_vars(Ps);
|
||||
pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
|
||||
pat_vars({assign, X, P}) -> pat_vars(X) ++ pat_vars(P);
|
||||
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
|
||||
|
||||
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
|
||||
@@ -1984,7 +2036,11 @@ rename_spat(Ren, {con, Ar, C, Xs}) ->
|
||||
{{con, Ar, C, Zs}, Ren1};
|
||||
rename_spat(Ren, {tuple, Xs}) ->
|
||||
{Zs, Ren1} = rename_bindings(Ren, Xs),
|
||||
{{tuple, Zs}, Ren1}.
|
||||
{{tuple, Zs}, Ren1};
|
||||
rename_spat(Ren, {assign, X, P}) ->
|
||||
{X1, Ren1} = rename_binding(Ren, X),
|
||||
{P1, Ren2} = rename_binding(Ren1, P),
|
||||
{{assign, X1, P1}, Ren2}.
|
||||
|
||||
rename_split(Ren, {split, Type, X, Cases}) ->
|
||||
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};
|
||||
|
||||
@@ -96,7 +96,7 @@ contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | R
|
||||
_ -> Icode1
|
||||
end,
|
||||
contract_to_icode(Rest, Icode2);
|
||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
|
||||
contract_to_icode([{letfun, Attrib, Name, Args, _What, [{guarded, _, [], Body={typed,_,_,T}}]}|Rest], Icode) ->
|
||||
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
|
||||
[ payable || proplists:get_value(payable, Attrib, false) ] ++
|
||||
[ private || is_private(Attrib, Icode) ],
|
||||
@@ -323,8 +323,8 @@ ast_body({list_comp, _, Yield, []}, Icode) ->
|
||||
ast_body({list_comp, As, Yield, [{comprehension_bind, {typed, _, Pat, ArgType}, BindExpr}|Rest]}, Icode) ->
|
||||
Arg = "%lc",
|
||||
Body = {switch, As, {typed, As, {id, As, Arg}, ArgType},
|
||||
[{'case', As, Pat, {list_comp, As, Yield, Rest}},
|
||||
{'case', As, {id, As, "_"}, {list, As, []}}]},
|
||||
[{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]},
|
||||
{'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]},
|
||||
#funcall
|
||||
{ function = #var_ref{ name = ["ListInternal", "flat_map"] }
|
||||
, args =
|
||||
@@ -349,14 +349,14 @@ ast_body({switch,_,A,Cases}, Icode) ->
|
||||
%% patterns appear in cases.
|
||||
#switch{expr=ast_body(A, Icode),
|
||||
cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)}
|
||||
|| {'case',_,Pat,Body} <- Cases]};
|
||||
|| {'case',_,Pat,[{guarded, _, [], Body}]} <- Cases]};
|
||||
ast_body({block, As, [{letval, _, Pat, E} | Rest]}, Icode) ->
|
||||
E1 = ast_body(E, Icode),
|
||||
Pat1 = ast_body(Pat, Icode),
|
||||
Rest1 = ast_body({block, As, Rest}, Icode),
|
||||
#switch{expr = E1,
|
||||
cases = [{Pat1, Rest1}]};
|
||||
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
|
||||
ast_body({block, As, [{letfun, Ann, F, Args, _Type, [{guarded, _, [], Expr}]} | Rest]}, Icode) ->
|
||||
ToArg = fun({typed, Ann1, Id, T}) -> {arg, Ann1, Id, T} end, %% Pattern matching has been desugared
|
||||
LamArgs = lists:map(ToArg, Args),
|
||||
ast_body({block, As, [{letval, Ann, F, {lam, Ann, LamArgs, Expr}} | Rest]}, Icode);
|
||||
|
||||
@@ -475,9 +475,9 @@ error_missing_call_function() ->
|
||||
get_call_type([{Contract, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
|
||||
case [ {lists:last(QFunName), FunType}
|
||||
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
|
||||
{typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
|
||||
[{guarded, _, [], {typed, _,
|
||||
{app, _,
|
||||
{typed, _, {qid, _, QFunName}, FunType}, _}, _}}]} <- Defs ] of
|
||||
[Call] -> {ok, Call};
|
||||
[] -> error_missing_call_function()
|
||||
end;
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
false -> fail()
|
||||
end).
|
||||
|
||||
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
|
||||
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
|
||||
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
|
||||
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
||||
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
||||
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})).
|
||||
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
|
||||
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
|
||||
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
|
||||
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
|
||||
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
|
||||
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F} )).
|
||||
-define(RULE(A, B, C, D, E, F, G, Do), map(fun({_1, _2, _3, _4, _5, _6, _7}) -> Do end, {A, B, C, D, E, F, G} )).
|
||||
-define(RULE(A, B, C, D, E, F, G, H, Do), map(fun({_1, _2, _3, _4, _5, _6, _7, _8}) -> Do end, {A, B, C, D, E, F, G, H})).
|
||||
|
||||
-import(aeso_parse_lib,
|
||||
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
|
||||
|
||||
+39
-4
@@ -109,6 +109,7 @@ decl() ->
|
||||
|
||||
, ?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 +136,21 @@ fundef_or_decl() ->
|
||||
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
|
||||
fundef()]).
|
||||
|
||||
using() ->
|
||||
Alias = {keyword(as), con()},
|
||||
For = ?RULE(keyword(for), bracket_list(id()), {for, _2}),
|
||||
Hiding = ?RULE(keyword(hiding), bracket_list(id()), {hiding, _2}),
|
||||
?RULE(keyword(using), con(), optional(Alias), optional(choice(For, Hiding)), using(get_ann(_1), _2, _3, _4)).
|
||||
|
||||
using(Ann, Con, none, none) ->
|
||||
{using, Ann, Con, none, none};
|
||||
using(Ann, Con, {ok, {_, Alias}}, none) ->
|
||||
{using, Ann, Con, Alias, none};
|
||||
using(Ann, Con, none, {ok, List}) ->
|
||||
{using, Ann, Con, none, List};
|
||||
using(Ann, Con, {ok, {_, Alias}}, {ok, List}) ->
|
||||
{using, Ann, Con, Alias, List}.
|
||||
|
||||
pragma() ->
|
||||
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
|
||||
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
|
||||
@@ -195,10 +211,16 @@ letdef() -> choice(valdef(), fundef()).
|
||||
valdef() ->
|
||||
?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}).
|
||||
|
||||
guarded_fundefs() ->
|
||||
choice(
|
||||
[ ?RULE(keyword('='), body(), [{guarded, _1, [], _2}])
|
||||
, maybe_block(?RULE(keyword('|'), comma_sep(expr()), tok('='), body(), {guarded, _1, _2, _4}))
|
||||
]).
|
||||
|
||||
fundef() ->
|
||||
choice(
|
||||
[ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4})
|
||||
, ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6})
|
||||
[ ?RULE(id(), args(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _3})
|
||||
, ?RULE(id(), args(), tok(':'), type(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, _4, _5})
|
||||
]).
|
||||
|
||||
args() -> paren_list(pattern()).
|
||||
@@ -208,6 +230,9 @@ arg() -> choice(
|
||||
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
|
||||
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
|
||||
|
||||
letpat() ->
|
||||
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
|
||||
|
||||
%% -- Types ------------------------------------------------------------------
|
||||
|
||||
type_vars() -> paren_list(tvar()).
|
||||
@@ -254,7 +279,8 @@ body() ->
|
||||
|
||||
stmt() ->
|
||||
?LAZY_P(choice(
|
||||
[ expr()
|
||||
[ using()
|
||||
, expr()
|
||||
, letdecl()
|
||||
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
|
||||
, {'if', keyword('if'), parens(expr()), body()}
|
||||
@@ -263,7 +289,13 @@ stmt() ->
|
||||
])).
|
||||
|
||||
branch() ->
|
||||
?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}).
|
||||
?RULE(pattern(), guarded_branches(), {'case', get_ann(lists:nth(1, _2)), _1, _2}).
|
||||
|
||||
guarded_branches() ->
|
||||
choice(
|
||||
[ ?RULE(keyword('=>'), body(), [{guarded, _1, [], _2}])
|
||||
, maybe_block(?RULE(tok('|'), comma_sep(expr()), keyword('=>'), body(), {guarded, _3, _2, _4}))
|
||||
]).
|
||||
|
||||
pattern() ->
|
||||
?LET_P(E, expr(), parse_pattern(E)).
|
||||
@@ -311,6 +343,7 @@ exprAtom() ->
|
||||
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
|
||||
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
||||
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
||||
, letpat()
|
||||
])
|
||||
end).
|
||||
|
||||
@@ -571,6 +604,8 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
||||
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
|
||||
|
||||
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
|
||||
parse_pattern({letpat, Ann, Id, Pat}) ->
|
||||
{letpat, Ann, Id, parse_pattern(Pat)};
|
||||
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
|
||||
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
|
||||
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
|
||||
|
||||
+18
-4
@@ -212,8 +212,10 @@ name({typed, _, Name, _}) -> name(Name).
|
||||
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
|
||||
letdecl(Let, {letval, _, P, E}) ->
|
||||
block_expr(0, hsep([text(Let), expr(P), text("=")]), E);
|
||||
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
||||
block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E).
|
||||
letdecl(Let, {letfun, _, F, Args, T, [GuardedBody]}) ->
|
||||
beside(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), guarded_body(GuardedBody, "="));
|
||||
letdecl(Let, {letfun, _, F, Args, T, GuardedBodies}) ->
|
||||
block(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), above(lists:map(fun(GB) -> guarded_body(GB, "=") end, GuardedBodies))).
|
||||
|
||||
-spec args([aeso_syntax:arg()]) -> doc().
|
||||
args(Args) ->
|
||||
@@ -299,6 +301,8 @@ tuple_type(Factors) ->
|
||||
]).
|
||||
|
||||
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
|
||||
expr_p(P, {letpat, _, Id, Pat}) ->
|
||||
paren(P > 100, follow(hsep(expr(Id), text("=")), expr(Pat)));
|
||||
expr_p(P, {named_arg, _, Name, E}) ->
|
||||
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
|
||||
expr_p(P, {lam, _, Args, E}) ->
|
||||
@@ -480,8 +484,18 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj));
|
||||
elim1(Get={map_get, _, _}) -> elim(Get);
|
||||
elim1(Get={map_get, _, _, _}) -> elim(Get).
|
||||
|
||||
alt({'case', _, Pat, Body}) ->
|
||||
block_expr(0, hsep(expr(Pat), text("=>")), Body).
|
||||
alt({'case', _, Pat, [GuardedBody]}) ->
|
||||
beside(expr(Pat), guarded_body(GuardedBody, "=>"));
|
||||
alt({'case', _, Pat, GuardedBodies}) ->
|
||||
block(expr(Pat), above(lists:map(fun(GB) -> guarded_body(GB, "=>") end, GuardedBodies))).
|
||||
|
||||
guarded_body({guarded, _, Guards, Body}, Then) ->
|
||||
block_expr(0, hsep(guards(Guards), text(Then)), Body).
|
||||
|
||||
guards([]) ->
|
||||
text("");
|
||||
guards(Guards) ->
|
||||
hsep([text(" |"), par(punctuate(text(","), lists:map(fun expr/1, Guards)), 0)]).
|
||||
|
||||
block_expr(_, Header, {block, _, Ss}) ->
|
||||
block(Header, statements(Ss));
|
||||
|
||||
+1
-1
@@ -45,7 +45,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"
|
||||
],
|
||||
KW = string:join(Keywords, "|"),
|
||||
|
||||
|
||||
+12
-3
@@ -35,6 +35,9 @@
|
||||
-type qcon() :: {qcon, ann(), [name()]}.
|
||||
-type tvar() :: {tvar, ann(), name()}.
|
||||
|
||||
-type namespace_alias() :: none | con().
|
||||
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
|
||||
|
||||
-type decl() :: {contract_main, ann(), con(), [decl()]}
|
||||
| {contract_child, ann(), con(), [decl()]}
|
||||
| {contract_interface, ann(), con(), [decl()]}
|
||||
@@ -44,6 +47,7 @@
|
||||
| {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 +56,12 @@
|
||||
|
||||
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
|
||||
|
||||
-type guard() :: expr().
|
||||
-type guarded_expr() :: {guarded, ann(), [guard()], expr()}.
|
||||
|
||||
-type letval() :: {letval, ann(), pat(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
|
||||
-type letfun() :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}.
|
||||
-type letpat() :: {letpat, ann(), id(), pat()}.
|
||||
-type fundecl() :: {fun_decl, ann(), id(), type()}.
|
||||
|
||||
-type letbind()
|
||||
@@ -119,7 +126,8 @@
|
||||
| {block, ann(), [stmt()]}
|
||||
| {op(), ann()}
|
||||
| id() | qid() | con() | qcon()
|
||||
| constant().
|
||||
| constant()
|
||||
| letpat().
|
||||
|
||||
-type record_or_map() :: record | map | record_or_map_error.
|
||||
|
||||
@@ -140,7 +148,7 @@
|
||||
-type stmt() :: letbind()
|
||||
| expr().
|
||||
|
||||
-type alt() :: {'case', ann(), pat(), expr()}.
|
||||
-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.
|
||||
|
||||
-type lvalue() :: nonempty_list(elim()).
|
||||
|
||||
@@ -153,6 +161,7 @@
|
||||
| {list, ann(), [pat()]}
|
||||
| {typed, ann(), pat(), type()}
|
||||
| {record, ann(), [field(pat())]}
|
||||
| letpat()
|
||||
| constant()
|
||||
| con()
|
||||
| id().
|
||||
|
||||
@@ -41,15 +41,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
Top = Fun(K, X),
|
||||
Rec = case X of
|
||||
%% lists (bound things in head scope over tail)
|
||||
[A | As] -> Scoped(Same(A), Same(As));
|
||||
[A | As] -> Scoped(Same(A), Same(As));
|
||||
%% decl()
|
||||
{contract, _, _, Ds} -> Decl(Ds);
|
||||
{namespace, _, _, Ds} -> Decl(Ds);
|
||||
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
||||
{fun_decl, _, _, T} -> Type(T);
|
||||
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
|
||||
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
|
||||
{contract, _, _, Ds} -> Decl(Ds);
|
||||
{namespace, _, _, Ds} -> Decl(Ds);
|
||||
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
|
||||
{fun_decl, _, _, T} -> Type(T);
|
||||
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{letfun, _, F, Xs, T, GEs} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ GEs)]);
|
||||
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
|
||||
%% typedef()
|
||||
{alias_t, T} -> Type(T);
|
||||
{record_t, Fs} -> Type(Fs);
|
||||
@@ -88,13 +88,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
{map_get, _, A, B} -> Expr([A, B]);
|
||||
{map_get, _, A, B, C} -> Expr([A, B, C]);
|
||||
{block, _, Ss} -> Expr(Ss);
|
||||
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
|
||||
{guarded, _, Gs, E} -> Expr([E | Gs]);
|
||||
%% field()
|
||||
{field, _, LV, E} -> Expr([LV, E]);
|
||||
{field, _, LV, _, E} -> Expr([LV, E]);
|
||||
%% arg()
|
||||
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
|
||||
%% alt()
|
||||
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
|
||||
{'case', _, P, GEs} -> Scoped(BindExpr(P), Expr(GEs));
|
||||
%% elim()
|
||||
{proj, _, _} -> Zero;
|
||||
{map_get, _, E} -> Expr(E);
|
||||
|
||||
@@ -120,10 +120,98 @@ from_fate({constr_t, _, Con, Types}, Args)
|
||||
when length(Types) == length(Args) ->
|
||||
{app, [], Con, [ from_fate(Type, Arg)
|
||||
|| {Type, Arg} <- lists:zip(Types, Args) ]};
|
||||
from_fate({qid, _, QType}, Val) ->
|
||||
from_fate_builtin(QType, Val);
|
||||
from_fate(_Type, _Data) ->
|
||||
throw(cannot_translate_to_sophia).
|
||||
|
||||
|
||||
from_fate_builtin(QType, Val) ->
|
||||
Con = fun([Name | _] = Names) when is_list(Name) -> {qcon, [], Names};
|
||||
(Name) -> {con, [], Name} end,
|
||||
App = fun(Name, []) -> Con(Name);
|
||||
(Name, Value) -> {app, [], Con(Name), Value} end,
|
||||
Chk = fun(Type, Value) -> from_fate(Type, Value) end,
|
||||
Int = {id, [], "int"},
|
||||
Str = {id, [], "string"},
|
||||
Adr = {id, [], "address"},
|
||||
Hsh = {bytes_t, [], 32},
|
||||
Qid = fun(Name) -> {qid, [], Name} end,
|
||||
Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end,
|
||||
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
|
||||
case {QType, Val} of
|
||||
{["Chain", "ttl"], {variant, [1, 1], 0, {X}}} -> App("RelativeTTL", [Chk(Int, X)]);
|
||||
{["Chain", "ttl"], {variant, [1, 1], 1, {X}}} -> App("FixedTTL", [Chk(Int, X)]);
|
||||
|
||||
{["AENS", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} ->
|
||||
App(["AENS","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL),
|
||||
Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]);
|
||||
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 0, {Addr}}} ->
|
||||
App(["AENS","AccountPt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 1, {Addr}}} ->
|
||||
App(["AENS","OraclePt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 2, {Addr}}} ->
|
||||
App(["AENS","ContractPt"], [Chk(Adr, Addr)]);
|
||||
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 3, {Addr}}} ->
|
||||
App(["AENS","ChannelPt"], [Chk(Adr, Addr)]);
|
||||
|
||||
{["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} ->
|
||||
App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]);
|
||||
|
||||
{["Chain", "paying_for_tx"], {variant, [2], 0, {Addr, X}}} ->
|
||||
App(["Chain","PayingForTx"], [Chk(Adr, Addr), Chk(Int, X)]);
|
||||
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 0, {Addr, Fee, Payload}}} ->
|
||||
App(["Chain","SpendTx"], [Chk(Adr, Addr), Chk(Int, Fee), Chk(Str, Payload)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 1, {}}} ->
|
||||
App(["Chain","OracleRegisterTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 2, {}}} ->
|
||||
App(["Chain","OracleQueryTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 3, {}}} ->
|
||||
App(["Chain","OracleResponseTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 4, {}}} ->
|
||||
App(["Chain","OracleExtendTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 5, {}}} ->
|
||||
App(["Chain","NamePreclaimTx"], []);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 6, {Name}}} ->
|
||||
App(["Chain","NameClaimTx"], [Chk(Str, Name)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 7, {NameHash}}} ->
|
||||
App(["Chain","NameUpdateTx"], [Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 8, {NameHash}}} ->
|
||||
App(["Chain","NameRevokeTx"], [Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 9, {NewOwner, NameHash}}} ->
|
||||
App(["Chain","NameTransferTx"], [Chk(Adr, NewOwner), Chk(Hsh, NameHash)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 10, {Addr}}} ->
|
||||
App(["Chain","ChannelCreateTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 11, {Addr, Amount}}} ->
|
||||
App(["Chain","ChannelDepositTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 12, {Addr, Amount}}} ->
|
||||
App(["Chain","ChannelWithdrawTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 13, {Addr}}} ->
|
||||
App(["Chain","ChannelForceProgressTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 14, {Addr}}} ->
|
||||
App(["Chain","ChannelCloseMutualTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 15, {Addr}}} ->
|
||||
App(["Chain","ChannelCloseSoloTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 16, {Addr}}} ->
|
||||
App(["Chain","ChannelSlashTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 17, {Addr}}} ->
|
||||
App(["Chain","ChannelSettleTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 18, {Addr}}} ->
|
||||
App(["Chain","ChannelSnapshotSoloTx"], [Chk(Adr, Addr)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 19, {Amount}}} ->
|
||||
App(["Chain","ContractCreateTx"], [Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 20, {Addr, Amount}}} ->
|
||||
App(["Chain","ContractCallTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
|
||||
{["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} ->
|
||||
App(["Chain","GAAttachTx"], []);
|
||||
|
||||
_ ->
|
||||
throw(cannot_translate_to_sophia)
|
||||
end.
|
||||
|
||||
make_bits(N) ->
|
||||
Id = fun(F) -> {qid, [], ["Bits", F]} end,
|
||||
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "6.0.1"},
|
||||
{vsn, "6.1.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -33,7 +33,7 @@ test_cases(1) ->
|
||||
stateful => true,
|
||||
payable => true}]}},
|
||||
DecACI = <<"payable main contract C =\n"
|
||||
" payable entrypoint a : (int) => int\n">>,
|
||||
" payable stateful entrypoint a : (int) => int\n">>,
|
||||
{Contract,MapACI,DecACI};
|
||||
|
||||
test_cases(2) ->
|
||||
|
||||
@@ -59,7 +59,7 @@ calldata_aci_test_() ->
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
parse_args(Fun, Args) ->
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
|
||||
[{contract_main, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
|
||||
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
|
||||
strip_ann(AST).
|
||||
|
||||
@@ -112,8 +112,28 @@ compilable_contracts() ->
|
||||
{"funargs", "traffic_light", ["Green"]},
|
||||
{"funargs", "traffic_light", ["Pantone(12)"]},
|
||||
{"funargs", "tuples", ["()"]},
|
||||
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
|
||||
{"funargs", "due", ["FixedTTL(1020)"]},
|
||||
{"funargs", "singleton_rec", ["{x = 1000}"]},
|
||||
{"funargs", "aens_name", ["AENS.Name(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, RelativeTTL(100), {[\"pt1\"] = AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)})"]},
|
||||
{"funargs", "aens_pointee", ["AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.OraclePt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.ContractPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "aens_pointee", ["AENS.ChannelPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
|
||||
{"funargs", "chain_ga_meta_tx", ["Chain.GAMetaTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
|
||||
{"funargs", "chain_paying_for_tx", ["Chain.PayingForTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.SpendTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42,\"foo\")"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.ContractCreateTx(12234)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.ContractCallTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 12234)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleRegisterTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleQueryTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleResponseTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.OracleExtendTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NamePreclaimTx"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameClaimTx(\"acoolname.chain\")"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameUpdateTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
|
||||
{"funargs", "chain_base_tx", ["Chain.GAAttachTx"]},
|
||||
{"variant_types", "init", []},
|
||||
{"basic_auth", "init", []},
|
||||
{"address_literals", "init", []},
|
||||
|
||||
@@ -199,6 +199,10 @@ compilable_contracts() ->
|
||||
"clone",
|
||||
"clone_simple",
|
||||
"create",
|
||||
"child_contract_init_bug",
|
||||
"using_namespace",
|
||||
"assign_patterns",
|
||||
"patterns_guards",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
@@ -234,6 +238,7 @@ failing_contracts() ->
|
||||
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
|
||||
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
|
||||
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
|
||||
, ?PARSE_ERROR(assign_pattern_to_pattern, [<<?Pos(3, 22) "Unexpected token '='.">>])
|
||||
|
||||
%% Type errors
|
||||
, ?TYPE_ERROR(name_clash,
|
||||
@@ -780,6 +785,38 @@ failing_contracts() ->
|
||||
[<<?Pos(1,6)
|
||||
"Only one main contract can be defined.">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_ambiguous_name,
|
||||
[ <<?Pos(2,3)
|
||||
"Ambiguous name: Xa.f at line 2, column 3\nXb.f at line 5, column 3">>
|
||||
, <<?Pos(13,23)
|
||||
"Unbound variable A.f at line 13, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_wrong_scope,
|
||||
[ <<?Pos(19,5)
|
||||
"Unbound variable f at line 19, column 5">>
|
||||
, <<?Pos(21,23)
|
||||
"Unbound variable f at line 21, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined,
|
||||
[<<?Pos(2,3)
|
||||
"Cannot use undefined namespace MyUndefinedNamespace">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_undefined_parts,
|
||||
[<<?Pos(5,3)
|
||||
"The namespace Nsp does not define the following names: a">>
|
||||
])
|
||||
, ?TYPE_ERROR(using_namespace_hidden_parts,
|
||||
[<<?Pos(8,23)
|
||||
"Unbound variable g at line 8, column 23">>
|
||||
])
|
||||
, ?TYPE_ERROR(stateful_pattern_guard,
|
||||
[<<?Pos(8,12)
|
||||
"Cannot reference stateful function g (at line 8, column 12) in a pattern guard.">>
|
||||
])
|
||||
, ?TYPE_ERROR(non_boolean_pattern_guard,
|
||||
[<<?Pos(4,24)
|
||||
"Cannot unify string\n and bool\nwhen checking the type of the expression at line 4, column 24\n \"y\" : string\nagainst the expected type\n bool">>
|
||||
])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
|
||||
@@ -17,7 +17,7 @@ simple_contracts_test_() ->
|
||||
?assertMatch(
|
||||
[{contract_main, _, {con, _, "Identity"},
|
||||
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
|
||||
{id, _, "x"}}]}], parse_string(Text)),
|
||||
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
|
||||
ok
|
||||
end},
|
||||
{"Operator precedence test.",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contract AssignPatternToPattern =
|
||||
entrypoint f() =
|
||||
let x::(t::z = y) = [1, 2, 3]
|
||||
(x + t)::y
|
||||
@@ -0,0 +1,16 @@
|
||||
include "List.aes"
|
||||
|
||||
contract AssignPatterns =
|
||||
|
||||
entrypoint test() = foo([1, 0, 2], (2, Some(3)), Some([4, 5]))
|
||||
|
||||
entrypoint foo(xs : list(int), p : int * option(int), some : option(list(int))) =
|
||||
let x::(t = y::_) = xs
|
||||
let z::_ = t
|
||||
|
||||
let (a, (o = Some(b))) = p
|
||||
|
||||
let Some((f = g::_)) = some
|
||||
g + List.get(1, f)
|
||||
|
||||
x + y + z + a + b
|
||||
@@ -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()
|
||||
@@ -50,3 +50,10 @@ contract FunctionArguments =
|
||||
|
||||
entrypoint singleton_rec(r : singleton_r) =
|
||||
r.x
|
||||
|
||||
entrypoint aens_name(n : AENS.name) = true
|
||||
entrypoint aens_pointee(p : AENS.pointee) = true
|
||||
|
||||
entrypoint chain_ga_meta_tx(tx : Chain.ga_meta_tx) = true
|
||||
entrypoint chain_paying_for_tx(tx : Chain.paying_for_tx) = true
|
||||
entrypoint chain_base_tx(tx : Chain.base_tx) = true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init(x) | "y" = 1
|
||||
@@ -0,0 +1,19 @@
|
||||
include "List.aes"
|
||||
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init() = f([1, 2, 3, 4])
|
||||
|
||||
function
|
||||
f(x::[])
|
||||
| x > 1, x < 10 = 1
|
||||
| x < 1 = 9
|
||||
f(x::y::[]) = 2
|
||||
f(x::y::z) = switch(z)
|
||||
[] => 4
|
||||
a::[]
|
||||
| a > 10, a < 20 => 5
|
||||
| a > 5 => 8
|
||||
b | List.length(b) > 5 => 6
|
||||
c => 7
|
||||
@@ -0,0 +1,10 @@
|
||||
contract C =
|
||||
type state = int
|
||||
|
||||
entrypoint init() = f(4)
|
||||
|
||||
function
|
||||
f(x) | x > 0 = 1
|
||||
f(x) | g(x) = 2
|
||||
|
||||
stateful function g(x) = x < 0
|
||||
@@ -0,0 +1,36 @@
|
||||
include "Option.aes"
|
||||
include "Pair.aes"
|
||||
include "String.aes"
|
||||
include "Triple.aes"
|
||||
|
||||
using Pair
|
||||
using Triple hiding [fst, snd]
|
||||
|
||||
namespace Nsp =
|
||||
using Option
|
||||
|
||||
function h() =
|
||||
let op = Some((2, 3, 4))
|
||||
if (is_some(op))
|
||||
thd(force(op)) == 4
|
||||
else
|
||||
false
|
||||
|
||||
contract Cntr =
|
||||
using Nsp
|
||||
|
||||
entrypoint init() = ()
|
||||
|
||||
function f() =
|
||||
let p = (1, 2)
|
||||
if (h())
|
||||
fst(p)
|
||||
else
|
||||
snd(p)
|
||||
|
||||
function g() =
|
||||
using String for [concat]
|
||||
|
||||
let s1 = "abc"
|
||||
let s2 = "def"
|
||||
concat(s1, s2)
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Xa =
|
||||
function f() = 1
|
||||
|
||||
namespace Xb =
|
||||
function f() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Xa as A
|
||||
using Xb as A
|
||||
|
||||
type state = int
|
||||
|
||||
entrypoint init() = A.f()
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Nsp =
|
||||
function f() = 1
|
||||
function g() = 2
|
||||
|
||||
contract Cntr =
|
||||
using Nsp for [f]
|
||||
|
||||
entrypoint init() = g()
|
||||
@@ -0,0 +1,4 @@
|
||||
contract C =
|
||||
using MyUndefinedNamespace
|
||||
|
||||
entrypoint init() = ()
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Nsp =
|
||||
function f() = 1
|
||||
|
||||
contract Cntr =
|
||||
using Nsp for [a]
|
||||
|
||||
entrypoint init() = f()
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Nsp1 =
|
||||
function f() = 1
|
||||
|
||||
namespace Nsp2 =
|
||||
using Nsp1
|
||||
|
||||
function g() = 1
|
||||
|
||||
contract Cntr =
|
||||
using Nsp2
|
||||
|
||||
type state = int
|
||||
|
||||
function x() =
|
||||
using Nsp1
|
||||
f()
|
||||
|
||||
function y() =
|
||||
f()
|
||||
|
||||
entrypoint init() = f()
|
||||
Reference in New Issue
Block a user