Compare commits

...

123 Commits

Author SHA1 Message Date
Hans Svensson ef761a4c57 Merge pull request #56 from aeternity/prepare_2.1.0
Preparing v2.1.0
2019-04-11 14:22:54 +02:00
Hans Svensson 330d8929fd Preparing v2.1.0 2019-04-11 13:42:41 +02:00
Thomas Arts 491b1211d1 Merge pull request #55 from aeternity/PT-165246396-prepare-remove-dependency
Update commit hash aebytecode
2019-04-11 10:30:41 +02:00
Thomas Arts e460b84bd0 Update commit hash aebytecode 2019-04-11 09:24:43 +02:00
Hans Svensson 9109712826 Merge pull request #53 from aeternity/generalized_accounts
Add Auth.tx_hash - namespace + primop
2019-04-08 14:45:56 +02:00
Hans Svensson d6a55e144e Test Auth.tx_hash compilation 2019-04-08 11:57:07 +02:00
Hans Svensson db64978d2e Add Auth.tx_hash 2019-04-08 11:57:07 +02:00
Hans Svensson 2ed9d17ce5 Switch to generalized_accounts branch of aebytecode 2019-04-08 11:57:07 +02:00
Robert Virding 7bf7cb0b8f Merge pull request #52 from aeternity/new-aci-generator
PT-163022973 Make a new aci generator
2019-04-05 15:14:17 +02:00
Robert Virding 4a01c852c9 Add more test cases
And some trivial code cleanup.
2019-04-03 22:58:54 +02:00
Robert Virding df00c3958b First version of final aci
Should have more test cases and code cleanup
2019-04-03 17:53:16 +02:00
Robert Virding 12cb37245b First version of new aci generator
We also include some updated simple tests which are only run by eunit.
2019-04-03 17:53:16 +02:00
Tino Breddin 562ad5ee87 Merge pull request #51 from aeternity/PT-165081160-update-rebar3
Upgrade rebar3 to 3.9.1-aeternity.2
2019-04-03 11:00:26 +02:00
Tino Breddin 4e78756b90 Upgrade rebar3 to 3.9.1-aeternity.2
Reference: https://github.com/aeternity/rebar3/releases/tag/3.9.1-aeternity.2
2019-04-03 10:23:02 +02:00
Robert Virding 9f32fb1925 Merge pull request #50 from aeternity/PT-164597852-move-aesophia-heap
PT-164597852 Move aesophia heap handling into aebytecode
2019-04-02 17:53:28 +02:00
Robert Virding 549a0c2201 Move TYPEREP definitions to aebytecode 2019-04-02 16:03:50 +02:00
Robert Virding 9f5f8d4444 Change function references from aeso_sophia to aeb_aevm_data 2019-04-02 16:00:10 +02:00
Robert Virding fd0dbdf207 Change references from aeso_memory to aeb_memory 2019-04-02 15:59:12 +02:00
Robert Virding 0d8b7c7c79 First commit fixinng references from aeso_ to aeb_
Also remove local copies of modules moved to aebytecode.
2019-04-02 15:59:12 +02:00
Erik Stenman 3271d6fba4 Merge pull request #49 from aeternity/PT-164597736-variant-types
Pt 164597736 variant types
2019-04-02 13:59:47 +02:00
Erik Stenman 30fbcc50c5 Longer ref. 2019-04-02 13:53:54 +02:00
Erik Stenman 27bc5474cb Upgrade aebytecode to new variant type representation. 2019-04-02 12:44:51 +02:00
Hans Svensson efeb391805 Merge pull request #48 from aeternity/cleanup_blake2
Remove aeso_blake2, use eblake2
2019-04-02 10:32:41 +02:00
Hans Svensson 15ca37342c Remove aeso_blake2, use eblake2 2019-04-02 09:00:20 +02:00
Luca Favatella 8b7e4db490 Merge pull request #47 from aeternity/PT-164834227-too-verbose-build
Fix build warning
2019-04-01 12:13:28 +01:00
Luca Favatella d89fd134b5 Fix build warning
Symptom:
```
src/aeso_aci.erl:18: Warning: record namespace is unused
```
2019-03-29 15:25:21 +00:00
Luca Favatella 37dfbf78ac Remove fragile file-based versioning (#46) 2019-03-15 10:12:41 +00:00
Tobias Lindahl 188916c61f Merge pull request #45 from aeternity/PT-164655050-fail-on-removed-VERSION
Check that VERSION file exists
2019-03-15 10:15:04 +01:00
Tobias Lindahl f395649419 Check that VERSION file exists 2019-03-15 09:50:39 +01:00
Tobias Lindahl 36395b597a Merge pull request #44 from aeternity/PT-164626753-latest-aebytecode
Upgrade to latest aebytecode (and by transitivity aeserialization)
2019-03-14 11:32:29 +01:00
Tobias Lindahl 177e32c117 Upgrade to latest aebytecode (and by transitivity aeserialization) 2019-03-14 10:59:15 +01:00
Hans Svensson 4d61ee65df Merge pull request #43 from aeternity/add_decode_calldata
add aeso_compiler:decode_calldata/3
2019-03-14 09:42:27 +01:00
Hans Svensson a089af555f More better type specs 2019-03-13 19:57:27 +01:00
Hans Svensson cd116b23d7 add aeso_compiler:decode_calldata/3 2019-03-13 16:15:48 +01:00
Tobias Lindahl fba6609c3a Merge pull request #42 from aeternity/fortuna2
Merge Fortuna to master
2019-03-13 13:44:48 +01:00
Tobias Lindahl e7c477d4de Merge pull request #41 from aeternity/PT-164595944-use-latest-aebytecode
Use latest aebytecode
2019-03-13 11:34:42 +01:00
Tobias Lindahl a44b787735 Use latest aebytecode 2019-03-13 11:17:33 +01:00
Hans Svensson cf46c9e303 Merge pull request #37 from aeternity/release_notes_etc
Release notes + note on versioning
2019-03-09 13:07:31 +01:00
Erik Stenman b6a789bbbc Merge pull request #39 from aeternity/merge_master_to_fortuna
Merge master to fortuna
2019-03-05 14:51:13 +01:00
Erik Stenman ad34363673 Merge branch 'fortuna' into merge_master_to_fortuna 2019-03-05 14:35:10 +01:00
Erik Stenman 257de08100 Upgrade rebar.lock. 2019-03-05 14:32:15 +01:00
Erik Stenman 7ae4a98360 Use latest aebytecode. (#36)
* Use latest aebytecode.

* Fix argument to dup instruction.
2019-03-05 14:27:01 +01:00
Erik Stenman 5e6e607fa4 Use v2.0.1 of aebytecode including new sub dependencies. 2019-03-05 14:27:01 +01:00
Erik Stenman a69056c35e Change aebytecode branch ref to a tag. 2019-03-05 14:26:23 +01:00
Erik Stenman 8cfa611b20 Use right name for inc/1. Remove dead code. 2019-03-05 14:24:46 +01:00
Erik Stenman e9bdd59def Use local (patched) rebar3 in CI builds. 2019-03-05 14:24:46 +01:00
Erik Stenman 0da7376d11 Add patched rebar3 that dont run post hooks in al deps. 2019-03-05 14:24:46 +01:00
Erik Stenman af4f2ad795 Clean up. 2019-03-05 14:24:46 +01:00
Erik Stenman abae4a7602 Upgrade to latest aebytecode. 2019-03-05 14:24:22 +01:00
Erik Stenman 1cfd4c6f24 Add release target. 2019-03-05 14:24:22 +01:00
Ulf Norell f07a49c91d Skeleton for Fate backend 2019-03-05 14:23:19 +01:00
Erik Stenman e33e4cf2cd Use fortuna version of aebytecode in fortuna version of aesophia. 2019-03-05 14:22:59 +01:00
Hans Svensson 89971fb275 Release notes + note on versioning 2019-03-05 11:01:43 +01:00
Erik Stenman 85a014958d Use latest aebytecode. (#36)
* Use latest aebytecode.

* Fix argument to dup instruction.
2019-03-04 14:22:36 +01:00
Hans Svensson cdc7b901e6 Merge pull request #35 from aeternity/proper_version
Add a VERSION file at the top level and go from there
2019-03-04 14:20:59 +01:00
Hans Svensson f266c5eed8 Add a VERSION file at the top level and go from there 2019-03-04 12:17:54 +01:00
Hans Svensson 4d9d3077ad Merge pull request #34 from aeternity/fix_create_calldata_again_again
Fix calldata creation for contracts involving oracles
2019-03-01 15:03:51 +01:00
Ulf Norell 3efde2a2a1 handle hash literals when permissive_address_literals 2019-03-01 11:36:28 +01:00
Ulf Norell edc37bcf1b add case for signature literals to pretty printer 2019-03-01 11:08:28 +01:00
Ulf Norell 20f2a05638 fix problem with indent detection when inserting the __call function 2019-03-01 11:08:14 +01:00
Ulf Norell c2a5ed28cf please dialyzer 2019-03-01 11:07:46 +01:00
Ulf Norell 56f70fea6c test oracle calldata fixes 2019-03-01 09:36:43 +01:00
Ulf Norell 9e908369ec check create_calldata contract without the __call function first 2019-03-01 09:21:56 +01:00
Ulf Norell 9984679a24 better handling of permissive_literals
...that doesn't make the compiler crash
2019-03-01 09:02:55 +01:00
Robert Virding 8f27168908 Merge pull request #31 from aeternity/new-aci-generator
New aci generator
2019-02-28 18:41:59 +01:00
Robert Virding 8619f47ee6 Add very basic ACI testing
These should be extended and expanded.
2019-02-28 16:13:57 +01:00
Robert Virding 0d56130baa Use correct parse error formats 2019-02-28 16:13:57 +01:00
Robert Virding 7448da16bb Make dialyzer happy and keep it quiet 2019-02-28 16:13:57 +01:00
Robert Virding 6f582af83e Add new encode function interface 2019-02-28 16:13:57 +01:00
Robert Virding 931f2d3dcb Move module documentation to separate files 2019-02-28 16:13:57 +01:00
Robert Virding 5d116b2e5a Make the decoder return a binary and untabify 2019-02-28 16:13:57 +01:00
Robert Virding cea581988d Don't decode init function as it should never be called 2019-02-28 16:13:57 +01:00
Robert Virding 2f36380a81 Add handling of private and stateful functions 2019-02-28 16:13:57 +01:00
Robert Virding d330133b3f First version, very much WIP 2019-02-28 16:13:57 +01:00
Hans Svensson 6fccc902d0 Merge pull request #33 from aeternity/fix_create_calldata_again
Correctly handle ArgTypes in create_calldata
2019-02-28 14:57:54 +01:00
Hans Svensson eb77a73d15 Correctly handle ArgTypes in create_calldata 2019-02-28 14:44:16 +01:00
Hans Svensson 62aa06cc3a Merge pull request #32 from aeternity/fix_create_calldata
Fix handling of init in create_calldata
2019-02-28 10:12:15 +01:00
Hans Svensson 6d6fff2612 Better error handling when init is present but wrong 2019-02-28 09:56:16 +01:00
Erik Stenman fadf3378b4 Merge pull request #27 from aeternity/fate-compiler2
Skeleton for Fate backend
2019-02-28 09:03:09 +01:00
Hans Svensson 95bf0d4b6c Fix handling of init in create_calldata 2019-02-27 20:42:42 +01:00
Hans Svensson e94c1f9d84 Merge pull request #30 from aeternity/to-sophia-value-revisited
To sophia value revisited
2019-02-27 11:36:03 +01:00
Hans Svensson a263b09e57 Remove leftover io:format 2019-02-27 11:00:00 +01:00
Hans Svensson 5a3c8530b4 Dialyzer found an error 2019-02-26 21:03:52 +01:00
Ulf Norell 4c79f7b9f2 tests for calldata creation 2019-02-26 17:41:04 +01:00
Ulf Norell e0fff00e64 get rid of byte code argument to create_calldata
This means that there is less type checking at calldata creation time.
Make sure that we check that the function hash exists before calling
a contract!
2019-02-26 17:31:53 +01:00
Hans Svensson 7c95aafbb8 Merge pull request #29 from aeternity/more_better_errors
Improve Events error message + more tests
2019-02-26 16:21:35 +01:00
Ulf Norell 5a4a84805f change create_calldata function to also take fun name and arguments 2019-02-26 15:01:27 +01:00
Ulf Norell cc3e322179 new version of to_sophia_value
takes function name and binary blob
2019-02-26 14:34:57 +01:00
Erik Stenman 54edba3164 Use v2.0.1 of aebytecode including new sub dependencies. 2019-02-26 09:43:14 +01:00
Ulf Norell f16d699f6d Merge pull request #28 from aeternity/decode-vm-to-sophia
Add function to decode VM values to Sophia abstract syntax
2019-02-26 09:00:48 +01:00
Hans Svensson 7b474e439c Improve Events error message + more tests 2019-02-25 21:53:52 +01:00
Erik Stenman fc64ca572d Change aebytecode branch ref to a tag. 2019-02-25 16:42:32 +01:00
Ulf Norell eb926b1352 fix type signature 2019-02-25 14:42:47 +01:00
Ulf Norell dc4a2ca2f9 tests for aeso:compiler:to_sophia_value/2 2019-02-25 14:24:16 +01:00
Ulf Norell f866e24624 pretty print nullary constructor applications without the parens 2019-02-25 14:23:44 +01:00
Ulf Norell ccad660eac add compiler function to translate a vm value to Sophia AST 2019-02-25 14:23:23 +01:00
Erik Stenman 53b85ce6f4 Use right name for inc/1. Remove dead code. 2019-02-25 13:25:36 +01:00
Erik Stenman a7af62c089 Use local (patched) rebar3 in CI builds. 2019-02-25 13:18:13 +01:00
Erik Stenman abc70ba288 Add patched rebar3 that dont run post hooks in al deps. 2019-02-25 13:12:42 +01:00
Erik Stenman 6342cd6a08 Clean up. 2019-02-25 13:10:04 +01:00
Erik Stenman 123d1d2fa2 Upgrade to latest aebytecode. 2019-02-25 09:07:52 +01:00
Erik Stenman b7b54b38a8 Add release target. 2019-02-20 17:34:49 +01:00
Ulf Norell ae3f292f03 Skeleton for Fate backend 2019-02-20 09:44:32 +01:00
Erik Stenman bcdf311096 Use fortuna version of aebytecode in fortuna version of aesophia. 2019-02-15 12:42:49 +01:00
Robert Virding 202a06a580 Merge pull request #25 from aeternity/PT-156466783-namespaces
PT-156466783 namespaces
2019-02-13 15:54:07 +01:00
Ulf Norell b5b0d30fc4 Less hacky handling of Chain.event 2019-02-08 14:48:38 +01:00
Ulf Norell 236ef6eb89 Dialyzed! 2019-02-08 14:16:06 +01:00
Ulf Norell aa6d56ce9b Allow passing an explicit "file system" for included files to the compiler 2019-02-08 14:16:06 +01:00
Ulf Norell 0b86cdc318 Clean up test case 2019-02-08 14:16:06 +01:00
Ulf Norell 27cbedc7ab Further generalise used names computation 2019-02-08 14:16:06 +01:00
Ulf Norell 2ac47059c1 Refactor used_ids and used_types into a generic fold 2019-02-08 14:16:06 +01:00
Hans Svensson 421bc01012 Add error messages for bad include and nested namespace 2019-02-08 14:16:06 +01:00
Hans Svensson 2b7490776e Add include directive
Add an include directive to include namespaces into a contract. Only allowed at the top level.

To allow includes, either call through aeso_compiler:file or set the option `allow_include` (and add `include_path`(s)).
2019-02-08 14:16:06 +01:00
Ulf Norell 0a5b80668f Don't mess up on multiple namespaces in icode compiler 2019-02-08 14:16:06 +01:00
Ulf Norell 6cdba58e35 Update error messages 2019-02-08 14:16:06 +01:00
Ulf Norell 8262d7780f Fix some issues pointed out by dialyzer 2019-02-08 14:16:06 +01:00
Ulf Norell e6f01481bf Bind state and event primitives only in contracts (and with the right types) 2019-02-08 14:16:06 +01:00
Ulf Norell d9188d58a7 Proper checking of types 2019-02-08 14:16:06 +01:00
Ulf Norell dfa286d43c Deadcode elimination (icode post pass) 2019-02-08 14:16:06 +01:00
Ulf Norell 478da2af33 Don't expose namespace functions as entrypoints 2019-02-08 14:16:06 +01:00
Ulf Norell 10be09fe30 Add checks on event constructor arguments to type checker 2019-02-08 14:16:06 +01:00
Ulf Norell e6c9d0fac1 Put event index information in constructor annotation instead of in argument types 2019-02-08 14:16:06 +01:00
Ulf Norell 367f87b612 Implement namespaces
This includes a massive refactoring of the type checker, getting
rid of most of the ets tables and keeping a proper environment.
2019-02-08 14:16:06 +01:00
54 changed files with 2988 additions and 1679 deletions
+4 -4
View File
@@ -19,16 +19,16 @@ jobs:
- dialyzer-cache-v2-
- run:
name: Build
command: rebar3 compile
command: ./rebar3 compile
- run:
name: Static Analysis
command: rebar3 dialyzer
command: ./rebar3 dialyzer
- run:
name: Eunit
command: rebar3 eunit
command: ./rebar3 eunit
- run:
name: Common Tests
command: rebar3 ct
command: ./rebar3 ct
- save_cache:
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
paths:
+1
View File
@@ -18,3 +18,4 @@ _build
rebar3.crashdump
*.erl~
*.aes~
aesophia
+40
View File
@@ -0,0 +1,40 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Removed
## [2.1.0] - 2019-04-11
### Added
- Stubs (not yet wired up) for compilation to FATE
- Add functions specific for Calldata decoding
- Support for `Auth.tx_hash`, not available in AEVM until Fortuna release
### Changed
- Improvements to the ACI generator
## [2.0.0] - 2019-03-11
### Added
- Add `Crypto.ecverify` to the compiler.
- Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
`String.sha256` to the compiler.
- Add the `bits` type for working with bit fields in Sophia.
- Add Namespaces to Sophia in order to simplify using library contracts, etc.
- Add a missig type check on the `init` function - detects programmer errors earlier.
- Add the ACI (Aeternity Contract Interface) generator.
### Changed
- Use native bit shift operations in builtin functions, reducing gas cost.
- Improve type checking of `record` fields - generates more understandable error messages.
- Improved, more coherent, error messages.
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+9
View File
@@ -9,8 +9,17 @@ It is an OTP application written in Erlang and is by default included in
also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system.
## Versioning
`aesophia` has a version that is only loosely connected to the version of the
Aeternity node - in principle they will share the major version but not
minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc.
## Interface Modules
The basic modules for interfacing the compiler:
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](./docs/aeso_aci.md)
+135
View File
@@ -0,0 +1,135 @@
# aeso_aci
### Module
### aeso_aci
The ACI interface encoder and decoder.
### Description
This module provides an interface to generate and convert between
Sophia contracts and a suitable JSON encoding of contract
interface. As yet the interface is very basic.
Encoding this contract:
```
contract Answers =
record state = { a : answers }
type answers() = map(string, int)
stateful function init() = { a = {} }
private function the_answer() = 42
function new_answer(q : string, a : int) : answers() = { [q] = a }
```
generates the following JSON structure representing the contract interface:
``` json
{
"contract": {
"name": "Answers",
"type_defs": [
{
"name": "state",
"vars": [],
"typedef": "{a : map(string,int)}"
},
{
"name": "answers",
"vars": [],
"typedef": "map(string,int)"
}
],
"functions": [
{
"name": "init",
"arguments": [],
"type": "{a : map(string,int)}",
"stateful": true
},
{
"name": "new_answer",
"arguments": [
{
"name": "q",
"type": "string"
},
{
"name": "a",
"type": "int"
}
],
"type": "map(string,int)",
"stateful": false
}
]
}
}
```
When that encoding is decoded the following include definition is generated:
```
contract Answers =
function new_answer : (string, int) => map(string,int)
```
### Types
``` erlang
contract_string() = string() | binary()
json_string() = binary()
```
### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,Encoding} = aeso_aci:encode(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>>
3> file:write_file("aci_test.aci", Encoding).
ok
4> Decoded = aeso_aci:decode(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">>
5> file:write_file("aci_test.include", Decoded).
ok
6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>>
```
The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
+3 -3
View File
@@ -15,7 +15,7 @@ returns the compiled module in a map which can then be loaded.
``` erlang
contract_string() = string() | binary()
contract_map() = #{bytecode => binary(),
compiler_version => string(),
compiler_version => binary(),
contract_souce => string(),
type_info => type_info()}
type_info()
@@ -75,12 +75,12 @@ Types
Get the type representation of a type declaration.
#### version() -> Version
#### version() -> {ok, Version} | {error, term()}
Types
``` erlang
Version = integer()
Version = binary()
```
Get the current version of the Sophia compiler.
-15
View File
@@ -1,15 +0,0 @@
-record(pmap, {key_t :: aeso_sophia:type(),
val_t :: aeso_sophia:type(),
parent :: none | non_neg_integer(),
size = 0 :: non_neg_integer(),
data :: #{aeso_heap:binary_value() => aeso_heap:binary_value() | tombstone}
| stored}).
-record(maps, { maps = #{} :: #{ non_neg_integer() => #pmap{} }
, next_id = 0 :: non_neg_integer() }).
-record(heap, { maps :: #maps{},
offset :: aeso_heap:offset(),
heap :: binary() | #{non_neg_integer() => non_neg_integer()} }).
+14 -2
View File
@@ -1,14 +1,18 @@
%% -*- mode: erlang; indent-tabs-mode: nil -*-
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"720510a"}}}
{ref, "28f6c42"}}}
, {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! +sbtu +A0\n"}.
{escript_emu_args, "%%! \n"}.
{provider_hooks, [{post, [{compile, escriptize}]}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
@@ -25,3 +29,11 @@
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "2.1.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.
+16 -2
View File
@@ -1,10 +1,24 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
0}]}.
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
].
Executable
BIN
View File
Binary file not shown.
-239
View File
@@ -1,239 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Encode and decode data and function calls according to
%%% Sophia-AEVM-ABI.
%%% @end
%%% Created : 25 Jan 2018
%%%
%%%-------------------------------------------------------------------
-module(aeso_abi).
-define(HASH_SIZE, 32).
-export([ old_create_calldata/3
, create_calldata/5
, check_calldata/2
, function_type_info/3
, function_type_hash/3
, arg_typerep_from_function/2
, type_hash_from_function_name/2
, typereps_from_type_hash/2
, function_name_from_type_hash/2
, get_function_hash_from_calldata/1
]).
-type hash() :: <<_:256>>. %% 256 = ?HASH_SIZE * 8.
-type function_name() :: binary(). %% String
-type typerep() :: aeso_sophia:type().
-type function_type_info() :: { FunctionHash :: hash()
, FunctionName :: function_name()
, ArgType :: binary() %% binary typerep
, OutType :: binary() %% binary typerep
}.
-type type_info() :: [function_type_info()].
%%%===================================================================
%%% API
%%%===================================================================
%%%===================================================================
%%% Handle calldata
create_calldata(Contract, FunName, Args, ArgTypes, RetType) ->
case get_type_info_and_hash(Contract, FunName) of
{ok, TypeInfo, TypeHashInt} ->
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
case check_calldata(Data, TypeInfo) of
{ok, CallDataType, OutType} ->
case check_given_type(FunName, ArgTypes, RetType, CallDataType, OutType) of
ok ->
{ok, Data, CallDataType, OutType};
{error, _} = Err ->
Err
end;
{error,_What} = Err -> Err
end;
{error, _} = Err -> Err
end.
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
FunBin = list_to_binary(FunName),
case type_hash_from_function_name(FunBin, TypeInfo) of
{ok, <<TypeHashInt:?HASH_SIZE/unit:8>>} -> {ok, TypeInfo, TypeHashInt};
{ok, _} -> {error, bad_type_hash};
{error, _} = Err -> Err
end.
%% Check that the given type matches the type from the metadata.
check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
{tuple, [word, {tuple, ExpectArgs}]} = CalldataType,
ReturnOk = if FunName == "init" -> true;
GivenRet == any -> true;
true -> GivenRet == ExpectRet
end,
ArgsOk = ExpectArgs == GivenArgs,
case ReturnOk andalso ArgsOk of
true -> ok;
false when FunName == "init" ->
{error, {init_args_mismatch,
{given, GivenArgs},
{expected, ExpectArgs}}};
false ->
{error, {call_type_mismatch,
{given, GivenArgs, '=>', GivenRet},
{expected, ExpectArgs, '=>', ExpectRet}}}
end.
-spec check_calldata(binary(), type_info()) ->
{'ok', typerep(), typerep()} | {'error', atom()}.
check_calldata(CallData, TypeInfo) ->
%% The first element of the CallData should be the function name
case get_function_hash_from_calldata(CallData) of
{ok, Hash} ->
case typereps_from_type_hash(Hash, TypeInfo) of
{ok, ArgType, OutType} ->
try aeso_heap:from_binary({tuple, [word, ArgType]}, CallData) of
{ok, _Something} ->
{ok, {tuple, [word, ArgType]}, OutType};
{error, _} ->
{error, bad_call_data}
catch
_T:_E ->
{error, bad_call_data}
end;
{error, _} ->
{error, unknown_function}
end;
{error, _What} ->
{error, bad_call_data}
end.
-spec get_function_hash_from_calldata(CallData::binary()) ->
{ok, binary()} | {error, term()}.
get_function_hash_from_calldata(CallData) ->
case aeso_heap:from_binary({tuple, [word]}, CallData) of
{ok, {HashInt}} -> {ok, <<HashInt:?HASH_SIZE/unit:8>>};
{error, _} = Error -> Error
end.
%%%===================================================================
%%% Handle type info from contract meta data
-spec function_type_info(function_name(), [typerep()], typerep()) ->
function_type_info().
function_type_info(Name, Args, OutType) ->
ArgType = {tuple, [T || {_, T} <- Args]},
{ function_type_hash(Name, ArgType, OutType)
, Name
, aeso_heap:to_binary(ArgType)
, aeso_heap:to_binary(OutType)
}.
-spec function_type_hash(function_name(), typerep(), typerep()) -> hash().
function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
Bin = iolist_to_binary([ Name
, aeso_heap:to_binary(ArgType)
, aeso_heap:to_binary(OutType)
]),
%% Calculate a 256 bit digest BLAKE2b hash value of a binary
{ok, Hash} = aeso_blake2:blake2b(?HASH_SIZE, Bin),
Hash.
-spec arg_typerep_from_function(function_name(), type_info()) ->
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
arg_typerep_from_function(Function, TypeInfo) ->
case lists:keyfind(Function, 2, TypeInfo) of
{_TypeHash, Function, ArgTypeBin,_OutTypeBin} ->
case aeso_heap:from_binary(typerep, ArgTypeBin) of
{ok, ArgType} -> {ok, ArgType};
{error,_} -> {error, bad_type_data}
end;
false ->
{error, unknown_function}
end.
-spec typereps_from_type_hash(hash(), type_info()) ->
{'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
typereps_from_type_hash(TypeHash, TypeInfo) ->
case lists:keyfind(TypeHash, 1, TypeInfo) of
{TypeHash,_Function, ArgTypeBin, OutTypeBin} ->
case {aeso_heap:from_binary(typerep, ArgTypeBin),
aeso_heap:from_binary(typerep, OutTypeBin)} of
{{ok, ArgType}, {ok, OutType}} -> {ok, ArgType, OutType};
{_, _} -> {error, bad_type_data}
end;
false ->
{error, unknown_function}
end.
-spec function_name_from_type_hash(hash(), type_info()) ->
{'ok', function_name()}
| {'error', 'unknown_function'}.
function_name_from_type_hash(TypeHash, TypeInfo) ->
case lists:keyfind(TypeHash, 1, TypeInfo) of
{TypeHash, Function,_ArgTypeBin,_OutTypeBin} ->
{ok, Function};
false ->
{error, unknown_function}
end.
-spec type_hash_from_function_name(function_name(), type_info()) ->
{'ok', hash()}
| {'error', 'unknown_function'}.
type_hash_from_function_name(Name, TypeInfo) ->
case lists:keyfind(Name, 2, TypeInfo) of
{TypeHash, Name,_ArgTypeBin,_OutTypeBin} ->
{ok, TypeHash};
false ->
{error, unknown_function}
end.
%% -- Old calldata creation. Kept for backwards compatibility. ---------------
old_create_calldata(Contract, Function, Argument) when is_map(Contract) ->
case aeso_constants:string(Argument) of
{ok, {tuple, _, _} = Tuple} ->
old_encode_call(Contract, Function, Tuple);
{ok, {unit, _} = Tuple} ->
old_encode_call(Contract, Function, Tuple);
{ok, ParsedArgument} ->
%% The Sophia compiler does not parse a singleton tuple (42) as a tuple,
%% Wrap it in a tuple.
old_encode_call(Contract, Function, {tuple, [], [ParsedArgument]});
{error, _} ->
{error, argument_syntax_error}
end.
%% Call takes one arument.
%% Use a tuple to pass multiple arguments.
old_encode_call(Contract, Function, ArgumentAst) ->
Argument = old_ast_to_erlang(ArgumentAst),
case get_type_info_and_hash(Contract, Function) of
{ok, TypeInfo, TypeHashInt} ->
Data = aeso_heap:to_binary({TypeHashInt, Argument}),
case check_calldata(Data, TypeInfo) of
{ok, CallDataType, OutType} ->
{ok, Data, CallDataType, OutType};
{error, _} = Err ->
Err
end;
{error, _} = Err -> Err
end.
old_ast_to_erlang({int, _, N}) -> N;
old_ast_to_erlang({hash, _, <<N:?HASH_SIZE/unit:8>>}) -> N;
old_ast_to_erlang({hash, _, <<Hi:256, Lo:256>>}) -> {Hi, Lo}; %% signature
old_ast_to_erlang({bool, _, true}) -> 1;
old_ast_to_erlang({bool, _, false}) -> 0;
old_ast_to_erlang({string, _, Bin}) -> Bin;
old_ast_to_erlang({unit, _}) -> {};
old_ast_to_erlang({con, _, "None"}) -> none;
old_ast_to_erlang({app, _, {con, _, "Some"}, [A]}) -> {some, old_ast_to_erlang(A)};
old_ast_to_erlang({tuple, _, Elems}) ->
list_to_tuple(lists:map(fun old_ast_to_erlang/1, Elems));
old_ast_to_erlang({list, _, Elems}) ->
lists:map(fun old_ast_to_erlang/1, Elems);
old_ast_to_erlang({map, _, Elems}) ->
maps:from_list([ {old_ast_to_erlang(element(1, Elem)), old_ast_to_erlang(element(2, Elem))}
|| Elem <- Elems ]).
+393
View File
@@ -0,0 +1,393 @@
%%%-------------------------------------------------------------------
%%% @author Robert Virding
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% ACI interface
%%% @end
%%% Created : 12 Jan 2019
%%%-------------------------------------------------------------------
-module(aeso_aci).
-export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
%% Top-level
-record(contract, {ann,con,decls}).
%% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
%% Types
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
%% Tokens
-record(arg, {ann,id,type}).
-record(id, {ann,name}).
-record(con, {ann,name}).
-record(qid, {ann,names}).
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
%% Expressions
-record(bool, {ann,bool}).
-record(int, {ann,value}).
-record(string, {ann,bin}).
-record(hash, {ann,hash}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
encode(ContractString) -> encode(ContractString, []).
encode(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options);
encode(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract.
Contract = lists:last(TypedAst),
Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) ||
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
%% encode_func(Function) -> JSON
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
encode_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, encode_name(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"returns">>, encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
%% encode_args(Args) -> [JSON].
%% encode_arg(Args) -> JSON.
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_types(Types) -> [JSON].
%% encode_type(Type) -> JSON.
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
encode_type(#tvar{name=N}) -> encode_name(N);
encode_type(#id{name=N}) -> encode_name(N);
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_type(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
[{<<"tuple">>,Eas}];
encode_type(#record_t{fields=Fs}) ->
Efs = encode_fields(Fs),
[{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
[{Name,Efs}];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
[{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
[{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) ->
list_to_binary(Name).
%% encode_fields(Fields) -> [JSON].
%% encode_field(Field) -> JSON.
%% Encode a record field.
encode_fields(Fs) ->
[ encode_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_typedef(TypeDef) -> JSON.
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, encode_name(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, encode_alias(Def)}].
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, encode_name(N)}].
encode_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
%% encode_stmt(Stmt) -> JSON.
encode_stmt(E) ->
encode_expr(E).
%% encode_exprs(Exprs) -> [JSON].
%% encode_expr(Expr) -> JSON.
encode_exprs(Es) ->
[ encode_expr(E) || E <- Es ].
encode_expr(#id{name=N}) -> encode_name(N);
encode_expr(#con{name=N}) -> encode_name(N);
encode_expr(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_expr(#typed{expr=E}) ->
encode_expr(E);
encode_expr(#bool{bool=B}) -> B;
encode_expr(#int{value=V}) -> V;
encode_expr(#string{bin=B}) -> B;
encode_expr(#hash{hash=H}) -> H;
encode_expr(#tuple{args=As}) ->
Eas = encode_exprs(As),
[{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) ->
Eas = encode_exprs(As),
[{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
%% decode(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)).
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts),
decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
decode_type(#{<<"tuple">> := Ets}) ->
Ts = decode_types(Ets),
[$(,lists:join(",", Ts),$)];
decode_type(#{<<"record">> := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(",", Fs),$}];
decode_type(#{<<"list">> := [Et]}) ->
T = decode_type(Et),
["list",$(,T,$)];
decode_type(#{<<"map">> := Ets}) ->
Ts = decode_types(Ets),
["map",$(,lists:join(",", Ts),$)];
decode_type(#{<<"variant">> := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts);
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec),
Ts = decode_types(Ets),
[C,$(,lists:join(",", Ts),$)];
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) ->
binary_to_list(En).
decode_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ].
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
Name = decode_name(En),
Type = decode_type(Et),
[Name," : ",Type].
%% decode_tdefs(Json) -> [TypeString].
%% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
" = ",decode_type(T),$\n].
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)].
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%% #contract{Ann, Con, [Declarations]}.
contract_name(#contract{con=#con{name=N}}) -> N.
contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, letfun) ].
contract_types(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, type_def) ].
%% To keep dialyzer happy and quiet.
%% namespace_name(#namespace{con=#con{name=N}}) -> N.
%%
%% namespace_funcs(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, letfun) ].
%%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
sort_decls(Ds) ->
Sort = fun (D1, D2) ->
aeso_syntax:get_ann(line, D1, 0) =<
aeso_syntax:get_ann(line, D2, 0)
end,
lists:sort(Sort, Ds).
%% #letfun{Ann, Id, [Arg], Type, Typedef}.
function_name(#letfun{id=#id{name=N}}) -> N.
function_args(#letfun{args=Args}) -> Args.
function_type(#letfun{type=Type}) -> Type.
is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error(Pos, ErrorString) ->
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
-1
View File
@@ -17,7 +17,6 @@ line({symbol, Line, _}) -> Line.
symbol_name({symbol, _, Name}) -> Name.
pp(Ast) ->
%% io:format("Tree:\n~p\n",[Ast]),
String = prettypr:format(aeso_pretty:decls(Ast, [])),
io:format("Ast:\n~s\n", [String]).
File diff suppressed because it is too large Load Diff
+103 -27
View File
@@ -17,11 +17,19 @@
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
convert_typed(TypedTree, Options) ->
code(TypedTree, aeso_icode:new(Options)).
Name = case lists:last(TypedTree) of
{contract, _, {con, _, Con}, _} -> Con;
_ -> gen_error(last_declaration_must_be_contract)
end,
Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))),
deadcode_elimination(Icode).
code([{contract, _Attribs, {con, _, Name}, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code,
aeso_icode:set_name(Name, Icode)),
code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
code(Rest, NewIcode);
code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
%% TODO: nested namespaces
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)),
code(Rest, NewIcode);
code([], Icode) ->
add_default_init_function(add_builtins(Icode)).
@@ -33,30 +41,38 @@ gen_error(Error) ->
%% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
case lists:keymember("init", 1, Funs) of
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of
true -> Icode;
false when State /= {tuple, []} -> gen_error(missing_init_function);
false when State /= {tuple, []} ->
gen_error(missing_init_function);
false ->
Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
DefaultInit = {"init", [], [], Value, Type},
DefaultInit = {QInit, [], [], Value, Type},
Icode#{ functions => [DefaultInit | Funs] }
end.
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
aeso_icode:icode().
contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode),
NewConstructors =
case Def of
{variant_t, Cons} ->
Tags = lists:seq(0, length(Cons) - 1),
GetName = fun({constr_t, _, {con, _, C}, _}) -> C end,
maps:from_list([ {GetName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
GetName = fun({constr_t, _, C, _}) -> C end,
QName = fun(Con) -> {_, _, Xs} = aeso_icode:qualify(GetName(Con), Icode), Xs end,
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
_ -> #{}
end,
Icode1 = Icode#{ types := Types#{ Name => TypeDef },
{_, _, TName} = aeso_icode:qualify(Id, Icode),
Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
@@ -68,8 +84,7 @@ contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
contract_to_icode(Rest, Icode2);
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
[ private || proplists:get_value(private, Attrib, false) orelse
proplists:get_value(internal, Attrib, false) ],
[ private || is_private(Attrib, Icode) ],
%% TODO: Handle types
FunName = ast_id(Name),
%% TODO: push funname to env
@@ -84,7 +99,8 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
{tuple, [typerep, ast_typerep(T, Icode)]}};
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
end,
NewIcode = ast_fun_to_icode(FunName, FunAttrs, FunArgs, FunBody, TypeRep, Icode),
QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode);
contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% OBS! This code ignores the letrec structure of the source,
@@ -94,11 +110,14 @@ contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode;
contract_to_icode(_Code, Icode) ->
%% TODO debug output for debug("Unhandled code ~p~n",[Code]),
Icode.
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
contract_to_icode(Code, Icode);
contract_to_icode([Decl | Code], Icode) ->
io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode).
ast_id({id, _, Id}) -> Id.
ast_id({id, _, Id}) -> Id;
ast_id({qid, _, Id}) -> Id.
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
@@ -121,7 +140,7 @@ ast_type(T, Icode) ->
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
ast_body(?qid_app(["Chain","event"], [Event], _, _), Icode) ->
ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) ->
aeso_builtins:check_event_type(Icode),
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
@@ -152,10 +171,10 @@ ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'});
%% State
ast_body({id, _, "state"}, _Icode) -> prim_state;
ast_body(?id_app("put", [NewState], _, _), Icode) ->
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
#prim_put{ state = ast_body(NewState, Icode) };
ast_body({id, _, "put"}, _Icode) ->
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
gen_error({underapplied_primitive, put}); %% TODO: eta
%% Abort
@@ -163,6 +182,11 @@ ast_body(?id_app("abort", [String], _, _), Icode) ->
#funcall{ function = #var_ref{ name = {builtin, abort} },
args = [ast_body(String, Icode)] };
%% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0},
[], [], aeso_icode:option_typerep(word));
%% Oracles
ast_body(?qid_app(["Oracle", "register"], Args, _, ?oracle_t(QType, RType)), Icode) ->
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
@@ -384,7 +408,8 @@ ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
%% Other terms
ast_body({id, _, Name}, _Icode) ->
%% TODO Look up id in env
#var_ref{name = Name};
ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
Value = if Bool -> 1 ; true -> 0 end,
@@ -400,7 +425,7 @@ ast_body({hash, _, Hash}, _Icode) ->
#integer{value = Lo}]}
end;
ast_body({string,_,Bin}, _Icode) ->
Cpts = [size(Bin) | aeso_memory:binary_to_words(Bin)],
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
ast_body({tuple,_,Args}, Icode) ->
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
@@ -424,7 +449,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
Value = proplists:get_value("value", ArgOpts ++ Defaults),
OutType = ast_typerep(OutT, Icode),
<<TypeHash:256>> = aeso_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
<<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
%% The function is represented by its type hash (which includes the name)
Fun = #integer{value = TypeHash},
#prim_call_contract{
@@ -441,9 +466,15 @@ ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode)
string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag}]};
ast_body({qcon, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag}]};
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app,As,Fun,Args}, Icode) ->
@@ -619,7 +650,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
true ->
PrimBin = binary:encode_unsigned(Prim),
ArgType = {tuple, ArgTypes},
<<TH:256>> = aeso_abi:function_type_hash(PrimBin, ArgType, OutType),
<<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
TH;
false ->
0
@@ -648,7 +679,7 @@ make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
end.
-spec ast_typerep(aeso_syntax:type()) -> aeso_sophia:type().
-spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type().
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
ast_typerep({id, _, Name}, Icode) ->
@@ -745,6 +776,15 @@ has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if marked 'private' or 'internal', or if it's not
%% defined in the main contract name space. (NOTE: changes when we introduce
%% inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) ->
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
proplists:get_value(private, Ann, false) orelse
proplists:get_value(internal, Ann, false) orelse
MainContract /= CurrentNamespace.
%% -------------------------------------------------------------------
%% Builtins
%% -------------------------------------------------------------------
@@ -756,3 +796,39 @@ builtin_call(Builtin, Args) ->
add_builtins(Icode = #{functions := Funs}) ->
Builtins = aeso_builtins:used_builtins(Funs),
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}.
%% -------------------------------------------------------------------
%% Deadcode elimination
%% -------------------------------------------------------------------
deadcode_elimination(Icode = #{ functions := Funs }) ->
PublicNames = [ Name || {Name, Ann, _, _, _} <- Funs, not lists:member(private, Ann) ],
ArgsToPat = fun(Args) -> [ #var_ref{ name = X } || {X, _} <- Args ] end,
Defs = maps:from_list([ {Name, {binder, ArgsToPat(Args), Body}} || {Name, _, Args, Body, _} <- Funs ]),
UsedNames = chase_names(Defs, PublicNames, #{}),
UsedFuns = [ Def || Def = {Name, _, _, _, _} <- Funs, maps:is_key(Name, UsedNames) ],
Icode#{ functions := UsedFuns }.
chase_names(_Defs, [], Used) -> Used;
chase_names(Defs, [X | Xs], Used) ->
%% can happen when compiling __call contracts
case maps:is_key(X, Used) orelse not maps:is_key(X, Defs) of
true -> chase_names(Defs, Xs, Used); %% already chased
false ->
Def = maps:get(X, Defs),
Vars = maps:keys(free_vars(Def)),
chase_names(Defs, Vars ++ Xs, Used#{ X => true })
end.
free_vars(#var_ref{ name = X }) -> #{ X => true };
free_vars(#arg{ name = X }) -> #{ X => true };
free_vars({binder, Pat, Body}) ->
maps:without(maps:keys(free_vars(Pat)), free_vars(Body));
free_vars(#switch{ expr = E, cases = Cases }) ->
free_vars([E | [{binder, P, B} || {P, B} <- Cases]]);
free_vars(#lambda{ args = Xs, body = E }) ->
free_vars({binder, Xs, E});
free_vars(T) when is_tuple(T) -> free_vars(tuple_to_list(T));
free_vars([H | T]) -> maps:merge(free_vars(H), free_vars(T));
free_vars(_) -> #{}.
-149
View File
@@ -1,149 +0,0 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net
%%% @end
%%%=============================================================================
-module(aeso_blake2).
-export([ blake2b/2
, blake2b/3
]).
-define(MAX_64BIT, 16#ffffffffffffffff).
-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}.
blake2b(HashLen, Msg) ->
blake2b(HashLen, Msg, <<>>).
-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}.
blake2b(HashLen, Msg0, Key) ->
%% If message should be keyed, prepend message with padded key.
Msg = <<(pad(128, Key))/binary, Msg0/binary>>,
%% Set up the initial state
Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen),
<<H0:64, H1_7/binary>> = blake_iv(),
H = <<(H0 bxor Init):64, H1_7/binary>>,
%% Perform the compression - message will be chopped into 128-byte chunks.
State = blake2b_compress(H, Msg, 0),
%% Just return the requested part of the hash
{ok, binary_part(to_little_endian(State), {0, HashLen})}.
blake2b_compress(H, <<Chunk:(128*8), Rest/binary>>, BCompr) when Rest /= <<>> ->
H1 = blake2b_compress(H, <<Chunk:(128*8)>>, BCompr + 128, false),
blake2b_compress(H1, Rest, BCompr + 128);
blake2b_compress(H, SmallChunk, BCompr) ->
Size = byte_size(SmallChunk),
FillSize = (128 - Size) * 8,
blake2b_compress(H, <<SmallChunk/binary, 0:FillSize>>, BCompr + Size, true).
blake2b_compress(H, Chunk0, BCompr, Last) ->
Chunk = to_big_endian(Chunk0),
<<V0_11:(12*64), V12:64, V13:64, V14:64, V15:64>> = <<H/binary, (blake_iv())/binary>>,
V12_ = V12 bxor (BCompr band ?MAX_64BIT),
V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT),
V14_ = case Last of
false -> V14;
true -> V14 bxor ?MAX_64BIT
end,
V = <<V0_11:(12*64), V12_:64, V13_:64, V14_:64, V15:64>>,
<<VLow:(8*64), VHigh:(8*64)>> =
lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)),
<<HInt:(8*64)>> = H,
<<((HInt bxor VLow) bxor VHigh):(8*64)>>.
blake2b_mix(Rnd, Chunk, V) ->
<<V0:64, V1:64, V2:64, V3:64, V4:64, V5:64, V6:64, V7:64, V8:64,
V9:64, V10:64, V11:64, V12:64, V13:64, V14:64, V15:64>> = V,
<<M0:64, M1:64, M2:64, M3:64, M4:64, M5:64, M6:64, M7:64, M8:64,
M9:64, M10:64, M11:64, M12:64, M13:64, M14:64, M15:64>> = Chunk,
Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15},
M = fun(Ix) -> element(Ix+1, Ms) end,
[S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10),
{Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)),
{Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)),
{Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)),
{Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)),
{Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)),
{Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)),
{Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)),
{Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)),
<<Vy0:64, Vy1:64, Vy2:64, Vy3:64, Vy4:64, Vy5:64, Vy6:64, Vy7:64, Vy8:64,
Vy9:64, Vy10:64, Vy11:64, Vy12:64, Vy13:64, Vy14:64, Vy15:64>>.
blake2b_mix(Va, Vb, Vc, Vd, X, Y) ->
Va1 = (Va + Vb + X) band ?MAX_64BIT,
Vd1 = rotr64(32, Vd bxor Va1),
Vc1 = (Vc + Vd1) band ?MAX_64BIT,
Vb1 = rotr64(24, Vb bxor Vc1),
Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT,
Vd2 = rotr64(16, Va2 bxor Vd1),
Vc2 = (Vc1 + Vd2) band ?MAX_64BIT,
Vb2 = rotr64(63, Vb1 bxor Vc2),
{Va2, Vb2, Vc2, Vd2}.
blake_iv() ->
IV0 = 16#6A09E667F3BCC908,
IV1 = 16#BB67AE8584CAA73B,
IV2 = 16#3C6EF372FE94F82B,
IV3 = 16#A54FF53A5F1D36F1,
IV4 = 16#510E527FADE682D1,
IV5 = 16#9B05688C2B3E6C1F,
IV6 = 16#1F83D9ABFB41BD6B,
IV7 = 16#5BE0CD19137E2179,
<<IV0:64, IV1:64, IV2:64, IV3:64, IV4:64, IV5:64, IV6:64, IV7:64>>.
sigma(N) ->
{_, Row} = lists:keyfind(N, 1, sigma()), Row.
sigma() ->
[{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]},
{1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]},
{2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]},
{3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]},
{4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]},
{5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]},
{6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]},
{7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]},
{8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]},
{9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}].
rotr64(N, I64) ->
<<I64rot:64>> = rotr641(N, <<I64:64>>),
I64rot.
rotr641(16, <<X:(64-16), Y:16>>) -> <<Y:16, X:(64-16)>>;
rotr641(24, <<X:(64-24), Y:24>>) -> <<Y:24, X:(64-24)>>;
rotr641(32, <<X:(64-32), Y:32>>) -> <<Y:32, X:(64-32)>>;
rotr641(63, <<X:(64-63), Y:63>>) -> <<Y:63, X:(64-63)>>.
pad(N, Bin) ->
case (N - (byte_size(Bin) rem N)) rem N of
0 -> Bin;
Pad -> <<Bin/binary, 0:(Pad *8)>>
end.
to_big_endian(Bin) -> to_big_endian(Bin, <<>>).
to_big_endian(<<>>, Acc) -> Acc;
to_big_endian(<<UInt64:1/little-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
to_big_endian(Rest, <<Acc/binary, UInt64:1/big-unsigned-integer-unit:64>>).
to_little_endian(Bin) -> to_little_endian(Bin, <<>>).
to_little_endian(<<>>, Acc) -> Acc;
to_little_endian(<<UInt64:1/big-unsigned-integer-unit:64, Rest/binary>>, Acc) ->
to_little_endian(Rest, <<Acc/binary, UInt64:1/little-unsigned-integer-unit:64>>).
+15 -15
View File
@@ -94,7 +94,7 @@ operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeso_memory:binary_to_words(BinStr)],
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) ->
@@ -106,21 +106,22 @@ check_event_type(Icode) ->
end.
check_event_type(Evts, Icode) ->
[ check_event_type(Name, T, Icode)
|| {constr_t, _, {con, _, Name}, Types} <- Evts, T <- Types ].
[ check_event_type(Name, Ix, T, Icode)
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
check_event_type(EvtName, Type, Icode) ->
check_event_type(EvtName, Ix, Type, Icode) ->
VMType =
try
aeso_ast_to_icode:ast_typerep(Type, Icode)
catch _:_ ->
error({EvtName, could_not_resolve_type, Type})
end,
case aeso_syntax:get_ann(indexed, Type, false) of
true when VMType == word -> ok;
false when VMType == string -> ok;
true -> error({EvtName, indexed_field_should_be_word, is, VMType});
false -> error({EvtName, payload_should_be_string, is, VMType})
case {Ix, VMType} of
{indexed, word} -> ok;
{notindexed, string} -> ok;
{indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
end.
bfun(B, {IArgs, IExpr, IRet}) ->
@@ -169,16 +170,15 @@ builtin_event(EventT) ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
IsIndexed = fun(T) -> aeso_syntax:get_ann(indexed, T, false) end,
Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
end,
Clause =
fun(_Tag, {con, _, Con}, Types) ->
Indexed = [ Var || {Var, Type} <- lists:zip(ArgPats(Types), Types),
IsIndexed(Type) ],
fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
end,
@@ -189,8 +189,8 @@ builtin_event(EventT) ->
{[{"e", event}],
{switch, v(e),
[{Pat(Tag, Types), Clause(Tag, Con, Types)}
|| {Tag, {constr_t, _, Con, Types}} <- lists:zip(Tags, Cons) ]},
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
{tuple, []}}.
%% Abort primitive.
+304 -75
View File
@@ -11,41 +11,64 @@
-export([ file/1
, file/2
, from_string/2
, check_call/2
, check_call/4
, create_calldata/3
, version/0
, sophia_type_to_typerep/1
, to_sophia_value/4
, to_sophia_value/5
, decode_calldata/3
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
-type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
pp_icode| pp_assembler | pp_bytecode.
-type option() :: pp_sophia_code
| pp_ast
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}.
-type options() :: [option()].
-export_type([ option/0
, options/0
]).
-define(COMPILER_VERSION_1, 1).
-define(COMPILER_VERSION_2, 2).
-define(COMPILER_VERSION, ?COMPILER_VERSION_2).
-spec version() -> pos_integer().
-spec version() -> {ok, binary()} | {error, term()}.
version() ->
?COMPILER_VERSION.
case lists:keyfind(aesophia, 1, application:loaded_applications()) of
false ->
case application:load(aesophia) of
ok ->
case application:get_key(aesophia, vsn) of
{ok, VsnString} ->
{ok, list_to_binary(VsnString)};
undefined ->
{error, failed_to_load_aesophia}
end;
Err = {error, _} ->
Err
end;
{_App, _Des, VsnString} ->
{ok, list_to_binary(VsnString)}
end.
-spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) ->
file(Filename, []).
Dir = filename:dirname(Filename),
{ok, Cwd} = file:get_cwd(),
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options) ->
case read_contract(File) of
{ok, Bin} -> from_string(Bin, Options);
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} ->
ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
@@ -56,22 +79,16 @@ from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
ok = pp_sophia_code(Ast, Options),
ok = pp_ast(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% pp_types is handled inside aeso_ast_infer_types.
ok = pp_typed_ast(TypedAst, Options),
ICode = to_icode(TypedAst, Options),
TypeInfo = extract_type_info(ICode),
ok = pp_icode(ICode, Options),
Assembler = assemble(ICode, Options),
ok = pp_assembler(Assembler, Options),
#{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
ok = pp_bytecode(ByteCode, Options),
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => version(),
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
@@ -87,38 +104,69 @@ from_string(ContractString, Options) ->
%% General programming errors in the compiler just signal error.
end.
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
string_to_icode(ContractString, Options0) ->
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
pp_typed_ast(TypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
#{ typed_ast => TypedAst,
type_env => TypeEnv,
icode => Icode }.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call").
-define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
%% Takes a string containing a contract with a declaration/prototype of a
%% function (foo, say) and a function __call() = foo(args) calling this
%% function (foo, say) and adds function __call() = foo(args) calling this
%% function. Returns the name of the called functions, typereps and Erlang
%% terms for the arguments.
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
%% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()}
when Type :: term().
check_call(ContractString, Options) ->
check_call(Source, "init" = FunName, Args, Options) ->
PatchFun = fun(T) -> {tuple, [typerep, T]} end,
case check_call(Source, FunName, Args, Options, PatchFun) of
Err = {error, _} when Args == [] ->
%% Try with default init-function
case check_call(insert_init_function(Source, Options), FunName, Args, Options, PatchFun) of
{error, _} -> Err; %% The first error is most likely better...
Res -> Res
end;
Res ->
Res
end;
check_call(Source, FunName, Args, Options) ->
PatchFun = fun(T) -> T end,
check_call(Source, FunName, Args, Options, PatchFun).
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try
Ast = parse(ContractString, Options),
ok = pp_sophia_code(Ast, Options),
ok = pp_ast(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]),
%% First check the contract without the __call function and no permissive literals
#{} = string_to_icode(ContractString0, Options),
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ok = pp_typed_ast(TypedAst, Options),
Icode = to_icode(TypedAst, Options),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
ok = pp_icode(Icode, Options),
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
{ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms}
{ok, FunName, {ArgVMTypes, PatchFun(RetVMType)}, ArgTerms}
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
@@ -132,44 +180,203 @@ check_call(ContractString, Options) ->
fun (E) -> io_lib:format("~p", [E]) end)}
end.
-spec create_calldata(map(), string(), string()) ->
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
| {error, argument_syntax_error}.
create_calldata(Contract, "", CallCode) when is_map(Contract) ->
case check_call(CallCode, []) of
{ok, FunName, {ArgTypes, RetType}, Args} ->
aeso_abi:create_calldata(Contract, FunName, Args, ArgTypes, RetType);
%% Add the __call function to a contract.
-spec insert_call_function(string(), string(), [string()], options()) -> string().
insert_call_function(Code, FunName, Args, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
insert_init_function(Code, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "), "function init() = ()\n"
]).
last_contract_indent(Decls) ->
case lists:last(Decls) of
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, _Options) ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err
end;
create_calldata(Contract, Function, Argument) when is_map(Contract) ->
%% Slightly hacky shortcut to let you get away without writing the full
%% call contract code.
%% Function should be "foo : type", and
%% Argument should be "Arg1, Arg2, .., ArgN" (no parens)
case string:lexemes(Function, ": ") of
%% If function is a single word fallback to old calldata generation
[FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument);
[FunName | _] ->
Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space
CallContract = lists:flatten(
[ "contract Call =\n"
, " function ", Function, "\n"
, " function __call() = ", FunName, "(", Args, ")"
]),
create_calldata(Contract, "", CallContract)
to_sophia_value(ContractString, FunName, ok, Data, Options) ->
try
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, Options),
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, translate_vm_value(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end.
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
%% TODO: somewhere else
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
{hash, [], <<Hi:256, Lo:256>>};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
none -> {con, [], "None"};
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [translate_vm_value(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
translate_vm_value(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
translate_vm_value(VmTypes, ConType, Args);
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}.
create_calldata(Code, Fun, Args) ->
case check_call(Code, Fun, Args, []) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end.
-spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}.
decode_calldata(ContractString, FunName, Calldata) ->
try
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, []),
{ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args),
Type0 = {tuple_t, [], ArgTypes},
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end.
get_arg_icode(Funs) ->
[Args] = [ Args || {?CALL_NAME, _, _, {funcall, _, Args}, _} <- Funs ],
Args.
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
[] -> error({missing_call_function, Funs})
end.
get_call_type([{contract, _, _, Defs}]) ->
case [ {FunName, FunType}
case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _,
{app, _,
{typed, _, {id, _, FunName}, FunType}, _}, _}} <- Defs ] of
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call};
[] -> {error, missing_call_function}
end;
@@ -177,8 +384,20 @@ get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract
get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
case lists:flatmap(GetType, Defs) of
[{Args, Ret}] -> {ok, Args, Ret};
[] -> {error, missing_function}
end;
get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
get_decode_type(FunName, Contracts).
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeso_heap:to_binary().
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
@@ -211,10 +430,7 @@ icode_to_term(T, V) ->
icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
parse(C,_Options) ->
parse_string(C).
to_icode(TypedAst, Options) ->
ast_to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) ->
@@ -228,7 +444,9 @@ to_bytecode([Op|Rest], Options) ->
to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) ->
TypeInfo = [aeso_abi:function_type_info(list_to_binary(Name), Args, TypeRep)
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name),
not lists:member(private, Attrs)
@@ -263,9 +481,9 @@ sophia_type_to_typerep(String) ->
catch _:_ -> {error, bad_type}
end.
parse_string(Text) ->
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text) of
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
@@ -278,12 +496,23 @@ parse_string(Text) ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString)
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error({Line, Pos}, ErrorString) ->
Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]),
parse_error(Pos, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
read_contract(Name) ->
file:read_file(Name).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
-301
View File
@@ -1,301 +0,0 @@
-module(aeso_heap).
-export([ to_binary/1
, to_binary/2
, from_heap/3
, from_binary/2
, from_binary/3
, maps_with_next_id/1
, set_next_id/2
, heap_fragment/3
, heap_value/3
, heap_value/4
, heap_value_pointer/1
, heap_value_maps/1
, heap_value_offset/1
, heap_value_heap/1
, heap_fragment_maps/1
, heap_fragment_offset/1
, heap_fragment_heap/1
]).
-export_type([binary_value/0, heap_value/0, offset/0, heap_fragment/0]).
-include("aeso_icode.hrl").
-include_lib("aesophia/include/aeso_heap.hrl").
-type word() :: non_neg_integer().
-type pointer() :: word().
-opaque heap_fragment() :: #heap{}.
-type offset() :: non_neg_integer().
-type binary_value() :: binary().
-type heap_value() :: {pointer(), heap_fragment()}.
-spec maps_with_next_id(heap_fragment()) -> #maps{}.
%% Create just a maps value, don't keep rest of Heap
maps_with_next_id(#heap{maps = #maps{next_id = N}}) ->
#maps{ next_id = N }.
-spec set_next_id(heap_fragment(), non_neg_integer()) -> heap_fragment().
set_next_id(Heap, N) ->
Heap#heap{ maps = Heap#heap.maps#maps{ next_id = N } }.
%% -- data type heap_fragment
-spec heap_fragment(binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
heap_fragment(Heap) ->
heap_fragment(#maps{ next_id = 0 }, 0, Heap).
-spec heap_fragment(#maps{}, offset(),
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
heap_fragment(Maps, Offset, Heap) ->
#heap{maps = Maps, offset = Offset, heap = Heap}.
-spec heap_fragment_maps(heap_fragment()) -> #maps{}.
heap_fragment_maps(#heap{maps = Maps}) ->
Maps.
-spec heap_fragment_offset(heap_fragment()) -> offset().
heap_fragment_offset(#heap{offset = Offs}) ->
Offs.
-spec heap_fragment_heap(heap_fragment()) -> binary() | #{non_neg_integer() => non_neg_integer()}.
heap_fragment_heap(#heap{heap = Heap}) ->
Heap.
%% -- data type heap_value
-spec heap_value(#maps{}, pointer(),
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_value().
heap_value(Maps, Ptr, Heap) ->
heap_value(Maps, Ptr, Heap, 0).
-spec heap_value(#maps{}, pointer(),
binary() | #{non_neg_integer() => non_neg_integer()}, offset()) -> heap_value().
heap_value(Maps, Ptr, Heap, Offs) ->
{Ptr, heap_fragment(Maps, Offs, Heap)}.
-spec heap_value_pointer(heap_value()) -> pointer().
heap_value_pointer({Ptr, _}) -> Ptr.
-spec heap_value_maps(heap_value()) -> #maps{}.
heap_value_maps({_, Heap}) -> Heap#heap.maps.
-spec heap_value_offset(heap_value()) -> offset().
heap_value_offset({_, Heap}) -> Heap#heap.offset.
-spec heap_value_heap(heap_value()) ->
binary() | #{non_neg_integer() => non_neg_integer()}.
heap_value_heap({_, Heap}) -> Heap#heap.heap.
%% -- Value to binary --------------------------------------------------------
-spec to_binary(aeso_sophia:data()) -> aeso_sophia:heap().
%% Encode the data as a heap where the first word is the value (for unboxed
%% types) or a pointer to the value (for boxed types).
to_binary(Data) ->
to_binary(Data, 0).
to_binary(Data, BaseAddress) ->
{Address, Memory} = to_binary1(Data, BaseAddress + 32),
R = <<Address:256, Memory/binary>>,
R.
%% Allocate the data in memory, from the given address. Return a pair
%% of memory contents from that address and the value representing the
%% data.
to_binary1(Data,_Address) when is_integer(Data) ->
{Data,<<>>};
to_binary1(Data, Address) when is_binary(Data) ->
%% a string
Words = aeso_memory:binary_to_words(Data),
{Address,<<(size(Data)):256, << <<W:256>> || W <- Words>>/binary>>};
to_binary1(none, Address) -> to_binary1({variant, 0, []}, Address);
to_binary1({some, Value}, Address) -> to_binary1({variant, 1, [Value]}, Address);
to_binary1(word, Address) -> to_binary1({?TYPEREP_WORD_TAG}, Address);
to_binary1(string, Address) -> to_binary1({?TYPEREP_STRING_TAG}, Address);
to_binary1(typerep, Address) -> to_binary1({?TYPEREP_TYPEREP_TAG}, Address);
to_binary1(function, Address) -> to_binary1({?TYPEREP_FUN_TAG}, Address);
to_binary1({list, T}, Address) -> to_binary1({?TYPEREP_LIST_TAG, T}, Address);
to_binary1({option, T}, Address) -> to_binary1({variant, [[], [T]]}, Address);
to_binary1({tuple, Ts}, Address) -> to_binary1({?TYPEREP_TUPLE_TAG, Ts}, Address);
to_binary1({variant, Cons}, Address) -> to_binary1({?TYPEREP_VARIANT_TAG, Cons}, Address);
to_binary1({map, K, V}, Address) -> to_binary1({?TYPEREP_MAP_TAG, K, V}, Address);
to_binary1({variant, Tag, Args}, Address) ->
to_binary1(list_to_tuple([Tag | Args]), Address);
to_binary1(Map, Address) when is_map(Map) ->
Size = maps:size(Map),
%% Sort according to binary ordering
KVs = lists:sort([ {to_binary(K), to_binary(V)} || {K, V} <- maps:to_list(Map) ]),
{Address, <<Size:256, << <<(byte_size(K)):256, K/binary,
(byte_size(V)):256, V/binary>> || {K, V} <- KVs >>/binary >>};
to_binary1({}, _Address) ->
{0, <<>>};
to_binary1(Data, Address) when is_tuple(Data) ->
{Elems,Memory} = to_binaries(tuple_to_list(Data),Address+32*size(Data)),
ElemsBin = << <<W:256>> || W <- Elems>>,
{Address,<< ElemsBin/binary, Memory/binary >>};
to_binary1([],_Address) ->
<<Nil:256>> = <<(-1):256>>,
{Nil,<<>>};
to_binary1([H|T],Address) ->
to_binary1({H,T},Address).
to_binaries([],_Address) ->
{[],<<>>};
to_binaries([H|T],Address) ->
{HRep,HMem} = to_binary1(H,Address),
{TRep,TMem} = to_binaries(T,Address+size(HMem)),
{[HRep|TRep],<<HMem/binary, TMem/binary>>}.
%% Interpret a return value (a binary) using a type rep.
-spec from_heap(Type :: ?Type(), Heap :: binary(), Ptr :: integer()) ->
{ok, term()} | {error, term()}.
from_heap(Type, Heap, Ptr) ->
try {ok, from_binary(#{}, Type, Heap, Ptr)}
catch _:Err ->
%% io:format("** Error: from_heap failed with ~p\n ~p\n", [Err, erlang:get_stacktrace()]),
{error, Err}
end.
%% Base address is the address of the first word of the given heap.
-spec from_binary(T :: ?Type(),
Heap :: binary(),
BaseAddr :: non_neg_integer()) ->
{ok, term()} | {error, term()}.
from_binary(T, Heap = <<V:256, _/binary>>, BaseAddr) ->
from_heap(T, <<0:BaseAddr/unit:8, Heap/binary>>, V);
from_binary(_, Bin, _BaseAddr) ->
{error, {binary_too_short, Bin}}.
-spec from_binary(?Type(), binary()) -> {ok, term()} | {error, term()}.
from_binary(T, Heap) ->
from_binary(T, Heap, 0).
from_binary(_, word, _, V) ->
V;
from_binary(_, signed_word, _, V) ->
<<N:256/signed>> = <<V:256>>,
N;
from_binary(_, bool, _, V) ->
case V of
0 -> false;
1 -> true
end;
from_binary(_, string, Heap, V) ->
StringSize = heap_word(Heap,V),
BitAddr = 8*(V+32),
<<_:BitAddr,Bytes:StringSize/binary,_/binary>> = Heap,
Bytes;
from_binary(_, {tuple, []}, _, _) ->
{};
from_binary(Visited, {tuple,Cpts}, Heap, V) ->
check_circular_refs(Visited, V),
NewVisited = Visited#{V => true},
ElementNums = lists:seq(0, length(Cpts)-1),
TypesAndPointers = lists:zip(Cpts, ElementNums),
ElementAddress = fun(Index) -> V + 32 * Index end,
Element = fun(Index) ->
heap_word(Heap, ElementAddress(Index))
end,
Convert = fun(Type, Index) ->
from_binary(NewVisited, Type, Heap, Element(Index))
end,
Elements = [Convert(T, I) || {T,I} <- TypesAndPointers],
list_to_tuple(Elements);
from_binary(Visited, {list, Elem}, Heap, V) ->
<<Nil:256>> = <<(-1):256>>,
if V==Nil ->
[];
true ->
{H,T} = from_binary(Visited, {tuple,[Elem,{list,Elem}]},Heap,V),
[H|T]
end;
from_binary(Visited, {option, A}, Heap, V) ->
from_binary(Visited, {variant_t, [{none, []}, {some, [A]}]}, Heap, V);
from_binary(Visited, {variant, Cons}, Heap, V) ->
Tag = heap_word(Heap, V),
Args = lists:nth(Tag + 1, Cons),
Visited1 = Visited#{V => true},
{variant, Tag, tuple_to_list(from_binary(Visited1, {tuple, Args}, Heap, V + 32))};
from_binary(Visited, {variant_t, TCons}, Heap, V) -> %% Tagged variants
{Tags, Cons} = lists:unzip(TCons),
{variant, I, Args} = from_binary(Visited, {variant, Cons}, Heap, V),
Tag = lists:nth(I + 1, Tags),
case Args of
[] -> Tag;
_ -> list_to_tuple([Tag | Args])
end;
from_binary(_Visited, {map, A, B}, Heap, Ptr) ->
%% FORMAT: [Size] [KeySize] Key [ValSize] Val .. [KeySize] Key [ValSize] Val
Size = heap_word(Heap, Ptr),
map_binary_to_value(A, B, Size, Heap, Ptr + 32);
from_binary(Visited, typerep, Heap, V) ->
check_circular_refs(Visited, V),
Tag = heap_word(Heap, V),
Arg1 = fun(T, I) -> from_binary(Visited#{V => true}, T, Heap, heap_word(Heap, V + 32 * I)) end,
Arg = fun(T) -> Arg1(T, 1) end,
case Tag of
?TYPEREP_WORD_TAG -> word;
?TYPEREP_STRING_TAG -> string;
?TYPEREP_TYPEREP_TAG -> typerep;
?TYPEREP_LIST_TAG -> {list, Arg(typerep)};
?TYPEREP_TUPLE_TAG -> {tuple, Arg({list, typerep})};
?TYPEREP_VARIANT_TAG -> {variant, Arg({list, {list, typerep}})};
?TYPEREP_MAP_TAG -> {map, Arg(typerep), Arg1(typerep, 2)};
?TYPEREP_FUN_TAG -> function
end.
map_binary_to_value(KeyType, ValType, N, Bin, Ptr) ->
%% Avoid looping on bogus sizes
MaxN = byte_size(Bin) div 64,
Heap = heap_fragment(Bin),
map_from_binary({value, KeyType, ValType}, min(N, MaxN), Heap, Ptr, #{}).
map_from_binary(_, 0, _, _, Map) -> Map;
map_from_binary({value, KeyType, ValType} = Output, I, Heap, Ptr, Map) ->
KeySize = get_word(Heap, Ptr),
KeyPtr = Ptr + 32,
KeyBin = get_chunk(Heap, KeyPtr, KeySize),
ValSize = get_word(Heap, KeyPtr + KeySize),
ValPtr = KeyPtr + KeySize + 32,
ValBin = get_chunk(Heap, ValPtr, ValSize),
%% Keys and values are self contained binaries
{ok, Key} = from_binary(KeyType, KeyBin),
{ok, Val} = from_binary(ValType, ValBin),
map_from_binary(Output, I - 1, Heap, ValPtr + ValSize, Map#{Key => Val}).
check_circular_refs(Visited, V) ->
case maps:is_key(V, Visited) of
true -> exit(circular_references);
false -> ok
end.
heap_word(Heap, Addr) when is_binary(Heap) ->
BitSize = 8*Addr,
<<_:BitSize,W:256,_/binary>> = Heap,
W;
heap_word(Heap, Addr) when is_map(Heap) ->
0 = Addr rem 32, %% Check that it's word aligned.
maps:get(Addr, Heap, 0).
get_word(#heap{offset = Offs, heap = Mem}, Addr) when Addr >= Offs ->
get_word(Mem, Addr - Offs);
get_word(Mem, Addr) when is_binary(Mem) ->
<<_:Addr/unit:8, Word:256, _/binary>> = Mem,
Word.
get_chunk(#heap{offset = Offs, heap = Mem}, Addr, Bytes) when Addr >= Offs ->
get_chunk(Mem, Addr - Offs, Bytes);
get_chunk(Mem, Addr, Bytes) when is_binary(Mem) ->
<<_:Addr/unit:8, Chunk:Bytes/binary, _/binary>> = Mem,
Chunk.
+43 -12
View File
@@ -9,19 +9,30 @@
%%%-------------------------------------------------------------------
-module(aeso_icode).
-export([new/1, pp/1, set_name/2, set_functions/2, map_typerep/2, option_typerep/1, get_constructor_tag/2]).
-export([new/1,
pp/1,
set_name/2,
set_namespace/2,
enter_namespace/2,
get_namespace/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]).
-include("aeso_icode.hrl").
-type type_def() :: fun(([aeso_sophia:type()]) -> aeso_sophia:type()).
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
-type bindings() :: any().
-type fun_dec() :: { string()
, [modifier()]
, arg_list()
, expr()
, aeso_sophia:type()}.
, aeb_aevm_data:type()}.
-type modifier() :: private | stateful.
@@ -29,12 +40,13 @@
-type icode() :: #{ contract_name => string()
, functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()]
, state_type => aeso_sophia:type()
, event_type => aeso_sophia:type()
, state_type => aeb_aevm_data:type()
, event_type => aeb_aevm_data:type()
, types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeso_sophia:type() }
, constructors => #{ string() => integer() } %% name to tag
, type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag
, options => [any()]
}.
@@ -73,10 +85,10 @@ builtin_types() ->
}.
builtin_constructors() ->
#{ "RelativeTTL" => 0
, "FixedTTL" => 1
, "None" => 0
, "Some" => 1 }.
#{ ["RelativeTTL"] => 0
, ["FixedTTL"] => 1
, ["None"] => 0
, ["Some"] => 1 }.
map_typerep(K, V) ->
{map, K, V}.
@@ -91,11 +103,30 @@ new_env() ->
set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag(string(), icode()) -> integer().
-spec get_constructor_tag([string()], icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name});
+2 -11
View File
@@ -1,14 +1,5 @@
-define(Type(), aeso_sophia:type()).
-define(TYPEREP_WORD_TAG, 0).
-define(TYPEREP_STRING_TAG, 1).
-define(TYPEREP_LIST_TAG, 2).
-define(TYPEREP_TUPLE_TAG, 3).
-define(TYPEREP_VARIANT_TAG, 4).
-define(TYPEREP_TYPEREP_TAG, 5).
-define(TYPEREP_MAP_TAG, 6).
-define(TYPEREP_FUN_TAG, 7).
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
-record(arg, {name::string(), type::?Type()}).
@@ -20,7 +11,7 @@
, args :: arg_list()
, body :: expr()}).
-record(var_ref, { name :: string() | {builtin, atom() | tuple()}}).
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
-record(prim_call_contract,
{ gas :: expr()
+2 -2
View File
@@ -17,7 +17,7 @@
i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> FName /= "init".
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(FName), ArgType, TypeRep),
<<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top
+299
View File
@@ -0,0 +1,299 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Fate backend for Sophia compiler
%%% @end
%%% Created : 11 Jan 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_fate).
-include("aeso_icode.hrl").
-export([compile/2]).
%% -- Preamble ---------------------------------------------------------------
-define(TODO(What), error({todo, ?FILE, ?LINE, ?FUNCTION_NAME, What})).
-define(i(__X__), {immediate, __X__}).
-define(a, {stack, 0}).
-record(env, { args = [], stack = [], tailpos = true }).
%% -- Debugging --------------------------------------------------------------
%% debug(Options, Fmt) -> debug(Options, Fmt, []).
debug(Options, Fmt, Args) ->
case proplists:get_value(debug, Options, true) of
true -> io:format(Fmt, Args);
false -> ok
end.
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(ICode, Options) ->
#{ contract_name := _ContractName,
state_type := _StateType,
functions := Functions } = ICode,
SFuns = functions_to_scode(Functions, Options),
SFuns1 = optimize_scode(SFuns, Options),
to_basic_blocks(SFuns1, Options).
functions_to_scode(Functions, Options) ->
maps:from_list(
[ {list_to_binary(Name), function_to_scode(Name, Args, Body, Type, Options)}
|| {Name, _Ann, Args, Body, Type} <- Functions, Name /= "init" ]). %% TODO: skip init for now
function_to_scode(Name, Args, Body, Type, Options) ->
debug(Options, "Compiling ~p ~p : ~p ->\n ~p\n", [Name, Args, Type, Body]),
ArgTypes = [ icode_type_to_fate(T) || {_, T} <- Args ],
ResType = icode_type_to_fate(Type),
SCode = to_scode(init_env(Args), Body),
debug(Options, " scode: ~p\n", [SCode]),
{{ArgTypes, ResType}, SCode}.
%% -- Types ------------------------------------------------------------------
%% TODO: the Fate types don't seem to be specified anywhere...
icode_type_to_fate(word) -> integer;
icode_type_to_fate(string) -> string;
icode_type_to_fate({tuple, Types}) ->
{tuple, lists:map(fun icode_type_to_fate/1, Types)};
icode_type_to_fate({list, Type}) ->
{list, icode_type_to_fate(Type)};
icode_type_to_fate(typerep) -> typerep;
icode_type_to_fate(Type) -> ?TODO(Type).
%% -- Phase I ----------------------------------------------------------------
%% Icode to structured assembly
%% -- Environment functions --
init_env(Args) ->
#env{ args = Args, stack = [], tailpos = true }.
push_env(Type, Env) ->
Env#env{ stack = [{"_", Type} | Env#env.stack] }.
notail(Env) -> Env#env{ tailpos = false }.
lookup_var(#env{ args = Args, stack = S }, X) ->
case {keyfind_index(X, 1, S), keyfind_index(X, 1, Args)} of
{false, false} -> false;
{false, Arg} -> {arg, Arg};
{Local, _} -> {stack, Local}
end.
%% -- The compiler --
to_scode(_Env, #integer{ value = N }) ->
[aeb_fate_code:push(?i(N))]; %% Doesn't exist (yet), translated by desugaring
to_scode(Env, #var_ref{name = X}) ->
case lookup_var(Env, X) of
false -> error({unbound_variable, X, Env});
{stack, N} -> [aeb_fate_code:dup(?i(N))];
{arg, N} -> [aeb_fate_code:push({arg, N})]
end;
to_scode(Env, #binop{ op = Op, left = A, right = B }) ->
[ to_scode(notail(Env), B)
, to_scode(push_env(binop_type_r(Op), Env), A)
, binop_to_scode(Op) ];
to_scode(Env, #ifte{decision = Dec, then = Then, else = Else}) ->
[ to_scode(notail(Env), Dec)
, {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
to_scode(_Env, Icode) -> ?TODO(Icode).
%% -- Operators --
binop_types('+') -> {word, word};
binop_types('-') -> {word, word};
binop_types('==') -> {word, word};
binop_types(Op) -> ?TODO(Op).
%% binop_type_l(Op) -> element(1, binop_types(Op)).
binop_type_r(Op) -> element(2, binop_types(Op)).
binop_to_scode('+') -> add_a_a_a(); %% Optimization introduces other variants
binop_to_scode('-') -> sub_a_a_a();
binop_to_scode('==') -> eq_a_a_a().
% binop_to_scode(Op) -> ?TODO(Op).
add_a_a_a() -> aeb_fate_code:add(?a, ?a, ?a).
sub_a_a_a() -> aeb_fate_code:sub(?a, ?a, ?a).
eq_a_a_a() -> aeb_fate_code:eq(?a, ?a, ?a).
%% -- Phase II ---------------------------------------------------------------
%% Optimize
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
flatten_s({ifte, Then, Else}) -> {ifte, flatten(Then), flatten(Else)};
flatten_s(I) -> I.
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
Code0 = flatten(Code),
debug(Options, "Optimizing ~s\n", [Name]),
debug(Options, " original : ~p\n", [Code0]),
Code1 = simplify(Code0),
debug(Options, " simplified: ~p\n", [Code1]),
Code2 = desugar(Code1),
debug(Options, " desugared : ~p\n", [Code2]),
{{Args, Res}, Code2}.
simplify([]) -> [];
simplify([I | Code]) ->
simpl_top(simpl_s(I), simplify(Code)).
simpl_s({ifte, Then, Else}) ->
{ifte, simplify(Then), simplify(Else)};
simpl_s(I) -> I.
%% add_i 0 --> nop
simpl_top({'ADD', _, ?i(0), _}, Code) -> Code;
%% push n, add_a --> add_i n
simpl_top({'PUSH', ?a, ?i(N)},
[{'ADD', ?a, ?a, ?a} | Code]) ->
simpl_top( aeb_fate_code:add(?a, ?i(N), ?a), Code);
%% push n, add_i m --> add_i (n + m)
simpl_top({'PUSH', ?a, ?i(N)}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top(aeb_fate_code:push(?i(N + M)), Code);
%% add_i n, add_i m --> add_i (n + m)
simpl_top({'ADD', ?a, ?i(N), ?a}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top({'ADD', ?a, ?i(N + M), ?a}, Code);
simpl_top(I, Code) -> [I | Code].
%% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_code:inc()];
desugar({ifte, Then, Else}) -> [{ifte, desugar(Then), desugar(Else)}];
desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I].
%% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks
to_basic_blocks(Funs, Options) ->
maps:from_list([ {Name, {{Args, Res},
bb(Name, Code ++ [aeb_fate_code:return()], Options)}}
|| {Name, {{Args, Res}, Code}} <- maps:to_list(Funs) ]).
bb(Name, Code, Options) ->
Blocks0 = blocks(Code),
Blocks = optimize_blocks(Blocks0),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
BBs = [ set_labels(Labels, B) || B <- Blocks ],
debug(Options, "Final code for ~s:\n ~p\n", [Name, BBs]),
maps:from_list(BBs).
%% -- Break up scode into basic blocks --
blocks(Code) ->
Top = make_ref(),
blocks([{Top, Code}], []).
blocks([], Acc) ->
lists:reverse(Acc);
blocks([{Ref, Code} | Blocks], Acc) ->
block(Ref, Code, [], Blocks, Acc).
block(Ref, [], CodeAcc, Blocks, BlockAcc) ->
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
block(Ref, [{ifte, Then, Else} | Code], Acc, Blocks, BlockAcc) ->
ThenLbl = make_ref(),
RestLbl = make_ref(),
block(Ref, Else ++ [{jump, RestLbl}],
[{jumpif, ThenLbl} | Acc],
[{ThenLbl, Then ++ [{jump, RestLbl}]},
{RestLbl, Code} | Blocks],
BlockAcc);
block(Ref, [I | Code], Acc, Blocks, BlockAcc) ->
block(Ref, Code, [I | Acc], Blocks, BlockAcc).
%% -- Reorder, inline, and remove dead blocks --
optimize_blocks(Blocks) ->
%% We need to look at the last instruction a lot, so reverse all blocks.
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
RBlocks = Rev(Blocks),
RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
RBlocks3 = remove_dead_blocks(RBlocks2),
Rev(RBlocks3).
%% Choose the next block based on the final jump.
reorder_blocks([], Acc) ->
lists:reverse(Acc);
reorder_blocks([{Ref, Code} | Blocks], Acc) ->
reorder_blocks(Ref, Code, Blocks, Acc).
reorder_blocks(Ref, Code, Blocks, Acc) ->
Acc1 = [{Ref, Code} | Acc],
case Code of
['RETURN'|_] -> reorder_blocks(Blocks, Acc1);
[{'RETURNR', _}|_] -> reorder_blocks(Blocks, Acc1);
[{jump, L}|_] ->
NotL = fun({L1, _}) -> L1 /= L end,
case lists:splitwith(NotL, Blocks) of
{Blocks1, [{L, Code1} | Blocks2]} ->
reorder_blocks(L, Code1, Blocks1 ++ Blocks2, Acc1);
{_, []} -> reorder_blocks(Blocks, Acc1)
end
end.
%% Inline short blocks ( 2 instructions)
inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
case maps:get(L, BlockMap, nocode) of
Dest when length(Dest) < 3 ->
%% Remove Ref to avoid infinite loops
inline_block(maps:remove(Ref, BlockMap), L, Dest) ++ Code;
_ -> Code0
end;
inline_block(_, _, Code) -> Code.
%% Remove unused blocks
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
BlockMap = maps:from_list(Blocks),
LiveBlocks = chase_labels([Top], BlockMap, #{}),
[ B || B = {L, _} <- Blocks, maps:is_key(L, LiveBlocks) ].
chase_labels([], _, Live) -> Live;
chase_labels([L | Ls], Map, Live) ->
Code = maps:get(L, Map),
Jump = fun({jump, A}) -> [A || not maps:is_key(A, Live)];
({jumpif, A}) -> [A || not maps:is_key(A, Live)];
(_) -> [] end,
New = lists:flatmap(Jump, Code),
chase_labels(New ++ Ls, Map, Live#{ L => true }).
%% -- Translate label refs to indices --
set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
set_labels(Labels, {jump, Ref}) -> aeb_fate_code:jump(maps:get(Ref, Labels));
set_labels(Labels, {jumpif, Ref}) -> aeb_fate_code:jumpif(?a, maps:get(Ref, Labels));
set_labels(_, I) -> I.
%% -- Helpers ----------------------------------------------------------------
with_ixs(Xs) ->
lists:zip(lists:seq(0, length(Xs) - 1), Xs).
keyfind_index(X, J, Xs) ->
case [ I || {I, E} <- with_ixs(Xs), X == element(J, E) ] of
[I | _] -> I;
[] -> false
end.
-19
View File
@@ -1,19 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Memory speifics that compiler and VM need to agree upon
%%% @end
%%% Created : 19 Dec 2018
%%%-------------------------------------------------------------------
-module(aeso_memory).
-export([binary_to_words/1]).
binary_to_words(<<>>) ->
[];
binary_to_words(<<N:256,Bin/binary>>) ->
[N|binary_to_words(Bin)];
binary_to_words(Bin) ->
binary_to_words(<<Bin/binary,0>>).
+1 -1
View File
@@ -19,7 +19,7 @@
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
-type pos() :: {integer(), integer()}.
-type pos() :: {string() | no_file, integer(), integer()} | {integer(), integer()}.
-type token() :: {atom(), pos(), term()} | {atom(), pos()}.
-type tokens() :: [token()].
-type error() :: {pos(), string() | no_error}.
+86 -25
View File
@@ -5,28 +5,37 @@
-module(aeso_parser).
-export([string/1,
string/2,
type/1]).
-include("aeso_parse_lib.hrl").
-spec string(string()) ->
{ok, aeso_syntax:ast()}
| {error, {aeso_parse_lib:pos(),
atom(),
term()}}
| {error, {aeso_parse_lib:pos(),
atom()}}.
-type parse_result() :: {ok, aeso_syntax:ast()}
| {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}.
-spec string(string()) -> parse_result().
string(String) ->
parse_and_scan(file(), String).
string(String, []).
-spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) ->
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
expand_includes(AST, Opts);
Err = {error, _} ->
Err
end.
type(String) ->
parse_and_scan(type(), String).
parse_and_scan(type(), String, []).
parse_and_scan(P, S) ->
case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error
end.
parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error
end.
%% -- Parsing rules ----------------------------------------------------------
@@ -36,7 +45,9 @@ decl() ->
?LAZY_P(
choice(
%% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -57,9 +68,14 @@ decl() ->
modifiers() ->
many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Node) ->
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
Node, Mods).
add_modifiers([], Node) -> Node;
add_modifiers(Mods = [Tok | _], Node) ->
%% Set the position to the position of the first modifier. This is
%% important for code transformation tools (like what we do in
%% create_calldata) to be able to get the indentation of the declaration.
set_pos(get_pos(Tok),
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
Node, Mods)).
%% -- Type declarations ------------------------------------------------------
@@ -301,6 +317,7 @@ binop(Ops) ->
con() -> token(con).
id() -> token(id).
tvar() -> token(tvar).
str() -> token(string).
token(Tag) ->
?RULE(tok(Tag),
@@ -336,10 +353,17 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{line, Line}, {col, Col}].
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) ->
{proplists:get_value(line, Ann),
{proplists:get_value(file, Ann),
proplists:get_value(line, Ann),
proplists:get_value(col, Ann)}.
get_ann(Ann) when is_list(Ann) -> Ann;
@@ -357,10 +381,10 @@ set_ann(Key, Val, Node) ->
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
get_pos(Node) ->
{get_ann(line, Node), get_ann(col, Node)}.
{current_file(), get_ann(line, Node), get_ann(col, Node)}.
set_pos({L, C}, Node) ->
set_ann(line, L, set_ann(col, C, Node)).
set_pos({F, L, C}, Node) ->
set_ann(file, F, set_ann(line, L, set_ann(col, C, Node))).
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
@@ -442,8 +466,10 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}.
return_error({L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err])).
return_error({no_file, L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) ->
@@ -455,3 +481,38 @@ bad_expr_err(Reason, E) ->
prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
case read_file(File, Opts) of
{ok, Bin} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case string(binary_to_list(Bin), Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Acc, Opts);
Err = {error, _} ->
Err
end;
{error, _} ->
{error, {get_pos(S), include_error, File}}
end;
expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts).
read_file(File, Opts) ->
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
{file_system, Paths} ->
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
{explicit_files, Files} ->
case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found};
Src -> {ok, Src}
end
end.
+9 -2
View File
@@ -147,6 +147,8 @@ decl(D, Options) ->
-spec decl(aeso_syntax:decl()) -> doc().
decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def),
@@ -234,8 +236,10 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) ->
tuple_type(Args);
type({named_arg_t, _, Name, Type, Default}) ->
follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
typed(name(Name), Type);
type(R = {record_t, _}) -> typedef(R);
type(T = {id, _, _}) -> name(T);
@@ -303,6 +307,8 @@ expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
{prefix, [A]} -> prefix(P, Op, A);
_ -> app(P, F, Args)
end;
expr_p(_, {app, _, C={Tag, _, _}, []}) when Tag == con; Tag == qcon ->
expr_p(0, C);
expr_p(P, {app, _, F, Args}) ->
app(P, F, Args);
%% -- Constants
@@ -314,6 +320,7 @@ expr_p(_, E = {int, _, N}) ->
text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) ->
+2 -2
View File
@@ -36,8 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal"],
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
KW = string:join(Keywords, "|"),
Rules =
-30
View File
@@ -1,30 +0,0 @@
-module(aeso_sophia).
-export_type([data/0,
type/0,
heap/0]).
-type type() :: word | signed_word | string | typerep | function
| {list, type()}
| {option, type()}
| {tuple, [type()]}
| {variant, [[type()]]}.
-type data() :: none
| {some, data()}
| {option, data()}
| word
| string
| {list, data()}
| {tuple, [data()]}
| {variant, integer(), [data()]}
| integer()
| binary()
| [data()]
| {}
| {data()}
| {data(), data()}.
-type heap() :: binary().
+9 -3
View File
@@ -8,14 +8,14 @@
-module(aeso_syntax).
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2]).
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]).
-export_type([arg/0, field_t/0, constructor_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]).
-type ast() :: [decl()].
@@ -35,6 +35,7 @@
-type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()}
@@ -140,3 +141,8 @@ get_ann(Key, Node) ->
get_ann(Key, Node, Default) ->
proplists:get_value(Key, get_ann(Node), Default).
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+134 -81
View File
@@ -6,89 +6,142 @@
%%%-------------------------------------------------------------------
-module(aeso_syntax_utils).
-export([used_ids/1, used_types/1]).
-export([used_ids/1, used_types/1, used/1]).
%% Var set combinators
none() -> [].
one(X) -> [X].
union_map(F, Xs) -> lists:umerge(lists:map(F, Xs)).
minus(Xs, Ys) -> Xs -- Ys.
-record(alg, {zero, plus, scoped}).
%% Compute names used by a definition or expression.
used_ids(Es) when is_list(Es) ->
union_map(fun used_ids/1, Es);
used_ids({bind, A, B}) ->
minus(used_ids(B), used_ids(A));
%% Declarations
used_ids({contract, _, _, Decls}) -> used_ids(Decls);
used_ids({type_decl, _, _, _}) -> none();
used_ids({type_def, _, _, _, _}) -> none();
used_ids({fun_decl, _, _, _}) -> none();
used_ids({letval, _, _, _, E}) -> used_ids(E);
used_ids({letfun, _, _, Args, _, E}) -> used_ids({bind, Args, E});
used_ids({letrec, _, Decls}) -> used_ids(Decls);
%% Args
used_ids({arg, _, X, _}) -> used_ids(X);
used_ids({named_arg, _, _, E}) -> used_ids(E);
%% Constants
used_ids({int, _, _}) -> none();
used_ids({bool, _, _}) -> none();
used_ids({hash, _, _}) -> none();
used_ids({unit, _}) -> none();
used_ids({string, _, _}) -> none();
used_ids({char, _, _}) -> none();
%% Expressions
used_ids({lam, _, Args, E}) -> used_ids({bind, Args, E});
used_ids({'if', _, A, B, C}) -> used_ids([A, B, C]);
used_ids({switch, _, E, Bs}) -> used_ids([E, Bs]);
used_ids({app, _, E, Es}) -> used_ids([E | Es]);
used_ids({proj, _, E, _}) -> used_ids(E);
used_ids({tuple, _, Es}) -> used_ids(Es);
used_ids({list, _, Es}) -> used_ids(Es);
used_ids({typed, _, E, _}) -> used_ids(E);
used_ids({record, _, Fs}) -> used_ids(Fs);
used_ids({record, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, KVs}) -> used_ids([ [K, V] || {K, V} <- KVs ]);
used_ids({map_get, _, M, K}) -> used_ids([M, K]);
used_ids({map_get, _, M, K, V}) -> used_ids([M, K, V]);
used_ids({block, _, Ss}) -> used_ids_s(Ss);
used_ids({Op, _}) when is_atom(Op) -> none();
used_ids({id, _, X}) -> [X];
used_ids({qid, _, _}) -> none();
used_ids({con, _, _}) -> none();
used_ids({qcon, _, _}) -> none();
%% Switch branches
used_ids({'case', _, P, E}) -> used_ids({bind, P, E});
%% Fields
used_ids({field, _, LV, E}) -> used_ids([LV, E]);
used_ids({field, _, LV, X, E}) -> used_ids([LV, {bind, X, E}]);
used_ids({proj, _, _}) -> none();
used_ids({map_get, _, E}) -> used_ids(E).
-type alg(A) :: #alg{ zero :: A
, plus :: fun((A, A) -> A)
, scoped :: fun((A, A) -> A) }.
%% Statements
used_ids_s([]) -> none();
used_ids_s([S | Ss]) ->
used_ids([S, {bind, bound_ids(S), {block, [], Ss}}]).
-type kind() :: decl | type | bind_type | expr | bind_expr.
bound_ids({letval, _, X, _, _}) -> one(X);
bound_ids({letfun, _, X, _, _, _}) -> one(X);
bound_ids({letrec, _, Decls}) -> union_map(fun bound_ids/1, Decls);
bound_ids(_) -> none().
-spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A
when E :: aeso_syntax:decl()
| aeso_syntax:typedef()
| aeso_syntax:field_t()
| aeso_syntax:constructor_t()
| aeso_syntax:type()
| aeso_syntax:expr()
| aeso_syntax:pat()
| aeso_syntax:arg()
| aeso_syntax:alt()
| aeso_syntax:elim()
| aeso_syntax:arg_expr()
| aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt().
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_decl, _, I, _} -> BindType(I);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
{variant_t, Cs} -> Type(Cs);
%% field_t() and constructor_t()
{field_t, _, _, T} -> Type(T);
{constr_t, _, _, Ts} -> Type(Ts);
%% type()
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
{app_t, _, T, Ts} -> Type([T | Ts]);
{tuple_t, _, Ts} -> Type(Ts);
%% named_arg_t()
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
%% expr()
{lam, _, Args, E} -> Scoped(BindExpr(Args), Expr(E));
{'if', _, A, B, C} -> Expr([A, B, C]);
{switch, _, E, Alts} -> Expr([E, Alts]);
{app, _, A, As} -> Expr([A | As]);
{proj, _, E, _} -> Expr(E);
{tuple, _, As} -> Expr(As);
{list, _, As} -> Expr(As);
{typed, _, E, T} -> Plus(Expr(E), Type(T));
{record, _, Fs} -> Expr(Fs);
{record, _, E, Fs} -> Expr([E | Fs]);
{map, _, E, Fs} -> Expr([E | Fs]);
{map, _, KVs} -> Sum([Expr([Key, Val]) || {Key, Val} <- KVs]);
{map_get, _, A, B} -> Expr([A, B]);
{map_get, _, A, B, C} -> Expr([A, B, C]);
{block, _, Ss} -> Expr(Ss);
%% field()
{field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]);
%% arg()
{arg, _, X, T} -> Plus(Expr(X), Type(T));
%% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim()
{proj, _, _} -> Zero;
{map_get, _, E} -> Expr(E);
%% arg_expr()
{named_arg, _, _, E} -> Expr(E);
_ -> Alg#alg.zero
end,
(Alg#alg.plus)(Top, Rec).
%% Name dependencies
used_ids(E) ->
[ X || {term, [X]} <- used(E) ].
used_types(T) ->
[ X || {type, [X]} <- used(T) ].
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg([entity()]).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Scoped = fun(Xs, Ys) ->
{Bound, Others} = lists:partition(IsBound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
lists:umerge(Xs -- Bound1, Others)
end,
#alg{ zero = []
, plus = fun lists:umerge/2
, scoped = Scoped }.
-spec used(_) -> [entity()].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
(type) -> type;
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
fold(entity_alg(),
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> []
end, decl, D),
lists:filter(NotBound, Xs).
used_types(Ts) when is_list(Ts) -> union_map(fun used_types/1, Ts);
used_types({type_def, _, _, _, T}) -> used_types(T);
used_types({alias_t, T}) -> used_types(T);
used_types({record_t, Fs}) -> used_types(Fs);
used_types({variant_t, Cs}) -> used_types(Cs);
used_types({field_t, _, _, T}) -> used_types(T);
used_types({constr_t, _, _, Ts}) -> used_types(Ts);
used_types({fun_t, _, Named, Args, T}) -> used_types([T | Named ++ Args]);
used_types({named_arg_t, _, _, T, _}) -> used_types(T);
used_types({app_t, _, T, Ts}) -> used_types([T | Ts]);
used_types({tuple_t, _, Ts}) -> used_types(Ts);
used_types({id, _, X}) -> one(X);
used_types({qid, _, _}) -> none();
used_types({con, _, _}) -> none();
used_types({qcon, _, _}) -> none();
used_types({tvar, _, _}) -> none().
+3 -2
View File
@@ -1,10 +1,11 @@
{application, aesophia,
[{description, "Contract Language for Aethernity"},
{vsn, "1.2.0"},
[{description, "Contract Language for aeternity"},
{vsn, "2.1.0"},
{registered, []},
{applications,
[kernel,
stdlib,
jsx,
syntax_tools,
getopt,
aebytecode
+12 -5
View File
@@ -4,6 +4,7 @@
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
@@ -14,11 +15,13 @@ usage() ->
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
case proplists:get_value(help, Opts, false) of
false ->
compile(Opts);
true ->
usage()
case Opts of
[version] ->
print_vsn();
[help] ->
usage();
_ ->
compile(Opts)
end;
{ok, {_, NonOpts}} ->
@@ -69,3 +72,7 @@ write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+117 -21
View File
@@ -19,8 +19,8 @@ sandbox(Code) ->
malicious_from_binary_test() ->
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
{ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
ok.
from_words(Ws) ->
@@ -54,35 +54,131 @@ encode_decode_test() ->
ok.
encode_decode_sophia_test() ->
{42} = encode_decode_sophia_string("int", "42"),
{1} = encode_decode_sophia_string("bool", "true"),
{0} = encode_decode_sophia_string("bool", "false"),
{<<"Hello">>} = encode_decode_sophia_string("string", "\"Hello\""),
{<<"Hello">>, [1,2,3], {variant, 1, [1]}} =
encode_decode_sophia_string(
"(string, list(int), option(bool))",
"\"Hello\", [1,2,3], Some(true)"),
Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of
{X, X} -> ok;
Other -> Other
end end,
ok = Check("int", "42"),
ok = Check("bool", "true"),
ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""),
ok = Check("(string, list(int), option(bool))",
"(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok.
encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]),
Code = [ "contract Call =\n"
, " function foo : ", SophiaType, " => _\n"
, " function __call() = foo(", String, ")\n" ],
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []),
Arg = list_to_tuple(Args),
Type = {tuple, Types},
io:format("Type ~p~n", [Type]),
Data = encode(Arg),
decode(Type, Data).
Code = [ "contract MakeCall =\n"
, " type arg_type = ", SophiaType, "\n"
, " type an_alias('a) = (string, 'a)\n"
, " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n"
, " function foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]),
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
{ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end;
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end.
calldata_test() ->
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
ok.
calldata_init_test() ->
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
calldata_indent_test() ->
Test = fun(Extra) ->
encode_decode_calldata_(
parameterized_contract(Extra, "foo", ["int"]),
"foo", ["42"], word)
end,
Test(" stateful function bla() = ()"),
Test(" type x = int"),
Test(" private function bla : int => int"),
Test(" public stateful function bla(x : int) =\n"
" x + 1"),
Test(" stateful private function bla(x : int) : int =\n"
" x + 1"),
ok.
parameterized_contract(FunName, Types) ->
parameterized_contract([], FunName, Types).
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n"
" function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() ->
Contract =
"contract OracleTest =\n"
" function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
ok.
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
ok.
encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word).
encode_decode_calldata(FunName, Types, Args, RetType) ->
Code = parameterized_contract(FunName, Types),
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
?assertEqual(RetVMType1, RetVMType),
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
case FunName of
"init" ->
ok;
_ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values})
end,
tuple_to_list(ArgTuple).
encode_decode(T, D) ->
?assertEqual(D, decode(T, encode(D))),
D.
encode(D) ->
aeso_heap:to_binary(D).
aeb_heap:to_binary(D).
decode(T,B) ->
{ok, D} = aeso_heap:from_binary(T, B),
{ok, D} = aeb_heap:from_binary(T, B),
D.
+76
View File
@@ -0,0 +1,76 @@
-module(aeso_aci_tests).
-include_lib("eunit/include/eunit.hrl").
do_test() ->
test_contract(1),
test_contract(2),
test_contract(3).
test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:encode(Contract),
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual(DecACI, aeso_aci:decode(JSON)).
test_cases(1) ->
Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> => [],
<<"functions">> =>
[#{<<"name">> => <<"a">>,
<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" function a(i : allan) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"allan">>,
<<"typedef">> => <<"int">>,
<<"vars">> => []}],
<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"name">> => <<"a">>,
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
" datatype bert('a) = Bin('a)\n"
" function a(i : bert(string)) = 1\n">>,
MapACI = #{<<"contract">> =>
#{<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> =>
[#{<<"C.bert">> => [<<"string">>]}]}],
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
<<"stateful">> => false}],
<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"bert">>,
<<"typedef">> =>
#{<<"variant">> =>
[#{<<"Bin">> => [<<"'a">>]}]},
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
" datatype bert('a) = Bin('a)\n"
" function a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}.
-73
View File
@@ -1,73 +0,0 @@
%%%=============================================================================
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Unit tests for the aeso_blake2 module
%%%
%%% In addition the aeso_blake2 module was compared to the C reference
%%% implementation by writing a QuickCheck property.
%%% @end
%%%=============================================================================
-module(aeso_blake2_tests).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
blake2b_test_() ->
{"Tests for BLAKE2b hash implementation",
[ fun() -> blake2b(Data) end || Data <- test_data_blake2b() ]}.
blake2b({Msg0, Key0, ExpectedOut0}) ->
Msg = mk_binary(Msg0),
Key = mk_binary(Key0),
ExpectedOut = mk_binary(ExpectedOut0),
Result = aeso_blake2:blake2b(byte_size(ExpectedOut), Msg, Key),
?assertEqual(Result, {ok, ExpectedOut}).
mk_binary(Bin) when is_binary(Bin) -> Bin;
mk_binary(HexStr) when is_list(HexStr) ->
<< << (erlang:list_to_integer([H], 16)):4 >> || H <- HexStr >>.
test_data_blake2b() ->
[ %% {Message, Key, ExpectedHash}
%% From Wikipedia
%% https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2
{<<>>,
<<>>,
"786A02F742015903C6C6FD852552D272912F4740E15847618A86E217F71F5419D25E1031AFEE585313896444934EB04B903A685B1448B755D56F701AFE9BE2CE"}
, {<<"The quick brown fox jumps over the lazy dog">>,
<<>>,
"A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918"}
%% From reference implementation testvectors
%% https://github.com/BLAKE2/BLAKE2/tree/master/testvectors
%%
%% Non-keyed
, {"00",
"",
"2FA3F686DF876995167E7C2E5D74C4C7B6E48F8068FE0E44208344D480F7904C36963E44115FE3EB2A3AC8694C28BCB4F5A0F3276F2E79487D8219057A506E4B"}
, {"0001",
"",
"1C08798DC641ABA9DEE435E22519A4729A09B2BFE0FF00EF2DCD8ED6F8A07D15EAF4AEE52BBF18AB5608A6190F70B90486C8A7D4873710B1115D3DEBBB4327B5"}
, {"00010203040506070809",
"",
"29102511D749DB3CC9B4E335FA1F5E8FACA8421D558F6A3F3321D50D044A248BA595CFC3EFD3D2ADC97334DA732413F5CBF4751C362BA1D53862AC1E8DABEEE8"}
%% Keyed
, {"",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"10ebb67700b1868efb4417987acf4690ae9d972fb7a590c2f02871799aaa4786b5e996e8f0f4eb981fc214b005f42d2ff4233499391653df7aefcbc13fc51568"}
, {"00",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"961f6dd1e4dd30f63901690c512e78e4b45e4742ed197c3c5e45c549fd25f2e4187b0bc9fe30492b16b0d0bc4ef9b0f34c7003fac09a5ef1532e69430234cebd"}
, {"0001",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"da2cfbe2d8409a0f38026113884f84b50156371ae304c4430173d08a99d9fb1b983164a3770706d537f49e0c916d9f32b95cc37a95b99d857436f0232c88a965"}
, {"00010203040506070809",
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
"4fe181f54ad63a2983feaaf77d1e7235c2beb17fa328b6d9505bda327df19fc37f02c4b6f0368ce23147313a8e5738b5fa2a95b29de1c7f8264eb77b69f585cd"}
].
-endif.
+67 -16
View File
@@ -10,15 +10,10 @@
-include_lib("eunit/include/eunit.hrl").
%% simple_compile_test_() -> ok.
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
simple_compile_test_() ->
{setup,
fun () -> ok end, %Setup
fun (_) -> ok end, %Cleanup
[ {"Testing the " ++ ContractName ++ " contract",
fun() ->
#{byte_code := ByteCode,
@@ -28,11 +23,34 @@ simple_compile_test_() ->
end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
<<"Type errors\n",ErrorString/binary>> = compile(ContractName),
check_errors(lists:sort(ExpectedErrors), ErrorString)
case compile(ContractName) of
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ]
}.
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
[ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination",
fun() ->
#{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
ok
end} ].
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
@@ -44,10 +62,13 @@ check_errors(Expect, ErrorString) ->
end.
compile(Name) ->
compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Name, Options) ->
String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, []) of
{ok,Map} -> Map;
{error,ErrorString} -> ErrorString
case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
{ok, Map} -> Map;
{error, ErrorString} -> ErrorString
end.
%% compilable_contracts() -> [ContractName].
@@ -70,7 +91,14 @@ compilable_contracts() ->
"stack",
"test",
"builtin_bug",
"builtin_map_get_bug"
"builtin_map_get_bug",
"nodeadcode",
"deadcode",
"variant_types",
"state_handling",
"events",
"include",
"basic_auth"
].
%% Contracts that should produce type errors
@@ -150,11 +178,11 @@ failing_contracts() ->
<<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>,
<<"No record type with fields y, z (at line 14, column 22)">>]}
<<"No record type with fields y, z (at line 14, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
, {"init_type_error",
[<<"Cannot unify string\n"
" and map(int, int)\n"
@@ -167,4 +195,27 @@ failing_contracts() ->
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events",
[<<"The payload type int (at line 10, column 30) should be string">>,
<<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
, {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash",
[<<"Cannot unify int\n"
" and string\n"
"when checking the record projection at line 12, column 40\n"
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]}
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
].
+2
View File
@@ -12,9 +12,11 @@ groups() ->
, aeso_parser_tests
, aeso_compiler_tests
, aeso_abi_tests
, aeso_aci_tests
]}].
aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests).
aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests).
aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests).
aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests).
aeso_aci_tests(_Config) -> ok = eunit:test(aeso_aci_tests).
-28
View File
@@ -1,28 +0,0 @@
-module(contract_tests).
-include_lib("eunit/include/eunit.hrl").
make_cmd() -> "make -C " ++ aeso_test_utils:contract_path().
contracts_test_() ->
{setup,
fun() -> os:cmd(make_cmd()) end,
fun(_) -> os:cmd(make_cmd() ++ " clean") end,
[ {"Testing the " ++ Contract ++ " contract",
fun() ->
?assertCmdOutput(Expected, filename:join(aeso_test_utils:contract_path(), Contract ++ "_test"))
end} || {Contract, Expected} <- contracts() ]}.
contracts() ->
[].
%% [{"voting",
%% "Delegate before vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% "Delegate after vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% }].
+25
View File
@@ -0,0 +1,25 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string, string)
| BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+24
View File
@@ -0,0 +1,24 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+6
View File
@@ -0,0 +1,6 @@
contract Bad =
include "included.aes"
namespace Foo =
function foo() = 42
function foo() = 43
+19
View File
@@ -0,0 +1,19 @@
// Contract replicating "normal" Aeternity authentication
contract BasicAuth =
record state = { nonce : int, owner : address }
function init() = { nonce = 1, owner = Call.caller }
function authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
function to_sign(h : hash, n : int) =
Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
+2 -2
View File
@@ -1,6 +1,6 @@
// Test more advanced chain interactions
contract Chain =
contract ChainTest =
record state = { last_bf : address }
@@ -10,4 +10,4 @@ contract Chain =
function miner() = Chain.coinbase
function save_coinbase() =
put(state{last_bf = Chain.coinbase})
put(state{last_bf = Chain.coinbase})
+21
View File
@@ -0,0 +1,21 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
function inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
+9
View File
@@ -0,0 +1,9 @@
include "included.aes"
include "../contracts/included2.aes"
contract Include =
function foo() =
Included.foo() < Included2a.bar()
function bar() =
Included2b.foo() > Included.foo()
+2
View File
@@ -0,0 +1,2 @@
namespace Included =
function foo() = 42
+5
View File
@@ -0,0 +1,5 @@
namespace Included2a =
function bar() = 43
namespace Included2b =
function foo() = 44
+5
View File
@@ -0,0 +1,5 @@
// You can't shadow existing contracts or namespaces.
contract Call =
function whatever() = ()
+21
View File
@@ -0,0 +1,21 @@
namespace Lib =
private function rev(xs, ys) =
switch(xs)
[] => ys
x :: xs => rev(xs, x :: ys)
function reverse(xs : list('a)) : list('a) = rev(xs, [])
function eqlist(xs : list(int), ys : list(int)) =
switch((xs, ys))
([], []) => true
(x :: xs, y :: ys) => x == y && eqlist(xs, ys)
_ => false
contract TestNamespaces =
function palindrome(xs : list(int)) : bool =
Lib.eqlist(xs, Lib.reverse(xs))
+21
View File
@@ -0,0 +1,21 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
function inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs)
-1
View File
@@ -18,7 +18,6 @@
contract SimpleStorage =
type event = int
record state = { data : int }
function init(value : int) : state = { data = value }
+13 -10
View File
@@ -1,19 +1,22 @@
contract Remote =
function look_at : (state) => ()
record rstate = { i : int, s : string, m : map(int, int) }
function look_at : (rstate) => ()
function return_s : (bool) => string
function return_m : (bool) => map(int, int)
function get : (state) => state
function get_i : (state) => int
function get_s : (state) => string
function get_m : (state) => map(int, int)
function get : (rstate) => rstate
function get_i : (rstate) => int
function get_s : (rstate) => string
function get_m : (rstate) => map(int, int)
function fun_update_i : (state, int) => state
function fun_update_s : (state, string) => state
function fun_update_m : (state, map(int, int)) => state
function fun_update_mk : (state, int, int) => state
function fun_update_i : (rstate, int) => rstate
function fun_update_s : (rstate, string) => rstate
function fun_update_m : (rstate, map(int, int)) => rstate
function fun_update_mk : (rstate, int, int) => rstate
contract StateHandling =
record state = { i : int, s : string, m : map(int, int) }
type state = Remote.rstate
function init(r : Remote, i : int) =
let state0 = { i = 0, s = "undefined", m = {} }
+13
View File
@@ -0,0 +1,13 @@
contract Remote =
type themap = map(int, string)
function foo : () => themap
contract Main =
type themap = map(string, int)
// Should fail
function foo(r : Remote) : themap = r.foo()