Compare commits

...

136 Commits

Author SHA1 Message Date
Ulf Norell 8a47603b62 Merge pull request #182 from aeternity/GH-181-prepare-4.1.0-rc1
GH-181 Prepare 4.1.0-rc1
2019-11-25 12:16:03 +01:00
Ulf Norell d4c9d369b1 Remove aesophia_cli and aesophia_http stuff from change log 2019-11-25 12:07:05 +01:00
Ulf Norell 8984ecc32d Bump version numbers 2019-11-25 11:55:31 +01:00
Ulf Norell 025c837886 4.1.0-rc1 change log 2019-11-25 11:52:42 +01:00
Ulf Norell 06e6138de1 Merge release notes for 4.0.0 release candidates into 4.0.0 entry 2019-11-25 11:42:05 +01:00
Ulf Norell 7eb4423e70 Merge pull request #180 from aeternity/fate-optimization-fixes
Sophia FATE backend overhaul
2019-11-25 11:29:35 +01:00
Ulf Norell bd64260e37 Remove impossible case
h/t dialyzer
2019-11-25 10:42:37 +01:00
Ulf Norell 6380e04a97 Strip switches on variants with only catch-all 2019-11-19 16:39:01 +01:00
Ulf Norell 2be3c9194d Optimize switches with a single successful branch
Typical case: require(_, _)
2019-11-19 15:14:00 +01:00
Ulf Norell d0cfd9cbbe Export to_basic_blocks for tests 2019-11-19 13:11:06 +01:00
Ulf Norell 7f7f53e044 Fix issue in basic block generation 2019-11-19 13:10:56 +01:00
Ulf Norell 7d8a773d6a Fix type specs 2019-11-19 13:10:26 +01:00
Ulf Norell d3f5d7f5c5 Fix lost dependency when inlining switch target 2019-11-18 12:20:32 +01:00
Ulf Norell 0b474843f9 Protect against ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell 1a628ab29f Fix bad annotations on switch-body 2019-11-18 12:20:32 +01:00
Ulf Norell 03ad1ad1dd Protect switch optimizations against ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell bfcb9ab324 Annotate switch bodies 2019-11-18 12:20:32 +01:00
Ulf Norell 4cc88be296 Desugar STORE R a to POP R 2019-11-18 12:20:32 +01:00
Ulf Norell 505603ad71 More optimizations for impure instructions 2019-11-18 12:20:32 +01:00
Ulf Norell 2d7c860e3a Rewrite liveness analysis 2019-11-18 12:20:32 +01:00
Ulf Norell 4976e0402e Don't crash constant propagation on ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell 0478df72fc Fix dependency analysis for loops 2019-11-18 12:20:32 +01:00
Ulf Norell 35b20800c9 Refactor argument inlining optimization 2019-11-18 12:20:32 +01:00
Ulf Norell d4c5c610ee Don't include stack and immediates in liveness annotations 2019-11-18 12:20:32 +01:00
Ulf Norell 6868bec3ed Fix bug in dependency analysis of GAS 2019-11-18 12:20:32 +01:00
Ulf Norell e5702c068c Impure == writes to the chain
Reading is ok
2019-11-18 12:20:31 +01:00
Ulf Norell a4b21063e3 Get rid of IsOp 2019-11-18 12:20:31 +01:00
Ulf Norell aca6b89fcf Store arguments are now separate from vars 2019-11-18 12:20:31 +01:00
Ulf Norell 13b196568b Handle reads from undefined variables in liveness analysis
Doesn't affect well-formed code, but makes testing easier.
2019-11-18 12:20:31 +01:00
Ulf Norell eba4f1c79c Call instructions read the function argument 2019-11-18 12:20:31 +01:00
Ulf Norell 1ca3018958 Don't run pretty printer if not pretty printing 2019-11-18 12:20:31 +01:00
Ulf Norell e6b5c5a526 Fix bug in short-cut for IS_NIL 2019-11-18 12:20:31 +01:00
Ulf Norell 47ad607dd5 Handle arbitrary store registers 2019-11-18 12:20:31 +01:00
Ulf Norell e8a54395bf Export optimize_fun for tests 2019-11-18 12:20:31 +01:00
Ulf Norell a87065c3a0 Merge pull request #177 from aeternity/GH-174-encode-decode-bits-lima
GH-174 Encode/decode bits. Now also for Lima
2019-11-18 12:19:47 +01:00
Ulf Norell 49f9ef955f Prefix format annotation for negative numbers 2019-11-18 12:16:04 +01:00
Ulf Norell f42353b300 Handle encoding/decoding bits
Fixes GH-174
2019-11-18 12:16:04 +01:00
Ulf Norell 5d23a76094 Merge pull request #173 from aeternity/GH-172-validate-byte-code
Add function to validate byte code against source code
2019-11-18 10:00:04 +01:00
Ulf Norell 878140e03c Add function to validate byte code against source code 2019-11-15 14:22:44 +01:00
Hans Svensson ac58eb4259 Merge pull request #171 from aeternity/GH-170-stdlib_in_escript
Add stdlib include handling when inside an escript
2019-11-11 11:40:29 +01:00
Hans Svensson 22b88bd393 Add stdlib include handling when inside an escript 2019-11-11 11:05:07 +01:00
Ulf Norell 83c3015899 Merge pull request #169 from aeternity/fix-illformed-lex-errors
Fix mangled lex errors
2019-10-21 09:00:43 +02:00
Ulf Norell ec9434fbfd Fix mangled lex errors 2019-10-21 08:53:32 +02:00
Hans Svensson b81312a714 Merge pull request #165 from aeternity/GH-164-prepare_release
Prepare release - v4.0.0
2019-10-11 15:52:29 +02:00
Hans Svensson 63c0b714d0 Prepare release - v4.0.0 2019-10-11 15:16:00 +02:00
Ulf Norell d018cc5819 Merge pull request #168 from aeternity/compiler-bug
Fix bug in compiler optimization
2019-10-10 14:35:56 +02:00
Ulf Norell f5b2732b04 Don't get rid of store updates! 2019-10-10 14:19:56 +02:00
Hans Svensson f86f7984f4 Merge pull request #167 from aeternity/radrow-patch-1
Fix not instantiated uvar
2019-10-10 08:31:48 +02:00
Radosław Rowicki 1ae0a42071 Fix not instantiated uvar 2019-10-09 17:42:12 +02:00
Ulf Norell 18ae801333 Merge pull request #162 from aeternity/address-to-contract
Add Address.to_contract
2019-10-01 14:28:32 +02:00
Ulf Norell 32d52f0abc Merge pull request #163 from aeternity/fail-on-multiple-contracts
Fail on defined functions in contract prototypes
2019-10-01 14:28:06 +02:00
Ulf Norell 5e6ff6c9a7 Nice type error if contract function is called as from a namespace 2019-10-01 14:13:56 +02:00
Ulf Norell 2d6d506d63 Fail on function definitions in contracts other than the main contract 2019-10-01 14:13:54 +02:00
Ulf Norell 482d22d46b Merge pull request #161 from aeternity/version-pragmas
add pragma to check compiler version
2019-10-01 14:10:06 +02:00
Ulf Norell a333888fb9 aebytecode commit 2019-09-30 14:47:26 +02:00
Ulf Norell 5fc6e18cd2 Add Address.to_contract
Casts an address to a (any) contract type.
2019-09-30 14:47:05 +02:00
Ulf Norell dd94a6bd67 add pragma to check compiler version 2019-09-27 17:31:10 +02:00
Hans Svensson 7f86b7d301 Merge pull request #160 from aeternity/prepare_rc5
Prepare 4.0.0-rc5
2019-09-27 09:34:49 +02:00
Hans Svensson e018c31ce1 Prepare 4.0.0-rc5 2019-09-27 09:06:52 +02:00
Ulf Norell 9234690d31 Merge pull request #159 from aeternity/more-compiler-fixes
Fix issues with liveness analysis
2019-09-27 08:45:37 +02:00
Ulf Norell 214a5f0a91 Fix issues with liveness analysis 2019-09-24 16:23:50 +02:00
Ulf Norell d4d3a9650a Merge pull request #158 from aeternity/fix-code-generation-bug
Don't confuse variables and store registers in fate asm generation
2019-09-24 14:55:08 +02:00
Ulf Norell b752965443 don't call aeb_fate_ops with {store, _} arg
(to not upset dialyzer)
2019-09-24 10:47:26 +02:00
Ulf Norell 0019d92e45 Don't confuse variables and store registers in fate asm generation 2019-09-23 16:52:16 +02:00
Ulf Norell 29f2168827 Merge pull request #157 from aeternity/big-number-literals
Allow underscore separators in number and bytes literals
2019-09-23 14:11:11 +02:00
Ulf Norell f81dc88526 Allow underscore separators in number and bytes literals
For instance, `1_000_000_000` or `#FFFF_FFFF_FFFF_FFFF`
2019-09-23 14:04:09 +02:00
Ulf Norell a21715a657 Merge pull request #156 from aeternity/fix-compiler-crash-bug
Fix bug with missing fields causing compiler crash
2019-09-23 11:56:30 +02:00
Ulf Norell 048c2ca98d Fix bug with missing fields causing compiler crash
... if under a lambda or switch.
2019-09-23 11:40:15 +02:00
Ulf Norell 662e5e70ef Merge pull request #153 from aeternity/src-loc-for-fun-app
Fix bug with missing source location for function applications
2019-09-14 15:36:02 +02:00
Ulf Norell 8e3483ced4 Merge pull request #154 from aeternity/bug-with-old-tuple-syntax
Fix bug when using old tuple syntax
2019-09-14 15:35:37 +02:00
Ulf Norell 6efc390bb6 Fix bug when using old tuple syntax 2019-09-14 14:44:25 +02:00
Ulf Norell 981027b2e7 Test case for function application source location 2019-09-14 12:12:55 +02:00
Ulf Norell 11d998b739 Set source location for function applications 2019-09-14 12:09:02 +02:00
Hans Svensson b481b3254b Merge pull request #152 from aeternity/prepare_rc4
Prepare v4.0.0-rc4
2019-09-13 08:12:05 +02:00
Hans Svensson 01a2efb7b8 Prepare v4.0.0-rc4 2019-09-13 08:08:47 +02:00
Hans Svensson a730fcc366 Merge pull request #151 from aeternity/fix_numeric_escapes
Fix numeric escapes in strings
2019-09-13 08:04:06 +02:00
Hans Svensson 457f9cf4ea Remove comment + CHANGELOG 2019-09-12 21:31:25 +02:00
Hans Svensson f34b6ed982 Fix numeric escapes 2019-09-12 21:17:01 +02:00
Hans Svensson 313c140c58 Merge pull request #150 from aeternity/fix_changelog
Fix CHANGELOG
2019-09-12 10:33:49 +02:00
Hans Svensson 48af37a41e Fix CHANGELOG 2019-09-12 10:29:11 +02:00
Hans Svensson 66511c9679 Merge pull request #149 from aeternity/fix_aevm_bytes_to_str
Fix bug in Bytes.to_str
2019-09-12 10:24:43 +02:00
Hans Svensson 8f0fe0b419 Fix bug in Bytes.to_str 2019-09-12 09:00:44 +02:00
Hans Svensson f80182ed18 Merge pull request #148 from aeternity/PT-168370661-prepare_4.0.0-rc3
PT-168370661 prepare 4.0.0-RC3
2019-09-11 09:55:21 +02:00
Hans Svensson d455671e24 Prepare v4.0.0-rc3 2019-09-10 15:14:19 +02:00
Hans Svensson 26a5a3b8ad implement option pp_assembler for FATE 2019-09-10 11:31:09 +02:00
Ulf Norell 92ac8b1f02 Merge pull request #147 from aeternity/bytes-concat
Bytes.concat and Bytes.split
2019-09-09 19:14:00 +02:00
Ulf Norell c849184c72 type spec 2019-09-09 18:47:06 +02:00
Ulf Norell f1b36c99ac Compile Bytes.concat/split for AEVM 2019-09-09 18:40:45 +02:00
Ulf Norell f09198b588 aebytecode commit 2019-09-09 18:40:45 +02:00
Ulf Norell cc531f9957 Test case for Bytes.concat/split 2019-09-09 18:40:45 +02:00
Ulf Norell 3ea8470dc8 Compile Bytes.concat and split to FATE 2019-09-09 18:40:45 +02:00
Ulf Norell 3ceeee22fa Don't forget to solve constraints 2019-09-09 18:40:45 +02:00
Ulf Norell e2ab41eeb2 Add Bytes.concat and Bytes.split to type checker 2019-09-09 18:40:45 +02:00
Ulf Norell 0f612ead90 Sort errors by position 2019-09-09 12:22:40 +02:00
Ulf Norell 9eeb9ab11d Don't freshen types in list comprehension generators 2019-09-09 11:00:26 +02:00
Ulf Norell 244ef6a6e2 Add a constraint field to type_sig 2019-09-09 11:00:26 +02:00
Ulf Norell 6551690dff Merge pull request #146 from aeternity/aevm-hash-on-bytes
[AEVM] Compile Crypto.(hash_fun) to String.(hash_fun) for byte arrays
2019-09-09 10:58:20 +02:00
Ulf Norell efe6f0ed06 [AEVM] Compile Crypto.(hash_fun) to String.(hash_fun) for byte arrays 2019-09-09 10:07:24 +02:00
Erik Stenman 263c297090 Upgrade aebytecode after eqc fix. (#145) 2019-09-06 15:20:01 +02:00
Erik Stenman d03cc50e03 Pt 168336524 renumber ops set base gas (#144)
* Use latest aebytecode with new opnumbers and gas.
2019-09-06 14:33:57 +02:00
Ulf Norell 76a789bd9e Merge pull request #143 from aeternity/minor-error-printing
Minor error printing
2019-09-06 12:10:33 +02:00
Hans Svensson 17f8cbb4d3 Merge pull request #141 from radrow/icode-char
Added chars in AEVM
2019-09-06 11:08:17 +02:00
Ulf Norell 46d244bfb4 aebytecode commit 2019-09-06 09:53:04 +02:00
Ulf Norell 9dac134477 Print the error kind in error messages 2019-09-06 09:51:17 +02:00
Ulf Norell 56b77f55fe Add json conversion of error messages 2019-09-06 09:37:02 +02:00
Hans Svensson 23534640c1 Merge pull request #142 from aeternity/more_structured_errors
More structured errors - also in aeso_compiler
2019-09-06 08:47:38 +02:00
Hans Svensson f07d1904ba Less redundant error message 2019-09-05 15:28:03 +02:00
Hans Svensson 47b3b9bcca Correct error type + new aebytecode 2019-09-05 15:14:17 +02:00
Hans Svensson 5a1acd9d18 Make aeso_compiler errors structured as well 2019-09-05 14:20:40 +02:00
radrow 92d1e10d0e Added chars in AEVM 2019-09-05 13:36:21 +02:00
Hans Svensson 37a37a169d File not found error 2019-09-05 11:16:31 +02:00
Ulf Norell ecfa04ba17 Merge pull request #140 from aeternity/PT-168026292-structured_error_messages
PT-168026292 structured error messages
2019-09-05 09:03:19 +02:00
Ulf Norell 97d58fcacd Nicer error for missing event type 2019-09-04 11:03:33 +02:00
Ulf Norell d8adfce465 Tests for unapplied builtins 2019-09-04 10:45:43 +02:00
Ulf Norell b9d141e035 Fix issue with AEVM eta expansion 2019-09-04 10:45:22 +02:00
Ulf Norell c37cc93abe Don't try to eta expand builtins with named arguments in AEVM 2019-09-04 10:21:30 +02:00
Ulf Norell 157ffbf9e2 Fix bug with unapplied builtins taking typerep arguments
(Oracle builtins and AENS.resolve)
2019-09-04 10:20:04 +02:00
Ulf Norell 602e99512f Fail gracefully on higher-order state in AEVM and accept it in FATE 2019-09-03 17:24:40 +02:00
Ulf Norell 325d69e96d Fail gracefully on bad top-level declaration 2019-09-03 17:24:06 +02:00
Ulf Norell 412b0b8b6d Improve some parse errors 2019-09-03 16:51:04 +02:00
Ulf Norell 61faa3e2dd Fix missing file name from type errors 2019-09-03 15:01:29 +02:00
Ulf Norell 69a4c1365b Test case for calling init function from inside the contract 2019-09-03 14:47:13 +02:00
Ulf Norell 0b56691533 Tell dialyzer to bugger off 2019-09-03 14:36:36 +02:00
Ulf Norell 30de1db163 More code errors 2019-09-03 14:35:13 +02:00
Ulf Norell adfa325f48 Don't use _main for the AEVM top entrypoint 2019-09-03 14:35:13 +02:00
Ulf Norell 0533ab27e1 Check that there are no maps in map keys already in type checker 2019-09-03 14:35:13 +02:00
Ulf Norell 510935d945 Framework and tests for code generation (icode/fcode) errors 2019-09-03 14:35:13 +02:00
Ulf Norell f2469a676d Refactor builtin compilation in icode
- Eta expand instead of failing on unapplied builtins
2019-09-03 14:35:13 +02:00
Ulf Norell db7bf7a730 Set error msg position to last occurrence of duplicate definition 2019-09-03 14:35:13 +02:00
Ulf Norell e37ac44726 Ensure that init is not payable 2019-09-03 14:35:13 +02:00
Hans Svensson 249b61238e Structured parse_errors and type_errors 2019-09-03 14:35:13 +02:00
Hans Svensson 9e955d5958 Remove unused aeso_constants 2019-09-03 14:35:13 +02:00
Hans Svensson f8cd3b87f3 Merge pull request #139 from aeternity/no-call-init
No calls to init-function (except when creating contract)
2019-09-03 13:17:06 +02:00
Hans Svensson f0c728ef1e set aebytecode commit 2019-09-03 13:15:19 +02:00
Ulf Norell 470970d937 Disallow calling init from inside the contract 2019-09-03 12:22:15 +02:00
Ulf Norell 58ab771dff Make init do the state updates in FATE (instead of a new INIT function) 2019-09-03 09:01:11 +02:00
74 changed files with 3120 additions and 1414 deletions
+36 -4
View File
@@ -6,13 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Added the `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
### Changed ### Changed
### Removed ### Removed
## [4.0.0-rc1] - 2019-08-22 ## [4.1.0-rc1] - 2019-11-25
### Added ### Added
- Support encoding and decoding bit fields in call arguments and results.
### Changed
- Various improvements to FATE code generator.
### Removed
## [4.0.0] - 2019-10-11
### Added
- `Address.to_contract` - casts an address to a (any) contract type.
- Pragma to check compiler version, e.g. `@compiler >= 4.0`.
- Handle numeric escapes, i.e. `"\x19Ethereum Signed Message:\n"`, and similar strings.
- `Bytes.concat` and `Bytes.split` are added to be able to
(de-)construct byte arrays.
- `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
- Checks that `init` is not called from other functions.
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many - FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
for. for.
@@ -29,6 +43,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
that shall be able to receive funds should be marked as payable. `Address.is_payable(a)` that shall be able to receive funds should be marked as payable. `Address.is_payable(a)`
can be used to check if an (contract) address is payable or not. can be used to check if an (contract) address is payable or not.
### Changed ### Changed
- Nice type error if contract function is called as from a namespace.
- Fail on function definitions in contracts other than the main contract.
- Bug fix in variable optimization - don't discard writes to the store/state.
- Bug fixes in error reporting.
- Bug fix in variable liveness analysis for FATE.
- Error messages are changed into a uniform format, and more helpful
messages have been added.
- `Crypto.<hash_fun>` and `String.<hash_fun>` for byte arrays now only
hash the actual byte array - not the internal ABI format.
- More strict checks for polymorphic oracles and higher order oracles
and entrypoints.
- `AENS.claim` is updated with a `NameFee` field - to be able to do
name auctions within contracts.
- Fixed a bug in `Bytes.to_str` for AEVM.
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and - New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and
regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`. regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`.
Parens are not necessary. Note it only affects the types, values remain as their were before, Parens are not necessary. Note it only affects the types, values remain as their were before,
@@ -136,7 +164,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0...HEAD
[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0
[4.0.0-rc5]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc4...v4.0.0-rc5
[4.0.0-rc4]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc3...v4.0.0-rc4
[4.0.0-rc3]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...v4.0.0-rc3
[4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1 [4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0 [3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0 [3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
+2 -2
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"72b2a58"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
@@ -15,7 +15,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "4.0.0-rc1"}, {relx, [{release, {aesophia, "4.1.0-rc1"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"72b2a581d5a6d488a208331da88de1a488ac2da1"}}, {ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
+1 -13
View File
@@ -74,21 +74,9 @@ do_contract_interface(Type, ContractString, Options) ->
string -> do_render_aci_json(JArray) string -> do_render_aci_json(JArray)
end end
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, 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. end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},
File diff suppressed because it is too large Load Diff
+117 -73
View File
@@ -20,7 +20,7 @@
-type fun_name() :: {entrypoint, binary()} -type fun_name() :: {entrypoint, binary()}
| {local_fun, [string()]} | {local_fun, [string()]}
| init | event. | event.
-type var_name() :: string(). -type var_name() :: string().
-type sophia_name() :: [string()]. -type sophia_name() :: [string()].
@@ -32,7 +32,7 @@
map_delete | map_member | map_size | string_length | map_delete | map_member | map_size | string_length |
string_concat | bits_set | bits_clear | bits_test | bits_sum | string_concat | bits_set | bits_clear | bits_test | bits_sum |
bits_intersection | bits_union | bits_difference | bits_intersection | bits_union | bits_difference |
contract_to_address | crypto_verify_sig | crypto_verify_sig_secp256k1 | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 |
crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_sha3 | crypto_sha256 | crypto_blake2b |
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1. crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
@@ -67,6 +67,7 @@
| {def_u, fun_name(), arity()} | {def_u, fun_name(), arity()}
| {remote_u, [ftype()], ftype(), fexpr(), fun_name()} | {remote_u, [ftype()], ftype(), fexpr(), fun_name()}
| {builtin_u, builtin(), arity()} | {builtin_u, builtin(), arity()}
| {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args.
| {lam, [var_name()], fexpr()}. | {lam, [var_name()], fexpr()}.
-type fsplit() :: {split, ftype(), var_name(), [fcase()]} -type fsplit() :: {split, ftype(), var_name(), [fcase()]}
@@ -140,6 +141,7 @@
functions := #{ fun_name() => fun_def() } }. functions := #{ fun_name() => fun_def() } }.
-define(HASH_BYTES, 32). -define(HASH_BYTES, 32).
%% -- Entrypoint ------------------------------------------------------------- %% -- Entrypoint -------------------------------------------------------------
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
@@ -196,9 +198,9 @@ builtins() ->
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
{["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
{["Int"], [{"to_str", 1}]}, {["Int"], [{"to_str", 1}]},
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} {["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
], ],
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes, || {NS, Funs} <- Scopes,
@@ -232,7 +234,7 @@ is_no_code(Env) ->
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
#{ builtins := Builtins } = Env, #{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main}, MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none}, builtins => Builtins#{[Main, "state"] => {get_state, none},
@@ -247,8 +249,10 @@ to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) ->
state_type => StateType, state_type => StateType,
event_type => EventType, event_type => EventType,
payable => Payable, payable => Payable,
functions => add_init_function(Env1, StateType, functions => add_init_function(Env1, MainCon, StateType,
add_event_function(Env1, EventType, Funs)) }; add_event_function(Env1, EventType, Funs)) };
to_fcode(_Env, [NotContract]) ->
fcode_error({last_declaration_must_be_contract, NotContract});
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
to_fcode(Env1, Code); to_fcode(Env1, Code);
@@ -270,21 +274,21 @@ decls_to_fcode(Env, Decls) ->
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) -> decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of case is_no_code(Env) of
false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)}); false -> fcode_error({missing_definition, Id});
true -> Env true -> Env
end; end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env; decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) -> decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def); typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, Body}) -> decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
Attrs = get_attributes(Ann), Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)), FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args), FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret), FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body), FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, FArgs, FRet) [ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ], || aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs, Def = #{ attrs => Attrs,
args => FArgs, args => FArgs,
@@ -294,9 +298,10 @@ decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, R
Env#{ functions := NewFuns }. Env#{ functions := NewFuns }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env(). -spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
Q = qname(Env, Name), Q = qname(Env, Name),
FDef = fun(Args) -> FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)), Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
case Def of case Def of
{record_t, Fields} -> {todo, Xs, Args, record_t, Fields}; {record_t, Fields} -> {todo, Xs, Args, record_t, Fields};
@@ -307,7 +312,9 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end || Con <- Cons ], end || Con <- Cons ],
{variant, FCons}; {variant, FCons};
{alias_t, Type} -> {todo, Xs, Args, alias_t, Type} {alias_t, Type} -> {todo, Xs, Args, alias_t, Type}
end end, end;
(Args) -> internal_error({type_arity_mismatch, Name, length(Args), length(Xs)})
end,
Constructors = Constructors =
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
@@ -328,6 +335,14 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end, end,
bind_type(Env2, Q, FDef). bind_type(Env2, Q, FDef).
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id});
_ -> ok
end;
check_state_and_event_types(_, _, _) -> ok.
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype(). -spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) -> type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type). type_to_fcode(Env, #{}, Type).
@@ -392,7 +407,31 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables %% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]); expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, _Type, {qid, _, X}) -> resolve_var(Env, X); expr_to_fcode(Env, Type, {qid, Ann, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(Ann, OType, QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
validate_aens_resolve_type(Ann, ResType, AensType),
TypeArgs = [{lit, {typerep, AensType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = bytes_split, Ar} ->
{fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type,
{builtin_u, B, Ar, [{lit, {int, N}}]};
Other -> Other
end;
%% Constructors %% Constructors
expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon ->
@@ -402,7 +441,7 @@ expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C
Arity = lists:nth(I + 1, Arities), Arity = lists:nth(I + 1, Arities),
case length(Args) == Arity of case length(Args) == Arity of
true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]};
false -> fcode_error({constructor_arity_mismatch, Con, length(Args), Arity}) false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity})
end; end;
%% Tuples %% Tuples
@@ -513,31 +552,13 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end; end;
%% Function calls %% Function calls
expr_to_fcode(Env, Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args), Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of case expr_to_fcode(Env, Fun) of
{builtin_u, B, _} when B =:= oracle_query; {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(B, FArgs ++ TypeArgs);
B =:= oracle_get_question; {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
B =:= oracle_get_answer; {def_u, F, _Ar} -> {def, F, FArgs};
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
%% Get the type of the oracle from the args or the expression itself
OType = get_oracle_type(B, Type, Args1),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(aeso_syntax:get_ann(Fun), QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _} when B =:= aens_resolve ->
%% Get the type we are assuming the name resolves to
AensType = type_to_fcode(Env, Type),
validate_aens_resolve_type(aeso_syntax:get_ann(Fun), AensType),
TypeArgs = [{lit, {typerep, AensType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
FFun -> FFun ->
%% FFun is a closure, with first component the function name and %% FFun is a closure, with first component the function name and
@@ -601,30 +622,37 @@ make_if(Cond, Then, Else) ->
{'let', X, Cond, make_if({var, X}, Then, Else)}. {'let', X, Cond, make_if({var, X}, Then, Else)}.
get_oracle_type(oracle_register, OType, _Args) -> OType; get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType;
get_oracle_type(oracle_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_question, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_answer, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_answer, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> OType; get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
validate_oracle_type(Ann, QType, RType) -> validate_oracle_type(Ann, Type, QType, RType) ->
ensure_monomorphic(QType, {polymorphic_query_type, Ann, QType}), ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {polymorphic_response_type, Ann, RType}), ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {higher_order_query_type, Ann, QType}), ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {higher_order_response_type, Ann, RType}), ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
ok. ok.
validate_aens_resolve_type(Ann, {variant, [[], [Type]]}) -> validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
ensure_monomorphic(Type, {polymorphic_aens_resolve, Ann, Type}), case FType of
ensure_first_order(Type, {higher_order_aens_resolve, Ann, Type}), string -> ok;
ok. address -> ok;
contract -> ok;
{oracle, _, _} -> ok;
oracle_query -> ok;
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Args, Ret) -> ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(T, {higher_order_entrypoint_argument, Ann, X, T}) [ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {X, T} <- Args ], || {{arg, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
ensure_first_order(Ret, {higher_order_entrypoint_return, Ann, Ret}), [ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok. ok.
ensure_monomorphic(Type, Err) -> ensure_monomorphic(Type, Err) ->
@@ -876,6 +904,7 @@ op_builtins() ->
string_length, string_concat, string_sha3, string_sha256, string_blake2b, string_length, string_concat, string_sha3, string_sha256, string_blake2b,
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
bits_difference, int_to_str, address_to_str, crypto_verify_sig, bits_difference, int_to_str, address_to_str, crypto_verify_sig,
address_to_contract,
crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1 crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
]. ].
@@ -904,19 +933,18 @@ builtin_to_fcode(Builtin, Args) ->
%% -- Init function -- %% -- Init function --
add_init_function(Env, StateType, Funs0) -> add_init_function(Env, Main, StateType, Funs0) ->
case is_no_code(Env) of case is_no_code(Env) of
true -> Funs0; true -> Funs0;
false -> false ->
Funs = add_default_init_function(Env, StateType, Funs0), Funs = add_default_init_function(Env, Main, StateType, Funs0),
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
InitFun = #{ args := InitArgs } = maps:get(InitName, Funs, none), InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Vars = [ {var, X} || {X, _} <- InitArgs ], Funs#{ InitName => InitFun#{ return => {tuple, []},
Funs#{ init => InitFun#{ return => {tuple, []}, body => {builtin, set_state, [InitBody]} } }
body => {builtin, set_state, [{def, InitName, Vars}]} } }
end. end.
add_default_init_function(_Env, StateType, Funs) -> add_default_init_function(_Env, Main, StateType, Funs) ->
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of case maps:get(InitName, Funs, none) of
%% Only add default init function if state is unit. %% Only add default init function if state is unit.
@@ -925,7 +953,7 @@ add_default_init_function(_Env, StateType, Funs) ->
args => [], args => [],
return => {tuple, []}, return => {tuple, []},
body => {tuple, []}} }; body => {tuple, []}} };
none -> fcode_error(missing_init_function); none -> fcode_error({missing_init_function, Main});
_ -> Funs _ -> Funs
end. end.
@@ -1007,9 +1035,14 @@ make_closure(FVs, Xs, Body) ->
lambda_lift_expr({lam, Xs, Body}) -> lambda_lift_expr({lam, Xs, Body}) ->
FVs = free_vars({lam, Xs, Body}), FVs = free_vars({lam, Xs, Body}),
make_closure(FVs, Xs, lambda_lift_expr(Body)); make_closure(FVs, Xs, lambda_lift_expr(Body));
lambda_lift_expr({Tag, F, Ar}) when Tag == def_u; Tag == builtin_u -> lambda_lift_expr(UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u ->
[Tag, F, Ar | _] = tuple_to_list(UExpr),
ExtraArgs = case UExpr of
{builtin_u, _, _, TypeArgs} -> TypeArgs;
_ -> []
end,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ],
Args = [{var, X} || X <- Xs], Args = [{var, X} || X <- Xs] ++ ExtraArgs,
Body = case Tag of Body = case Tag of
builtin_u -> builtin_to_fcode(F, Args); builtin_u -> builtin_to_fcode(F, Args);
def_u -> {def, F, Args} def_u -> {def, F, Args}
@@ -1116,7 +1149,7 @@ lookup_type(Env, {qid, _, Name}, Args) ->
lookup_type(Env, Name, Args); lookup_type(Env, Name, Args);
lookup_type(Env, Name, Args) -> lookup_type(Env, Name, Args) ->
case lookup_type(Env, Name, Args, not_found) of case lookup_type(Env, Name, Args, not_found) of
not_found -> error({unknown_type, Name}); not_found -> internal_error({unknown_type, Name});
Type -> Type Type -> Type
end. end.
@@ -1201,7 +1234,7 @@ resolve_var(Env, Q) -> resolve_fun(Env, Q).
resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) -> resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) ->
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
{not_found, not_found} -> fcode_error({unbound_variable, Q}); {not_found, not_found} -> internal_error({unbound_variable, Q});
{_, {B, none}} -> {builtin, B, []}; {_, {B, none}} -> {builtin, B, []};
{_, {B, Ar}} -> {builtin_u, B, Ar}; {_, {B, Ar}} -> {builtin_u, B, Ar};
{{Fun, Ar}, _} -> {def_u, Fun, Ar} {{Fun, Ar}, _} -> {def_u, Fun, Ar}
@@ -1259,6 +1292,7 @@ free_vars(Expr) ->
{remote_u, _, _, Ct, _} -> free_vars(Ct); {remote_u, _, _, Ct, _} -> free_vars(Ct);
{builtin, _, As} -> free_vars(As); {builtin, _, As} -> free_vars(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> []; %% Typereps are always literals
{con, _, _, As} -> free_vars(As); {con, _, _, As} -> free_vars(As);
{tuple, As} -> free_vars(As); {tuple, As} -> free_vars(As);
{proj, A, _} -> free_vars(A); {proj, A, _} -> free_vars(A);
@@ -1287,6 +1321,7 @@ used_defs(Expr) ->
{remote_u, _, _, Ct, _} -> used_defs(Ct); {remote_u, _, _, Ct, _} -> used_defs(Ct);
{builtin, _, As} -> used_defs(As); {builtin, _, As} -> used_defs(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> [];
{con, _, _, As} -> used_defs(As); {con, _, _, As} -> used_defs(As);
{tuple, As} -> used_defs(As); {tuple, As} -> used_defs(As);
{proj, A, _} -> used_defs(A); {proj, A, _} -> used_defs(A);
@@ -1327,6 +1362,7 @@ rename(Ren, Expr) ->
{def_u, _, _} -> Expr; {def_u, _, _} -> Expr;
{builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]};
{builtin_u, _, _} -> Expr; {builtin_u, _, _} -> Expr;
{builtin_u, _, _, _} -> Expr;
{remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]};
{remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F};
{con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]};
@@ -1441,8 +1477,14 @@ get_attributes(Ann) ->
indexed(Xs) -> indexed(Xs) ->
lists:zip(lists:seq(1, length(Xs)), Xs). lists:zip(lists:seq(1, length(Xs)), Xs).
fcode_error(Err) -> -dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
error(Err).
fcode_error(Error) ->
aeso_errors:throw(aeso_code_errors:format(Error)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)).
%% -- Pretty printing -------------------------------------------------------- %% -- Pretty printing --------------------------------------------------------
@@ -1460,7 +1502,6 @@ pp_fun(Name, #{ args := Args, return := Return, body := Body }) ->
pp_text(" : "), pp_ftype(Return), pp_text(" =")]), pp_text(" : "), pp_ftype(Return), pp_text(" =")]),
prettypr:nest(2, pp_fexpr(Body))). prettypr:nest(2, pp_fexpr(Body))).
pp_fun_name(init) -> pp_text('INIT');
pp_fun_name(event) -> pp_text(event); pp_fun_name(event) -> pp_text(event);
pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E)); pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E));
pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")). pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
@@ -1468,7 +1509,8 @@ pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
pp_text(<<>>) -> prettypr:text("\"\""); pp_text(<<>>) -> prettypr:text("\"\"");
pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)])));
pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S]));
pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)). pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A));
pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)).
pp_int(I) -> prettypr:text(integer_to_list(I)). pp_int(I) -> prettypr:text(integer_to_list(I)).
@@ -1542,6 +1584,8 @@ pp_fexpr({'let', X, A, B}) ->
pp_fexpr(B)]); pp_fexpr(B)]);
pp_fexpr({builtin_u, B, N}) -> pp_fexpr({builtin_u, B, N}) ->
pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); pp_beside([pp_text(B), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin_u, B, N, TypeArgs}) ->
pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin, B, As}) -> pp_fexpr({builtin, B, As}) ->
pp_call(pp_text(B), As); pp_call(pp_text(B), As);
pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) ->
+401 -306
View File
@@ -21,8 +21,8 @@ convert_typed(TypedTree, Options) ->
case lists:last(TypedTree) of case lists:last(TypedTree) of
{contract, Attrs, {con, _, Con}, _} -> {contract, Attrs, {con, _, Con}, _} ->
{proplists:get_value(payable, Attrs, false), Con}; {proplists:get_value(payable, Attrs, false), Con};
_ -> Decl ->
gen_error(last_declaration_must_be_contract) gen_error({last_declaration_must_be_contract, Decl})
end, end,
NewIcode = aeso_icode:set_payable(Payable, NewIcode = aeso_icode:set_payable(Payable,
aeso_icode:set_name(Name, aeso_icode:new(Options))), aeso_icode:set_name(Name, aeso_icode:new(Options))),
@@ -41,18 +41,19 @@ code([], Icode, Options) ->
%% Generate error on correct format. %% Generate error on correct format.
-dialyzer({nowarn_function, gen_error/1}).
gen_error(Error) -> gen_error(Error) ->
error({code_errors, [Error]}). aeso_errors:throw(aeso_code_errors:format(Error)).
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}, Options) -> add_default_init_function(Icode = #{namespace := NS, functions := Funs, state_type := State}, Options) ->
NoCode = proplists:get_value(no_code, Options, false), NoCode = proplists:get_value(no_code, Options, false),
{_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode), {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when NoCode -> Icode; false when NoCode -> Icode;
false when State /= {tuple, []} -> false when State /= {tuple, []} ->
gen_error(missing_init_function); gen_error({missing_init_function, NS});
false -> false ->
Type = {tuple, [typerep, {tuple, []}]}, Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
@@ -66,7 +67,7 @@ contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode), NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, 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(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest], contract_to_icode([Decl = {type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) -> Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode), TypeDef = make_type_def(Args, Def, Icode),
NewConstructors = NewConstructors =
@@ -82,10 +83,14 @@ contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode1 = Icode#{ types := Types#{ TName => TypeDef }, Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) }, constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; "state" when Args == [] ->
"state" -> gen_error(state_type_cannot_be_parameterized); case is_first_order_type(Def) of
true -> Icode1#{ state_type => ast_typerep(Def, Icode) };
false -> gen_error({higher_order_state, Decl})
end;
"state" -> gen_error({parameterized_state, Id});
"event" when Args == [] -> Icode1#{ event_type => Def }; "event" when Args == [] -> Icode1#{ event_type => Def };
"event" -> gen_error(event_type_cannot_be_parameterized); "event" -> gen_error({parameterized_event, Id});
_ -> Icode1 _ -> Icode1
end, end,
contract_to_icode(Rest, Icode2); contract_to_icode(Rest, Icode2);
@@ -113,8 +118,12 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode), NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode); contract_to_icode(Rest, NewIcode);
contract_to_icode([], Icode) -> Icode; contract_to_icode([], Icode) -> Icode;
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) -> contract_to_icode([{fun_decl, _, Id, _} | Code], Icode = #{ options := Options }) ->
contract_to_icode(Code, Icode); NoCode = proplists:get_value(no_code, Options, false),
case aeso_icode:in_main_contract(Icode) andalso not NoCode of
true -> gen_error({missing_definition, Id});
false -> contract_to_icode(Code, Icode)
end;
contract_to_icode([Decl | Code], Icode) -> contract_to_icode([Decl | Code], Icode) ->
io:format("Unhandled declaration: ~p\n", [Decl]), io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode). contract_to_icode(Code, Icode).
@@ -140,20 +149,7 @@ ast_type(T, Icode) ->
-define(option_t(A), {app_t, _, {id, _, "option"}, [A]}). -define(option_t(A), {app_t, _, {id, _, "option"}, [A]}).
-define(map_t(K, V), {app_t, _, {id, _, "map"}, [K, V]}). -define(map_t(K, V), {app_t, _, {id, _, "map"}, [K, V]}).
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([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)]);
%% Chain environment %% Chain environment
ast_body(?qid_app(["Chain", "balance"], [Address], _, _), Icode) ->
#prim_balance{ address = ast_body(Address, Icode) };
ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]);
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
prim_gas_left;
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address; ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator; ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address }; ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
@@ -166,133 +162,19 @@ ast_body({qid, _, ["Chain", "timestamp"]}, _Icode) -> prim_timestamp;
ast_body({qid, _, ["Chain", "block_height"]}, _Icode) -> prim_block_height; ast_body({qid, _, ["Chain", "block_height"]}, _Icode) -> prim_block_height;
ast_body({qid, _, ["Chain", "difficulty"]}, _Icode) -> prim_difficulty; ast_body({qid, _, ["Chain", "difficulty"]}, _Icode) -> prim_difficulty;
ast_body({qid, _, ["Chain", "gas_limit"]}, _Icode) -> prim_gas_limit; ast_body({qid, _, ["Chain", "gas_limit"]}, _Icode) -> prim_gas_limit;
%% TODO: eta expand!
ast_body({qid, _, ["Chain", "balance"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.balance'});
ast_body({qid, _, ["Chain", "block_hash"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.block_hash'});
ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'});
%% State %% State
ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state; ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) -> ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
#prim_put{ state = ast_body(NewState, Icode) }; #prim_put{ state = ast_body(NewState, Icode) };
ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) -> ast_body({typed, _, Id = {qid, _, [Con, "put"]}, Type}, Icode = #{ contract_name := Con }) ->
gen_error({underapplied_primitive, put}); %% TODO: eta eta_expand(Id, Type, Icode);
%% Abort
ast_body(?id_app("abort", [String], _, _), Icode) ->
builtin_call(abort, [ast_body(String, Icode)]);
ast_body(?id_app("require", [Bool, String], _, _), Icode) ->
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Authentication %% Authentication
ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) -> ast_body({qid, _, ["Auth", "tx_hash"]}, _Icode) ->
prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0}, prim_call(?PRIM_CALL_AUTH_TX_HASH, #integer{value = 0},
[], [], aeso_icode:option_typerep(word)); [], [], 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),
prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0},
[ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode),
ast_type_value(QType, Icode), ast_type_value(RType, Icode)],
[word, sign_t(), word, ttl_t(Icode), typerep, typerep], word);
ast_body(?qid_app(["Oracle", "query_fee"], [Oracle], _, _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0},
[ast_body(Oracle, Icode)], [word], word);
ast_body(?qid_app(["Oracle", "query"], [Oracle, Q, QFee, QTTL, RTTL], [_, QType, _, _, _], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode),
[ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)],
[word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word);
ast_body(?qid_app(["Oracle", "extend"], Args, _, _), Icode) ->
{Sign, [Oracle, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)],
[word, sign_t(), ttl_t(Icode)], {tuple, []});
ast_body(?qid_app(["Oracle", "respond"], Args, [_, _, RType], _), Icode) ->
{Sign, [Oracle, Query, R]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)],
[word, word, sign_t(), ast_type(RType, Icode)], {tuple, []});
ast_body(?qid_app(["Oracle", "get_question"], [Oracle, Q], [_, ?query_t(QType, _)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode));
ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
ast_body({qid, _, ["Oracle", "respond"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.respond'});
ast_body({qid, _, ["Oracle", "query_fee"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query_fee'});
ast_body({qid, _, ["Oracle", "get_answer"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_answer'});
ast_body({qid, _, ["Oracle", "get_question"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.get_question'});
%% Name service
ast_body(?qid_app(["AENS", "resolve"], [Name, Key], _, ?option_t(Type)), Icode) ->
case is_monomorphic(Type) of
true ->
case ast_type(Type, Icode) of
T when T == word; T == string -> ok;
_ -> gen_error({invalid_result_type, 'AENS.resolve', Type})
end,
prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0},
[ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)],
[string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode)));
false ->
gen_error({unresolved_result_type, 'AENS.resolve', Type})
end;
ast_body(?qid_app(["AENS", "preclaim"], Args, _, _), Icode) ->
{Sign, [Addr, CHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "claim"], Args, _, _), Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)],
[word, string, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "transfer"], Args, _, _), Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []});
ast_body(?qid_app(["AENS", "revoke"], Args, _, _), Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
ast_body({qid, _, ["AENS", "resolve"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.resolve'});
ast_body({qid, _, ["AENS", "preclaim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.preclaim'});
ast_body({qid, _, ["AENS", "claim"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.claim'});
ast_body({qid, _, ["AENS", "transfer"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.transfer'});
ast_body({qid, _, ["AENS", "revoke"]}, _Icode) -> gen_error({underapplied_primitive, 'AENS.revoke'});
%% Maps %% Maps
%% -- map lookup m[k] %% -- map lookup m[k]
@@ -306,35 +188,6 @@ ast_body({map_get, _, Map, Key, Val}, Icode) ->
Fun = {map_lookup_default, ast_typerep(ValType, Icode)}, Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]); builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
%% -- lookup functions
ast_body(?qid_app(["Map", "lookup"], [Key, Map], _, _), Icode) ->
map_get(Key, Map, Icode);
ast_body(?qid_app(["Map", "lookup_default"], [Key, Map, Val], _, _), Icode) ->
{_, ValType} = check_monomorphic_map(Map, Icode),
Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
ast_body(?qid_app(["Map", "member"], [Key, Map], _, _), Icode) ->
builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]);
ast_body(?qid_app(["Map", "size"], [Map], _, _), Icode) ->
builtin_call(map_size, [ast_body(Map, Icode)]);
ast_body(?qid_app(["Map", "delete"], [Key, Map], _, _), Icode) ->
map_del(Key, Map, Icode);
%% -- map conversion to/from list
ast_body(App = ?qid_app(["Map", "from_list"], [List], _, MapType), Icode) ->
Ann = aeso_syntax:get_ann(App),
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]);
ast_body(?qid_app(["Map", "to_list"], [Map], _, _), Icode) ->
map_tolist(Map, Icode);
ast_body({qid, _, ["Map", "from_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.from_list'});
%% ast_body({qid, _, ["Map", "to_list"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.to_list'});
ast_body({qid, _, ["Map", "lookup"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup'});
ast_body({qid, _, ["Map", "lookup_default"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.lookup_default'});
ast_body({qid, _, ["Map", "member"]}, _Icode) -> gen_error({underapplied_primitive, 'Map.member'});
%% -- map construction { k1 = v1, k2 = v2 } %% -- map construction { k1 = v1, k2 = v2 }
ast_body({typed, Ann, {map, _, KVs}, MapType}, Icode) -> ast_body({typed, Ann, {map, _, KVs}, MapType}, Icode) ->
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), {KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
@@ -356,104 +209,22 @@ ast_body({map, _, Map, [Upd]}, Icode) ->
ast_body({map, Ann, Map, [Upd | Upds]}, Icode) -> ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto
ast_body(?qid_app(["Crypto", "verify_sig"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "verify_sig_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, Addr, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)],
[word, bytes_t(20), bytes_t(65)], word);
ast_body(?qid_app(["Crypto", "ecrecover_secp256k1"], [Msg, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Sig, Icode)],
[word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20)));
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings
%% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]);
%% -- String concat
ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]);
%% -- String hash (sha3)
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) };
%% -- Bits %% -- Bits
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
ast_body({qid, _, ["Bits", "none"]}, _Icode) -> ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
#integer{ value = 0 }; #integer{ value = 0 };
ast_body({qid, _, ["Bits", "all"]}, _Icode) -> ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
#integer{ value = 1 bsl 256 - 1 }; #integer{ value = 1 bsl 256 - 1 };
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
%% -- Conversion %% -- Conversion
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]);
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Address", "is_payable"], [Addr], _, _), Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
ast_body(?qid_app(["Bytes", "to_int"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
ast_body(?qid_app(["Bytes", "to_str"], [Bytes], _, _), Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({typed, _, Id = {qid, _, _}, Type}, Icode) ->
case is_builtin_fun(Id, Icode) of
true -> eta_expand(Id, Type, Icode);
false -> ast_body(Id, Icode)
end;
ast_body({qid, _, Name}, _Icode) -> ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
@@ -461,6 +232,8 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
#integer{value = Value}; #integer{value = Value};
ast_body({int, _, Value}, _Icode) -> ast_body({int, _, Value}, _Icode) ->
#integer{value = Value}; #integer{value = Value};
ast_body({char, _, Value}, _Icode) ->
#integer{value = Value};
ast_body({bytes, _, Bin}, _Icode) -> ast_body({bytes, _, Bin}, _Icode) ->
case aeb_memory:binary_to_words(Bin) of case aeb_memory:binary_to_words(Bin) of
[Word] -> #integer{value = Word}; [Word] -> #integer{value = Word};
@@ -482,16 +255,12 @@ ast_body({list,_,Args}, Icode) ->
%% Typed contract calls %% Typed contract calls
ast_body({proj, _, {typed, _, Addr, {con, _, _}}, {id, _, "address"}}, Icode) -> ast_body({proj, _, {typed, _, Addr, {con, _, _}}, {id, _, "address"}}, Icode) ->
ast_body(Addr, Icode); %% Values of contract types _are_ addresses. ast_body(Addr, Icode); %% Values of contract types _are_ addresses.
ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id, _, FunName}}, ast_body({app, _, {typed, _, {proj, _, Addr, {id, _, FunName}},
{fun_t, _, NamedT, ArgsT, OutT}}, Args0}, Icode) -> {fun_t, _, NamedT, ArgsT, OutT}}, Args0}, Icode) ->
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
Args = Args0 -- NamedArgs, Args = Args0 -- NamedArgs,
ArgOpts = [ {Name, ast_body(Value, Icode)} || {named_arg, _, {id, _, Name}, Value} <- NamedArgs ], ArgOpts = [ {Name, ast_body(Value, Icode)} || {named_arg, _, {id, _, Name}, Value} <- NamedArgs ],
Defaults = [ {Name, ast_body(Default, Icode)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedT ], Defaults = [ {Name, ast_body(Default, Icode)} || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedT ],
%% TODO: eta expand
length(Args) /= length(ArgsT) andalso
gen_error({underapplied_contract_call,
string:join([Contract, FunName], ".")}),
ArgsI = [ ast_body(Arg, Icode) || Arg <- Args ], ArgsI = [ ast_body(Arg, Icode) || Arg <- Args ],
ArgType = ast_typerep({tuple_t, [], ArgsT}), ArgType = ast_typerep({tuple_t, [], ArgsT}),
Gas = proplists:get_value("gas", ArgOpts ++ Defaults), Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
@@ -509,9 +278,8 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
%% entrypoint on the callee side. %% entrypoint on the callee side.
type_hash= #integer{value = 0} type_hash= #integer{value = 0}
}; };
ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode) -> ast_body({proj, _, Con = {typed, _, _, {con, _, _}}, _Fun}, _Icode) ->
gen_error({underapplied_contract_call, gen_error({unapplied_contract_call, Con});
string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) -> ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode), Tag = aeso_icode:get_constructor_tag([Name], Icode),
@@ -529,7 +297,7 @@ ast_body({app, _, {'..', _}, [A, B]}, Icode) ->
#funcall #funcall
{ function = #var_ref{ name = ["ListInternal", "from_to"] } { function = #var_ref{ name = ["ListInternal", "from_to"] }
, args = [ast_body(A, Icode), ast_body(B, Icode)] }; , args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app, As, Fun, Args}, Icode) ->
case aeso_syntax:get_ann(format, As) of case aeso_syntax:get_ann(format, As) of
infix -> infix ->
{Op, _} = Fun, {Op, _} = Fun,
@@ -540,8 +308,13 @@ ast_body({app,As,Fun,Args}, Icode) ->
[A] = Args, [A] = Args,
#unop{op = Op, rand = ast_body(A, Icode)}; #unop{op = Op, rand = ast_body(A, Icode)};
_ -> _ ->
#funcall{function=ast_body(Fun, Icode), {typed, _, Fun1, {fun_t, _, _, ArgsT, RetT}} = Fun,
args=[ast_body(A, Icode) || A <- Args]} case is_builtin_fun(Fun1, Icode) of
true -> builtin_code(As, Fun1, Args, ArgsT, RetT, Icode);
false ->
#funcall{function=ast_body(Fun, Icode),
args=[ast_body(A, Icode) || A <- Args]}
end
end; end;
ast_body({list_comp, _, Yield, []}, Icode) -> ast_body({list_comp, _, Yield, []}, Icode) ->
#list{elems = [ast_body(Yield, Icode)]}; #list{elems = [ast_body(Yield, Icode)]};
@@ -571,9 +344,12 @@ ast_body({switch,_,A,Cases}, Icode) ->
#switch{expr=ast_body(A, Icode), #switch{expr=ast_body(A, Icode),
cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)} cases=[{ast_body(Pat, Icode),ast_body(Body, Icode)}
|| {'case',_,Pat,Body} <- Cases]}; || {'case',_,Pat,Body} <- Cases]};
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) -> ast_body({block, As, [{letval, _, Pat, _, E} | Rest]}, Icode) ->
#switch{expr=ast_body(E, Icode), E1 = ast_body(E, Icode),
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]}; Pat1 = ast_body(Pat, Icode),
Rest1 = ast_body({block, As, Rest}, Icode),
#switch{expr = E1,
cases = [{Pat1, Rest1}]};
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) -> ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode); ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
ast_body({block,_,[]}, _Icode) -> ast_body({block,_,[]}, _Icode) ->
@@ -600,8 +376,6 @@ ast_body({typed,_,{record,Attrs,Fields},{record_t,DefFields}}, Icode) ->
ast_body(E, Icode) ast_body(E, Icode)
end end
|| {field_t,_,{id,_,Name},_} <- DefFields]}; || {field_t,_,{id,_,Name},_} <- DefFields]};
ast_body({typed,_,{record,Attrs,_Fields},T}, _Icode) ->
gen_error({record_has_bad_type,Attrs,T});
ast_body({proj,_,{typed,_,Record,{record_t,Fields}},{id,_,FieldName}}, Icode) -> ast_body({proj,_,{typed,_,Record,{record_t,Fields}},{id,_,FieldName}}, Icode) ->
[Index] = [I [Index] = [I
|| {I,{field_t,_,{id,_,Name},_}} <- || {I,{field_t,_,{id,_,Name},_}} <-
@@ -638,16 +412,14 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
when Op == '=='; Op == '!='; when Op == '=='; Op == '!=';
Op == '<'; Op == '>'; Op == '<'; Op == '>';
Op == '<='; Op == '=<'; Op == '>=' -> Op == '<='; Op == '=<'; Op == '>=' ->
Monomorphic = is_monomorphic(Type), [ gen_error({cant_compare_type_aevm, Ann, Op, Type}) || not is_simple_type(Type) ],
case ast_typerep(Type, Icode) of case ast_typerep(Type, Icode) of
_ when not Monomorphic ->
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}; word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
OtherType -> OtherType ->
Neg = case Op of Neg = case Op of
'==' -> fun(X) -> X end; '==' -> fun(X) -> X end;
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end; '!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type})
end, end,
Args = [ast_body(A, Icode), ast_body(B, Icode)], Args = [ast_body(A, Icode), ast_body(B, Icode)],
Builtin = Builtin =
@@ -658,10 +430,10 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
case lists:usort(Types) of case lists:usort(Types) of
[word] -> [word] ->
builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]); builtin_call(str_equal_p, [ #integer{value = 32 * length(Types)} | Args]);
_ -> gen_error({cant_compare, Ann, Op, Type}) _ -> gen_error({cant_compare_type_aevm, Ann, Op, Type})
end; end;
_ -> _ ->
gen_error({cant_compare, Ann, Op, Type}) gen_error({cant_compare_type_aevm, Ann, Op, Type})
end, end,
Neg(Builtin) Neg(Builtin)
end; end;
@@ -670,18 +442,315 @@ ast_binop('++', _, A, B, Icode) ->
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
is_builtin_fun({qid, _, ["Chain","spend"]}, _Icode) -> true;
is_builtin_fun({qid, _, [Con, "Chain", "event"]}, #{ contract_name := Con }) -> true;
is_builtin_fun({qid, _, ["Chain", "balance"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Chain", "block_hash"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Call", "gas_left"]}, _Icode) -> true;
is_builtin_fun({id, _, "abort"}, _Icode) -> true;
is_builtin_fun({id, _, "require"}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "register"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "query_fee"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "query"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "extend"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "respond"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "get_question"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "get_answer"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "check"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Oracle", "check_query"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "resolve"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "preclaim"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "claim"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "transfer"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["AENS", "revoke"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "lookup"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "lookup_default"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "member"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "size"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "delete"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "from_list"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Map", "to_list"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "verify_sig"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "verify_sig_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "ecverify_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "ecrecover_secp256k1"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "sha3"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "sha256"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Crypto", "blake2b"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "sha256"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "blake2b"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "length"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "concat"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["String", "sha3"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "test"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "set"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "clear"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "union"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "intersection"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "difference"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bits", "sum"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Int", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_oracle"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_payable"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "to_contract"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "to_int"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "concat"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "split"]}, _Icode) -> true;
is_builtin_fun(_, _) -> false.
%% -- Code generation for builtin functions --
%% Chain operations
builtin_code(_, {qid, _, ["Chain","spend"]}, [To, Amount], _, _, Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
builtin_code(_, {qid, _, [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)]);
builtin_code(_, {qid, _, ["Chain", "balance"]}, [Address], _, _, Icode) ->
#prim_balance{ address = ast_body(Address, Icode) };
builtin_code(_, {qid, _, ["Chain", "block_hash"]}, [Height], _, _, Icode) ->
builtin_call(block_hash, [ast_body(Height, Icode)]);
builtin_code(_, {qid, _, ["Call", "gas_left"]}, [], _, _, _Icode) ->
prim_gas_left;
%% Abort
builtin_code(_, {id, _, "abort"}, [String], _, _, Icode) ->
builtin_call(abort, [ast_body(String, Icode)]);
builtin_code(_, {id, _, "require"}, [Bool, String], _, _, Icode) ->
builtin_call(require, [ast_body(Bool, Icode), ast_body(String, Icode)]);
%% Oracles
builtin_code(_, {qid, Ann, ["Oracle", "register"]}, Args, _, OracleType = ?oracle_t(QType, RType), Icode) ->
check_oracle_type(Ann, OracleType),
{Sign, [Acct, QFee, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_REGISTER, #integer{value = 0},
[ast_body(Acct, Icode), ast_body(Sign, Icode), ast_body(QFee, Icode), ast_body(TTL, Icode),
ast_type_value(QType, Icode), ast_type_value(RType, Icode)],
[word, sign_t(), word, ttl_t(Icode), typerep, typerep], word);
builtin_code(_, {qid, _, ["Oracle", "query_fee"]}, [Oracle], [_], _, Icode) ->
prim_call(?PRIM_CALL_ORACLE_QUERY_FEE, #integer{value = 0},
[ast_body(Oracle, Icode)], [word], word);
builtin_code(_, {qid, Ann, ["Oracle", "query"]}, [Oracle, Q, QFee, QTTL, RTTL], [OracleType, QType, _, _, _], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_QUERY, ast_body(QFee, Icode),
[ast_body(Oracle, Icode), ast_body(Q, Icode), ast_body(QTTL, Icode), ast_body(RTTL, Icode)],
[word, ast_type(QType, Icode), ttl_t(Icode), ttl_t(Icode)], word);
builtin_code(_, {qid, _, ["Oracle", "extend"]}, Args, [_, _], _, Icode) ->
{Sign, [Oracle, TTL]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_EXTEND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Sign, Icode), ast_body(TTL, Icode)],
[word, sign_t(), ttl_t(Icode)], {tuple, []});
builtin_code(_, {qid, Ann, ["Oracle", "respond"]}, Args, [OracleType, _, RType], _, Icode) ->
check_oracle_type(Ann, OracleType),
{Sign, [Oracle, Query, R]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_ORACLE_RESPOND, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode), ast_body(Sign, Icode), ast_body(R, Icode)],
[word, word, sign_t(), ast_type(RType, Icode)], {tuple, []});
builtin_code(_, {qid, Ann, ["Oracle", "get_question"]}, [Oracle, Q], [OracleType, ?query_t(QType, _)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_GET_QUESTION, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], ast_type(QType, Icode));
builtin_code(_, {qid, Ann, ["Oracle", "get_answer"]}, [Oracle, Q], [OracleType, ?query_t(_, RType)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
builtin_code(_, {qid, Ann, ["Oracle", "check"]}, [Oracle], [OracleType = ?oracle_t(Q, R)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
builtin_code(_, {qid, Ann, ["Oracle", "check_query"]}, [Oracle, Query], [OracleType, ?query_t(Q, R)], _, Icode) ->
check_oracle_type(Ann, OracleType),
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
[ast_body(Oracle, Icode), ast_body(Query, Icode),
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
[word, typerep, typerep], word);
%% Name service
builtin_code(_, {qid, Ann, ["AENS", "resolve"]}, [Name, Key], _, ?option_t(Type), Icode) ->
case is_monomorphic(Type) of
true ->
case ast_type(Type, Icode) of
T when T == word; T == string -> ok;
_ -> gen_error({invalid_aens_resolve_type, Ann, Type})
end,
prim_call(?PRIM_CALL_AENS_RESOLVE, #integer{value = 0},
[ast_body(Name, Icode), ast_body(Key, Icode), ast_type_value(Type, Icode)],
[string, string, typerep], aeso_icode:option_typerep(ast_type(Type, Icode)));
false ->
gen_error({invalid_aens_resolve_type, Ann, Type})
end;
builtin_code(_, {qid, _, ["AENS", "preclaim"]}, Args, _, _, Icode) ->
{Sign, [Addr, CHash]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_PRECLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(CHash, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "claim"]}, Args, _, _, Icode) ->
{Sign, [Addr, Name, Salt, NameFee]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_CLAIM, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Salt, Icode), ast_body(NameFee, Icode), ast_body(Sign, Icode)],
[word, string, word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "transfer"]}, Args, _, _, Icode) ->
{Sign, [FromAddr, ToAddr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_TRANSFER, #integer{value = 0},
[ast_body(FromAddr, Icode), ast_body(ToAddr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, word, sign_t()], {tuple, []});
builtin_code(_, {qid, _, ["AENS", "revoke"]}, Args, _, _, Icode) ->
{Sign, [Addr, Name]} = get_signature_arg(Args),
prim_call(?PRIM_CALL_AENS_REVOKE, #integer{value = 0},
[ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)],
[word, word, sign_t()], {tuple, []});
%% -- Maps
%% -- lookup functions
builtin_code(_, {qid, _, ["Map", "lookup"]}, [Key, Map], _, _, Icode) ->
map_get(Key, Map, Icode);
builtin_code(_, {qid, _, ["Map", "lookup_default"]}, [Key, Map, Val], _, _, Icode) ->
{_, ValType} = check_monomorphic_map(Map, Icode),
Fun = {map_lookup_default, ast_typerep(ValType, Icode)},
builtin_call(Fun, [ast_body(Map, Icode), ast_body(Key, Icode), ast_body(Val, Icode)]);
builtin_code(_, {qid, _, ["Map", "member"]}, [Key, Map], _, _, Icode) ->
builtin_call(map_member, [ast_body(Map, Icode), ast_body(Key, Icode)]);
builtin_code(_, {qid, _, ["Map", "size"]}, [Map], _, _, Icode) ->
builtin_call(map_size, [ast_body(Map, Icode)]);
builtin_code(_, {qid, _, ["Map", "delete"]}, [Key, Map], _, _, Icode) ->
map_del(Key, Map, Icode);
%% -- map conversion to/from list
builtin_code(_, {qid, Ann, ["Map", "from_list"]}, [List], _, MapType, Icode) ->
{KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode),
builtin_call(map_from_list, [ast_body(List, Icode), map_empty(KeyType, ValType, Icode)]);
builtin_code(_, {qid, _, ["Map", "to_list"]}, [Map], _, _, Icode) ->
map_tolist(Map, Icode);
%% Crypto
builtin_code(_, {qid, _, ["Crypto", "verify_sig"]}, [Msg, PK, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
builtin_code(_, {qid, _, ["Crypto", "verify_sig_secp256k1"]}, [Msg, PK, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_VERIFY_SIG_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
builtin_code(_, {qid, _, ["Crypto", "ecverify_secp256k1"]}, [Msg, Addr, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Addr, Icode), ast_body(Sig, Icode)],
[word, bytes_t(20), bytes_t(65)], word);
builtin_code(_, {qid, _, ["Crypto", "ecrecover_secp256k1"]}, [Msg, Sig], _, _, Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECRECOVER_SECP256K1, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(Sig, Icode)],
[word, bytes_t(65)], aeso_icode:option_typerep(bytes_t(20)));
builtin_code(_, {qid, _, ["Crypto", Op]}, [Term], [Type], _, Icode)
when Op == "sha3"; Op == "sha256"; Op == "blake2b" ->
generic_hash_primop(list_to_atom(Op), ast_body(Term, Icode), Type, Icode);
builtin_code(_, {qid, _, ["String", Op]}, [String], _, _, Icode)
when Op == "sha3"; Op == "sha256"; Op == "blake2b" ->
string_hash_primop(list_to_atom(Op), ast_body(String, Icode));
%% Strings
%% -- String length
builtin_code(_, {qid, _, ["String", "length"]}, [String], _, _, Icode) ->
builtin_call(string_length, [ast_body(String, Icode)]);
%% -- String concat
builtin_code(_, {qid, _, ["String", "concat"]}, [String1, String2], _, _, Icode) ->
builtin_call(string_concat, [ast_body(String1, Icode), ast_body(String2, Icode)]);
builtin_code(_, {qid, _, ["Bits", Fun]}, Args, _, _, Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
builtin_code(_, {qid, _, ["Bits", "sum"]}, [Bits], _, _, Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
builtin_code(_, {qid, _, ["Int", "to_str"]}, [Int], _, _, Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]);
builtin_code(_, {qid, _, ["Address", "to_str"]}, [Addr], _, _, Icode) ->
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
builtin_code(_, {qid, _, ["Address", "is_oracle"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Address", "is_contract"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Address", "is_payable"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word);
builtin_code(_, {qid, _, ["Address", "to_contract"]}, [Addr], _, _, Icode) ->
ast_body(Addr, Icode);
builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_int, N}, [ast_body(Bytes, Icode)]);
builtin_code(_, {qid, _, ["Bytes", "to_str"]}, [Bytes], _, _, Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes,
builtin_call({bytes_to_str, N}, [ast_body(Bytes, Icode)]);
builtin_code(_, {qid, _, ["Bytes", "concat"]}, [A, B], [TypeA, TypeB], _, Icode) ->
{bytes_t, _, M} = TypeA,
{bytes_t, _, N} = TypeB,
builtin_call({bytes_concat, M, N}, [ast_body(A, Icode), ast_body(B, Icode)]);
builtin_code(_, {qid, _, ["Bytes", "split"]}, [A], _, ResType, Icode) ->
{tuple_t, _, [{bytes_t, _, M}, {bytes_t, _, N}]} = ResType,
builtin_call({bytes_split, M, N}, [ast_body(A, Icode)]);
builtin_code(_As, Fun, _Args, _ArgsT, _RetT, _Icode) ->
gen_error({missing_code_for, Fun}).
eta_expand(Id = {_, Ann0, _}, Type = {fun_t, _, [], ArgsT, _}, Icode) ->
Ann = [{origin, system} | Ann0],
Xs = [ {arg, Ann, {id, Ann, "%" ++ integer_to_list(I)}, T} ||
{I, T} <- lists:zip(lists:seq(1, length(ArgsT)), ArgsT) ],
Args = [ {typed, Ann, X, T} || {arg, _, X, T} <- Xs ],
ast_body({lam, Ann, Xs, {app, Ann, {typed, Ann, Id, Type}, Args}}, Icode);
eta_expand(Id, _Type, _Icode) ->
gen_error({unapplied_builtin, Id}).
check_monomorphic_map({typed, Ann, _, MapType}, Icode) -> check_monomorphic_map({typed, Ann, _, MapType}, Icode) ->
check_monomorphic_map(Ann, MapType, Icode). check_monomorphic_map(Ann, MapType, Icode).
check_monomorphic_map(Ann, Type = ?map_t(KeyType, ValType), Icode) -> -dialyzer({nowarn_function, check_monomorphic_map/3}).
case is_monomorphic(KeyType) of check_monomorphic_map(Ann, ?map_t(KeyType, ValType), _Icode) ->
true -> Err = fun(Why) -> gen_error({invalid_map_key_type, Why, Ann, KeyType}) end,
case has_maps(ast_type(KeyType, Icode)) of [ Err(polymorphic) || not is_monomorphic(KeyType) ],
false -> {KeyType, ValType}; [ Err(function) || not is_first_order_type(KeyType) ],
true -> gen_error({cant_use_map_as_map_keys, Ann, Type}) {KeyType, ValType}.
end;
false -> gen_error({cant_compile_map_with_polymorphic_keys, Ann, Type})
end.
map_empty(KeyType, ValType, Icode) -> map_empty(KeyType, ValType, Icode) ->
prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0}, prim_call(?PRIM_CALL_MAP_EMPTY, #integer{value = 0},
@@ -689,8 +758,8 @@ map_empty(KeyType, ValType, Icode) ->
ast_type_value(ValType, Icode)], ast_type_value(ValType, Icode)],
[typerep, typerep], word). [typerep, typerep], word).
map_get(Key, Map = {typed, Ann, _, MapType}, Icode) -> map_get(Key, Map = {typed, _Ann, _, MapType}, Icode) ->
{_KeyType, ValType} = check_monomorphic_map(Ann, MapType, Icode), {_KeyType, ValType} = check_monomorphic_map(aeso_syntax:get_ann(Key), MapType, Icode),
builtin_call({map_lookup, ast_type(ValType, Icode)}, [ast_body(Map, Icode), ast_body(Key, Icode)]). builtin_call({map_lookup, ast_type(ValType, Icode)}, [ast_body(Map, Icode), ast_body(Key, Icode)]).
map_put(Key, Val, Map, Icode) -> map_put(Key, Val, Map, Icode) ->
@@ -720,14 +789,29 @@ map_upd(Key, Default, ValFun, Map = {typed, Ann, _, MapType}, Icode) ->
builtin_call(FunName, Args). builtin_call(FunName, Args).
check_entrypoint_type(Ann, Name, Args, Ret) -> check_entrypoint_type(Ann, Name, Args, Ret) ->
Check = fun(T, Err) -> CheckFirstOrder = fun(T, Err) ->
case is_simple_type(T) of case is_first_order_type(T) of
false -> gen_error(Err); false -> gen_error(Err);
true -> ok true -> ok
end end, end end,
[ Check(T, {entrypoint_argument_must_have_simple_type, Ann1, Name, X, T}) CheckMonomorphic = fun(T, Err) ->
case is_monomorphic(T) of
false -> gen_error(Err);
true -> ok
end end,
[ CheckFirstOrder(T, {invalid_entrypoint, higher_order, Ann1, Name, {argument, X, T}})
|| {arg, Ann1, X, T} <- Args ], || {arg, Ann1, X, T} <- Args ],
Check(Ret, {entrypoint_must_have_simple_return_type, Ann, Name, Ret}). CheckFirstOrder(Ret, {invalid_entrypoint, higher_order, Ann, Name, {result, Ret}}),
[ CheckMonomorphic(T, {invalid_entrypoint, polymorphic, Ann1, Name, {argument, X, T}})
|| {arg, Ann1, X, T} <- Args ],
CheckMonomorphic(Ret, {invalid_entrypoint, polymorphic, Ann, Name, {result, Ret}}).
check_oracle_type(Ann, Type = ?oracle_t(QType, RType)) ->
[ gen_error({invalid_oracle_type, Why, Which, Ann, Type})
|| {Why, Check} <- [{polymorphic, fun is_monomorphic/1},
{higher_order, fun is_first_order_type/1}],
{Which, T} <- [{query, QType}, {response, RType}],
not Check(T) ].
is_simple_type({tvar, _, _}) -> false; is_simple_type({tvar, _, _}) -> false;
is_simple_type({fun_t, _, _, _, _}) -> false; is_simple_type({fun_t, _, _, _, _}) -> false;
@@ -735,6 +819,11 @@ is_simple_type(Ts) when is_list(Ts) -> lists:all(fun is_simple_type/1, Ts);
is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T)); is_simple_type(T) when is_tuple(T) -> is_simple_type(tuple_to_list(T));
is_simple_type(_) -> true. is_simple_type(_) -> true.
is_first_order_type({fun_t, _, _, _, _}) -> false;
is_first_order_type(Ts) when is_list(Ts) -> lists:all(fun is_first_order_type/1, Ts);
is_first_order_type(T) when is_tuple(T) -> is_first_order_type(tuple_to_list(T));
is_first_order_type(_) -> true.
is_monomorphic({tvar, _, _}) -> false; is_monomorphic({tvar, _, _}) -> false;
is_monomorphic([H|T]) -> is_monomorphic([H|T]) ->
is_monomorphic(H) andalso is_monomorphic(T); is_monomorphic(H) andalso is_monomorphic(T);
@@ -761,15 +850,29 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
type_hash= #integer{value = TypeHash} type_hash= #integer{value = TypeHash}
}. }.
generic_hash_primop(PrimOp, Term, Type, Icode) -> generic_hash_primop(Op, Arg, {bytes_t, _, N}, _Icode) ->
%% Compile hashing bytes to String.hash. Makes it easier for the user to
%% predict the result.
string_hash_primop(Op, aeso_builtins:bytes_to_raw_string(N, Arg));
generic_hash_primop(Op, Arg, Type, Icode) ->
PrimOp = case Op of
sha3 -> ?PRIM_CALL_CRYPTO_SHA3;
sha256 -> ?PRIM_CALL_CRYPTO_SHA256;
blake2b -> ?PRIM_CALL_CRYPTO_BLAKE2B
end,
ArgType = ast_type(Type, Icode), ArgType = ast_type(Type, Icode),
TypeValue = type_value(ArgType), TypeValue = type_value(ArgType),
prim_call(PrimOp, #integer{value = 0}, prim_call(PrimOp, #integer{value = 0},
[TypeValue, ast_body(Term, Icode)], [TypeValue, Arg], [typerep, ArgType], word).
[typerep, ArgType], word).
string_hash_primop(PrimOp, String, Icode) -> string_hash_primop(sha3, String) ->
prim_call(PrimOp, #integer{value = 0}, [ast_body(String, Icode)], [string], word). #unop{ op = 'sha3', rand = String };
string_hash_primop(Op, String) ->
PrimOp = case Op of
sha256 -> ?PRIM_CALL_CRYPTO_SHA256_STRING;
blake2b -> ?PRIM_CALL_CRYPTO_BLAKE2B_STRING
end,
prim_call(PrimOp, #integer{value = 0}, [String], [string], word).
make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) -> make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
TVars = [ X || {tvar, _, X} <- Args ], TVars = [ X || {tvar, _, X} <- Args ],
@@ -870,14 +973,6 @@ ast_fun_to_icode(Name, Attrs, Args, Body, TypeRep, #{functions := Funs} = Icode)
NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs], NewFuns = [{Name, Attrs, Args, Body, TypeRep}| Funs],
aeso_icode:set_functions(NewFuns, Icode). aeso_icode:set_functions(NewFuns, Icode).
has_maps({map, _, _}) -> true;
has_maps(word) -> false;
has_maps(string) -> false;
has_maps(typerep) -> false;
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 not an 'entrypoint', or if it's not defined in the %% A function is private if not an 'entrypoint', or if it's not defined in the
%% main contract name space. (NOTE: changes when we introduce inheritance). %% main contract name space. (NOTE: changes when we introduce inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) -> is_private(Ann, #{ contract_name := MainContract } = Icode) ->
+131 -24
View File
@@ -10,6 +10,7 @@
-module(aeso_builtins). -module(aeso_builtins).
-export([ builtin_function/1 -export([ builtin_function/1
, bytes_to_raw_string/2
, check_event_type/1 , check_event_type/1
, used_builtins/1 ]). , used_builtins/1 ]).
@@ -44,7 +45,7 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker]; builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort]; builtin_deps1(require) -> [abort];
builtin_deps1(_) -> []. builtin_deps1(_) -> [].
@@ -89,7 +90,13 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)). -define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)). -define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}. op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
%% We generate a lot of B * 8 for integer B from BSL and BSR.
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
{integer, A * B};
simpl(Op) -> Op.
operand(A) when is_atom(A) -> v(A); operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I}; operand(I) when is_integer(I) -> {integer, I};
@@ -161,7 +168,10 @@ builtin_function(BF) ->
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N)); {bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N)); {bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker()); bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_()) string_reverse_ -> bfun(BF, builtin_string_reverse_())
end. end.
@@ -512,40 +522,60 @@ builtin_bytes_to_int(N) when N > 32 ->
end, end,
{[{"b", pointer}], Body, word}. {[{"b", pointer}], Body, word}.
builtin_bytes_to_str_worker() -> %% Two versions of this helper function, worker for sections not even 16 bytes long
%% and worker_x for the full sized chunks.
builtin_bytes_to_str_worker_x() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>, <<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}], {[{"w", word}, {"offs", word}, {"acc", word}],
{seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))), {ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
{seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]}, ?LET(b, ?BYTE(offs, w),
{inline_asm, []}}, ?LET(lo, ?BYTE(?MOD(b, 16), Tab),
{ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]}, ?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?LET(b, ?BYTE(offs, w), ?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
?LET(lo, ?BYTE(?MOD(b, 16), Tab), },
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker,
[?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
}
]},
word}. word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
},
word}.
builtin_bytes_to_str_body(Var, N) when N < 16 ->
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
builtin_bytes_to_str_body(Var, 16) ->
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N < 32 ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
builtin_bytes_to_str_body(Var, 32) ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N > 32 ->
WholeWords = ((N + 31) div 32) - 1,
lists:append(
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|| I <- lists:seq(0, WholeWords - 1) ]) ++
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
builtin_bytes_to_str(N) when N =< 32 -> builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}], {[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]), builtin_bytes_to_str_body(w, N) ++
{inline_asm, [?A(?POP)]}, [{inline_asm, [?A(?POP)]}, ?V(ret)]}),
?V(ret)]}),
string}; string};
builtin_bytes_to_str(N) when N > 32 -> builtin_bytes_to_str(N) when N > 32 ->
Work = fun(I) ->
[?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])),
{inline_asm, [?A(?POP)]}]
end,
{[{"p", pointer}], {[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++ {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++ builtin_bytes_to_str_body(p, N) ++
[?V(ret)]}), [{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string}. string}.
builtin_string_reverse() -> builtin_string_reverse() ->
@@ -575,3 +605,80 @@ builtin_string_reverse_() ->
builtin_addr_to_str() -> builtin_addr_to_str() ->
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}. {[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
%% At most one word
%% | ..... | ========= | ........ |
%% Offs ^ ^- Len -^ TotalLen ^
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
%% Bytes are packed into a single word
Masked =
case Offs of
0 -> Bytes;
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
end,
Unpadded =
case 32 - (Offs + Len) of
0 -> Masked;
N -> ?BSR(Masked, N)
end,
case Len of
32 -> Unpadded;
_ -> ?BSL(Unpadded, 32 - Len)
end;
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
%% Might read one word more than necessary.
Word = op('!', Offs, Bytes),
case Len == 32 of
true -> Word;
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
end.
builtin_bytes_concat(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Words = fun(N) -> (N + 31) div 32 end,
WordsRes = Words(A + B),
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
(I) when 32 * I < A ->
Len = A rem 32,
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
?ADD(Hi, ?BSR(Lo, Len));
(I) ->
Offs = 32 * I - A,
Len = min(32, B - Offs),
bytes_slice(Offs, Len, B, ?V(b))
end,
Body =
case {A, B} of
{0, _} -> ?V(b);
{_, 0} -> ?V(a);
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
end,
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
builtin_bytes_split(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Word = fun(I, Max) ->
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
end,
Body =
case {A, B} of
{0, _} -> [?I(0), ?V(c)];
{_, 0} -> [?V(c), ?I(0)];
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
end,
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
bytes_to_raw_string(N, Term) when N =< 32 ->
{tuple, [?I(N), Term]};
bytes_to_raw_string(N, Term) when N > 32 ->
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
end,
Words = (N + 31) div 32,
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
+120
View File
@@ -0,0 +1,120 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Formatting of code generation errors.
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(aeso_code_errors).
-export([format/1, pos/1]).
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
[C]),
mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
mk_err(pos(Con), Msg, Cxt);
format({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
mk_err(pos(Id), Msg);
format({parameterized_state, Decl}) ->
Msg = "The state type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({parameterized_event, Decl}) ->
Msg = "The event type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)";
polymorphic -> "polymorphic (contains type variables)" end,
ThingS = case Thing of
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
end,
Bad = case Thing of
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
[ThingS, Name, Bad]),
case Why of
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
higher_order -> mk_err(pos(Ann), Msg)
end;
format({cant_compare_type_aevm, Ann, Op, Type}) ->
StringAndTuple = [ "- type string\n"
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
Msg = io_lib:format("Cannot compare values of type\n"
"~s\n"
"The AEVM only supports '~s' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"~s",
[pp_type(2, Type), Op, StringAndTuple]),
Cxt = "Use FATE if you need to compare arbitrary types.\n",
mk_err(pos(Ann), Msg, Cxt);
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
"It must be a string or a pubkey type (address, oracle, etc).\n",
[pp_type(2, T)]),
mk_err(pos(Ann), Msg);
format({unapplied_contract_call, Contract}) ->
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
"~s\n", [pp_expr(2, Contract)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Contract), Msg, Cxt);
format({unapplied_builtin, Id}) ->
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Id), Msg, Cxt);
format({invalid_map_key_type, Why, Ann, Type}) ->
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
Cxt = case Why of
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
function -> "Map keys cannot be higher-order.\n"
end,
mk_err(pos(Ann), Msg, Cxt);
format({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
mk_err(pos(Ann), Msg, Cxt);
format(Err) ->
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
aeso_errors:pos(File, Line, Col).
pp_typed(E, T) ->
prettypr:format(prettypr:nest(2,
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
[aeso_pretty:expr(E), prettypr:text(" : "),
aeso_pretty:type(T)]))).
pp_expr(E) ->
pp_expr(0, E).
pp_expr(N, E) ->
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
pp_type(N, T) ->
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
mk_err(Pos, Msg) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
mk_err(Pos, Msg, Cxt) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).
+175 -124
View File
@@ -15,6 +15,7 @@
, create_calldata/3 %% deprecated , create_calldata/3 %% deprecated
, create_calldata/4 , create_calldata/4
, version/0 , version/0
, numeric_version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/5 , to_sophia_value/5
@@ -22,6 +23,7 @@
, decode_calldata/4 , decode_calldata/4
, parse/2 , parse/2
, add_include_path/2 , add_include_path/2
, validate_byte_code/3
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -65,18 +67,29 @@ version() ->
{ok, list_to_binary(VsnString)} {ok, list_to_binary(VsnString)}
end. end.
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}.
numeric_version() ->
case version() of
{ok, Bin} ->
[NoSuf | _] = binary:split(Bin, <<"-">>),
Numbers = binary:split(NoSuf, <<".">>, [global]),
{ok, [binary_to_integer(Num) || Num <- Numbers]};
{error, _} = Err ->
Err
end.
-spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(File, Options0) -> file(File, Options0) ->
Options = add_include_path(File, Options0), Options = add_include_path(File, Options0),
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} -> {error, Error} ->
ErrorString = [File,": ",file:format_error(Error)], Msg = lists:flatten([File,": ",file:format_error(Error)]),
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)} {error, [aeso_errors:new(file_error, Msg)]}
end. end.
add_include_path(File, Options) -> add_include_path(File, Options) ->
@@ -88,7 +101,7 @@ add_include_path(File, Options) ->
[{include, {file_system, [Cwd, Dir]}} | Options] [{include, {file_system, [Cwd, Dir]}} | Options]
end. end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
from_string(Contract, Options) -> from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
@@ -98,22 +111,14 @@ from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) from_string1(Backend, ContractString, Options)
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, 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. end.
from_string1(aevm, ContractString, Options) -> from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_code(ContractString, Options), #{icode := Icode} = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode), TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options), Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options), pp_assembler(aevm, Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options), ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options), pp_bytecode(ByteCode, Options),
@@ -128,6 +133,7 @@ from_string1(aevm, ContractString, Options) ->
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options), #{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options), FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, {ok, #{byte_code => ByteCode,
@@ -144,7 +150,7 @@ string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options), pp_sophia_code(Ast, Options),
pp_ast(Ast, Options), pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
@@ -160,10 +166,6 @@ string_to_code(ContractString, Options) ->
type_env => TypeEnv} type_env => TypeEnv}
end. end.
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"). -define(DECODE_NAME, "__decode").
@@ -175,7 +177,7 @@ join_errors(Prefix, Errors, Pfun) ->
%% a special return type (typerep, T) %% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} -spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]} | {ok, string(), [term()]}
| {error, term()} | {error, [aeso_errors:error()]}
when Type :: term(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of case check_call1(Source, FunName, Args, Options) of
@@ -230,16 +232,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, 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_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> arguments_of_body(CallName, _FunName, Fcode) ->
@@ -287,24 +280,31 @@ last_contract_indent(Decls) ->
end. end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) -> -spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(ContractString, Fun, ResType, Data) -> to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]). to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) -> -spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(_, _, error, Err, _Options) -> to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}}; {ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) -> to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
case aeb_heap:from_binary(string, Data) of case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}; {ok, Err} ->
{error, _} = Err -> Err {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
Err = aeb_fate_encoding:deserialize(Data), try aeb_fate_encoding:deserialize(Data) of
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}} Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end
end; end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) -> to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
@@ -324,49 +324,41 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)} {ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), 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", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))], [Data, VmType, Type0Str]),
fun (E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
try try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error", Type1 = prettypr:format(aeso_pretty:type(Type)),
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))], [aeb_fate_encoding:deserialize(Data), Type1]),
fun (E) -> E end)}; {error, [aeso_errors:new(data_error, Msg)]};
_:R -> _:_ ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))} Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, 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. end.
-spec create_calldata(string(), string(), [string()]) -> -spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()} {ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}. | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) -> create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]). create_calldata(Code, Fun, Args, [{backend, aevm}]).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) -> -spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()} {ok, binary()} | {error, [aeso_errors:error()]}.
| {error, term()}.
create_calldata(Code, Fun, Args, Options0) -> create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0], Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
@@ -386,7 +378,7 @@ create_calldata(Code, Fun, Args, Options0) ->
-spec decode_calldata(string(), string(), binary()) -> -spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]} {ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}. | {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata) -> decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]). decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
@@ -413,15 +405,14 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
%% Values are Sophia expressions in AST format %% Values are Sophia expressions in AST format
{ok, ArgTypes, Values} {ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", [VmValue, VmType, Type0Str]),
[VmValue, VmType, Type0Str]))], {error, [aeso_errors:new(data_error, Msg)]}
fun (E) -> E end)}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of case aeb_fate_abi:decode_calldata(FunName, Calldata) of
@@ -433,35 +424,30 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
{ok, ArgTypes, AstArgs} {ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", [FateArgs, Type0Str]),
[FateArgs, Type0Str]))], {error, [aeso_errors:new(data_error, Msg)]}
fun (E) -> E end)}
end; end;
{error, _} -> {error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"], Msg = io_lib:format("Failed to decode calldata binary\n", []),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, 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. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args; [Args] -> Args;
[] -> error({missing_call_function, Funs}) [] -> error_missing_call_function()
end. end.
-dialyzer({nowarn_function, error_missing_call_function/0}).
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
@@ -469,13 +455,14 @@ get_call_type([{contract, _, _, Defs}]) ->
{app, _, {app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> error_missing_call_function()
end; end;
get_call_type([_ | Contracts]) -> get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract %% The __call should be in the final contract
get_call_type(Contracts). get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) -> -dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; 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}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end, (_) -> [] end,
@@ -484,7 +471,10 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
[] -> [] ->
case FunName of case FunName of
"init" -> {ok, [], {tuple_t, [], []}}; "init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function} _ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end end
end; end;
get_decode_type(FunName, [_ | Contracts]) -> get_decode_type(FunName, [_ | Contracts]) ->
@@ -518,6 +508,14 @@ icode_to_term(T = {map, KT, VT}, M) ->
#{}; #{};
_ -> throw({todo, M}) _ -> throw({todo, M})
end; end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) -> icode_to_term(typerep, _) ->
throw({todo, typerep}); throw({todo, typerep});
icode_to_term(T, V) -> icode_to_term(T, V) ->
@@ -556,9 +554,11 @@ pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1). pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1). pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1). pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
pp_assembler(C, Opts)-> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1).
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1). pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp(Code, Options, Option, PPFun) -> pp(Code, Options, Option, PPFun) ->
case proplists:lookup(Option, Options) of case proplists:lookup(Option, Options) of
{Option, true} -> {Option, true} ->
@@ -567,6 +567,87 @@ pp(Code, Options, Option, PPFun) ->
ok ok
end. end.
%% -- Byte code validation ---------------------------------------------------
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
case proplists:get_value(backend, Options, aevm) of
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
fate ->
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(fate, Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end
end.
compare_fate_code(FCode1, FCode2) ->
Funs1 = aeb_fate_code:functions(FCode1),
Funs2 = aeb_fate_code:functions(FCode2),
Syms1 = aeb_fate_code:symbols(FCode1),
Syms2 = aeb_fate_code:symbols(FCode2),
FunHashes1 = maps:keys(Funs1),
FunHashes2 = maps:keys(Funs2),
case FunHashes1 == FunHashes2 of
false ->
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
{error, Msg};
true ->
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
maps:to_list(Funs2)) ]) of
[] -> ok;
Errs -> {error, Errs}
end
end.
compare_fate_fun(_Name, Fun, Fun) -> [];
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
[io_lib:format("- The attributes of the function ~s differ:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
[io_lib:format("- The type of the function ~s differs:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
pp_fate_sig({[Arg], Res}) ->
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
pp_fate_sig({Args, Res}) ->
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
pp_fate_type(T) -> io_lib:format("~w", [T]).
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
sophia_type_to_typerep(String) -> sophia_type_to_typerep(String) ->
@@ -582,37 +663,7 @@ parse(Text, Options) ->
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast(). -spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Included, Options) -> parse(Text, Included, Options) ->
%% Try and return something sensible here! aeso_parser:string(Text, Included, Options).
case aeso_parser:string(Text, Included, 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.
-spec parse_error(aeso_parse_lib:pos(), string()) -> none().
parse_error(Pos, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
file:read_file(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]).
-42
View File
@@ -1,42 +0,0 @@
-module(aeso_constants).
-export([string/1, get_type/1]).
string(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [{letval, _, _, _, E}]} -> {ok, E};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
get_type(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [Ast]} ->
AstT = aeso_ast_infer_types:infer_constant(Ast),
T = ast_to_type(AstT),
{ok, T};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
ast_to_type({id, _, T}) ->
T;
ast_to_type({tuple_t, _, []}) -> "()";
ast_to_type({tuple_t, _, Ts}) ->
"(" ++ list_ast_to_type(Ts) ++ ")";
ast_to_type({app_t,_, {id, _, "list"}, [T]}) ->
lists:flatten("list(" ++ ast_to_type(T) ++ ")");
ast_to_type({app_t,_, {id, _, "option"}, [T]}) ->
lists:flatten("option(" ++ ast_to_type(T) ++ ")").
list_ast_to_type([T]) ->
ast_to_type(T);
list_ast_to_type([T|Ts]) ->
ast_to_type(T)
++ ", "
++ list_ast_to_type(Ts).
+112
View File
@@ -0,0 +1,112 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc ADT for structured error messages + formatting.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_errors).
-type src_file() :: no_file | iolist().
-record(pos, { file = no_file :: src_file()
, line = 0 :: non_neg_integer()
, col = 0 :: non_neg_integer()
}).
-type pos() :: #pos{}.
-type error_type() :: type_error | parse_error | code_error
| file_error | data_error | internal_error.
-record(err, { pos = #pos{} :: pos()
, type :: error_type()
, message :: iolist()
, context = none :: none | iolist()
}).
-opaque error() :: #err{}.
-export_type([error/0, pos/0]).
-export([ err_msg/1
, msg/1
, new/2
, new/3
, new/4
, pos/2
, pos/3
, pp/1
, to_json/1
, throw/1
, type/1
]).
new(Type, Msg) ->
new(Type, pos(0, 0), Msg).
new(Type, Pos, Msg) ->
#err{ type = Type, pos = Pos, message = Msg }.
new(Type, Pos, Msg, Ctxt) ->
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
pos(Line, Col) ->
#pos{ line = Line, col = Col }.
pos(File, Line, Col) ->
#pos{ file = File, line = Line, col = Col }.
-spec throw(_) -> ok | no_return().
throw([]) -> ok;
throw(Errs) when is_list(Errs) ->
SortedErrs = lists:sort(fun(E1, E2) -> E1#err.pos =< E2#err.pos end, Errs),
erlang:throw({error, SortedErrs});
throw(#err{} = Err) ->
erlang:throw({error, [Err]}).
msg(#err{ message = Msg, context = none }) -> Msg;
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
err_msg(#err{ pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])).
str_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format("~p:~p:", [L, C]);
str_pos(#pos{file = F, line = L, col = C}) ->
io_lib:format("~s:~p:~p:", [F, L, C]).
type(#err{ type = Type }) -> Type.
pp(#err{ type = Kind, pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
pp_kind(type_error) -> "Type error";
pp_kind(parse_error) -> "Parse error";
pp_kind(code_error) -> "Code generation error";
pp_kind(file_error) -> "File error";
pp_kind(data_error) -> "Data error";
pp_kind(internal_error) -> "Internal error".
pp_pos(#pos{file = no_file, line = 0, col = 0}) ->
"";
pp_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format(" at line ~p, col ~p", [L, C]);
pp_pos(#pos{file = F, line = L, col = C}) ->
io_lib:format(" in '~s' at line ~p, col ~p", [F, L, C]).
to_json(#err{pos = Pos, type = Type, message = Msg, context = Cxt}) ->
Json = #{ pos => pos_to_json(Pos),
type => atom_to_binary(Type, utf8),
message => iolist_to_binary(Msg) },
case Cxt of
none -> Json;
_ -> Json#{ context => iolist_to_binary(Cxt) }
end.
pos_to_json(#pos{ file = File, line = Line, col = Col }) ->
Json = #{ line => Line, col => Col },
case File of
no_file -> Json;
_ -> Json#{ file => iolist_to_binary(File) }
end.
+311 -254
View File
@@ -11,19 +11,23 @@
-export([compile/2, term_to_fate/1]). -export([compile/2, term_to_fate/1]).
-ifdef(TEST).
-export([optimize_fun/4, to_basic_blocks/1]).
-endif.
%% -- Preamble --------------------------------------------------------------- %% -- Preamble ---------------------------------------------------------------
-type scode() :: [sinstr()]. -type scode() :: [sinstr()].
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all -type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
| switch_body | switch_body
| tuple(). %% FATE instruction | loop
| tuple() | atom(). %% FATE instruction
-type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg(). -type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg().
%% Annotated scode %% Annotated scode
-type scode_a() :: [sinstr_a()]. -type scode_a() :: [sinstr_a()].
-type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all -type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all
| switch_body
| {i, ann(), tuple()}. %% FATE instruction | {i, ann(), tuple()}. %% FATE instruction
-type ann() :: #{ live_in := vars(), live_out := vars() }. -type ann() :: #{ live_in := vars(), live_out := vars() }.
@@ -38,82 +42,9 @@
-define(i(X), {immediate, X}). -define(i(X), {immediate, X}).
-define(a, {stack, 0}). -define(a, {stack, 0}).
-define(s, {var, -1}). %% TODO: until we have state support in FATE -define(s, {store, 1}).
-define(void, {var, 9999}). -define(void, {var, 9999}).
-define(IsState(X), (is_tuple(X) andalso tuple_size(X) =:= 2 andalso element(1, X) =:= var andalso element(2, X) < 0)).
-define(IsOp(Op), (
Op =:= 'STORE' orelse
Op =:= 'ADD' orelse
Op =:= 'SUB' orelse
Op =:= 'MUL' orelse
Op =:= 'DIV' orelse
Op =:= 'MOD' orelse
Op =:= 'POW' orelse
Op =:= 'LT' orelse
Op =:= 'GT' orelse
Op =:= 'EQ' orelse
Op =:= 'ELT' orelse
Op =:= 'EGT' orelse
Op =:= 'NEQ' orelse
Op =:= 'AND' orelse
Op =:= 'OR' orelse
Op =:= 'NOT' orelse
Op =:= 'ELEMENT' orelse
Op =:= 'MAP_EMPTY' orelse
Op =:= 'MAP_LOOKUP' orelse
Op =:= 'MAP_LOOKUPD' orelse
Op =:= 'MAP_UPDATE' orelse
Op =:= 'MAP_DELETE' orelse
Op =:= 'MAP_MEMBER' orelse
Op =:= 'MAP_FROM_LIST' orelse
Op =:= 'MAP_TO_LIST' orelse
Op =:= 'MAP_SIZE' orelse
Op =:= 'NIL' orelse
Op =:= 'IS_NIL' orelse
Op =:= 'CONS' orelse
Op =:= 'HD' orelse
Op =:= 'TL' orelse
Op =:= 'LENGTH' orelse
Op =:= 'APPEND' orelse
Op =:= 'STR_JOIN' orelse
Op =:= 'INT_TO_STR' orelse
Op =:= 'ADDR_TO_STR' orelse
Op =:= 'STR_REVERSE' orelse
Op =:= 'STR_LENGTH' orelse
Op =:= 'INT_TO_ADDR' orelse
Op =:= 'VARIANT_TEST' orelse
Op =:= 'VARIANT_ELEMENT' orelse
Op =:= 'BITS_NONE' orelse
Op =:= 'BITS_ALL' orelse
Op =:= 'BITS_ALL_N' orelse
Op =:= 'BITS_SET' orelse
Op =:= 'BITS_CLEAR' orelse
Op =:= 'BITS_TEST' orelse
Op =:= 'BITS_SUM' orelse
Op =:= 'BITS_OR' orelse
Op =:= 'BITS_AND' orelse
Op =:= 'BITS_DIFF' orelse
Op =:= 'SHA3' orelse
Op =:= 'SHA256' orelse
Op =:= 'BLAKE2B' orelse
Op =:= 'VERIFY_SIG' orelse
Op =:= 'VERIFY_SIG_SECP256K1' orelse
Op =:= 'ECVERIFY_SECP256K1' orelse
Op =:= 'ECRECOVER_SECP256K1' orelse
Op =:= 'CONTRACT_TO_ADDRESS' orelse
Op =:= 'AUTH_TX_HASH' orelse
Op =:= 'BYTES_TO_INT' orelse
Op =:= 'BYTES_TO_STR' orelse
Op =:= 'ORACLE_CHECK' orelse
Op =:= 'ORACLE_CHECK_QUERY' orelse
Op =:= 'IS_ORACLE' orelse
Op =:= 'IS_CONTRACT' orelse
Op =:= 'IS_PAYABLE' orelse
Op =:= 'CREATOR' orelse
false)).
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }). -record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
%% -- Debugging -------------------------------------------------------------- %% -- Debugging --------------------------------------------------------------
@@ -122,12 +53,19 @@ is_debug(Tag, Options) ->
Tags = proplists:get_value(debug, Options, []), Tags = proplists:get_value(debug, Options, []),
Tags == all orelse lists:member(Tag, Tags). Tags == all orelse lists:member(Tag, Tags).
debug(Tag, Options, Fmt, Args) -> -define(debug(Tag, Options, Fmt, Args),
debug(Tag, Options, fun() -> io:format(Fmt, Args) end)).
debug(Tag, Options, Fun) ->
case is_debug(Tag, Options) of case is_debug(Tag, Options) of
true -> io:format(Fmt, Args); true -> Fun();
false -> ok false -> ok
end. end.
-dialyzer({nowarn_function, [code_error/1]}).
code_error(Err) ->
aeso_errors:throw(aeso_code_errors:format(Err)).
%% -- Main ------------------------------------------------------------------- %% -- Main -------------------------------------------------------------------
%% @doc Main entry point. %% @doc Main entry point.
@@ -137,13 +75,12 @@ compile(FCode, Options) ->
SFuns = functions_to_scode(ContractName, Functions, Options), SFuns = functions_to_scode(ContractName, Functions, Options),
SFuns1 = optimize_scode(SFuns, Options), SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1), FateCode = to_basic_blocks(SFuns1),
debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
FateCode. FateCode.
make_function_id(X) -> make_function_id(X) ->
aeb_fate_code:symbol_identifier(make_function_name(X)). aeb_fate_code:symbol_identifier(make_function_name(X)).
make_function_name(init) -> <<"INIT">>;
make_function_name(event) -> <<"Chain.event">>; make_function_name(event) -> <<"Chain.event">>;
make_function_name({entrypoint, Name}) -> Name; make_function_name({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")). make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
@@ -221,8 +158,6 @@ bind_local(Name, Env) ->
notail(Env) -> Env#env{ tailpos = false }. notail(Env) -> Env#env{ tailpos = false }.
code_error(Err) -> error(Err).
lookup_var(#env{vars = Vars}, X) -> lookup_var(#env{vars = Vars}, X) ->
case lists:keyfind(X, 1, Vars) of case lists:keyfind(X, 1, Vars) of
{_, Var} -> Var; {_, Var} -> Var;
@@ -259,6 +194,18 @@ term_to_fate({tuple, As}) ->
term_to_fate({con, Ar, I, As}) -> term_to_fate({con, Ar, I, As}) ->
FateAs = [ term_to_fate(A) || A <- As ], FateAs = [ term_to_fate(A) || A <- As ],
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
term_to_fate({builtin, bits_all, []}) ->
aeb_fate_data:make_bits(-1);
term_to_fate({builtin, bits_none, []}) ->
aeb_fate_data:make_bits(0);
term_to_fate({op, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
{bits, N bor (1 bsl J)};
term_to_fate({op, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
{bits, N band bnot (1 bsl J)};
term_to_fate({builtin, map_empty, []}) -> term_to_fate({builtin, map_empty, []}) ->
aeb_fate_data:make_map(#{}); aeb_fate_data:make_map(#{});
term_to_fate({'let', _, {builtin, map_empty, []}, Set}) -> term_to_fate({'let', _, {builtin, map_empty, []}, Set}) ->
@@ -476,7 +423,7 @@ call_to_scode(Env, CallCode, Args) ->
builtin_to_scode(_Env, get_state, []) -> builtin_to_scode(_Env, get_state, []) ->
[push(?s)]; [push(?s)];
builtin_to_scode(Env, set_state, [_] = Args) -> builtin_to_scode(Env, set_state, [_] = Args) ->
call_to_scode(Env, [aeb_fate_ops:store(?s, ?a), call_to_scode(Env, [{'STORE', ?s, ?a},
tuple(0)], Args); tuple(0)], Args);
builtin_to_scode(Env, chain_event, Args) -> builtin_to_scode(Env, chain_event, Args) ->
call_to_scode(Env, [erlang:apply(aeb_fate_ops, log, lists:duplicate(length(Args), ?a)), call_to_scode(Env, [erlang:apply(aeb_fate_ops, log, lists:duplicate(length(Args), ?a)),
@@ -491,6 +438,10 @@ builtin_to_scode(Env, bytes_to_int, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_to_int(?a, ?a), Args); call_to_scode(Env, aeb_fate_ops:bytes_to_int(?a, ?a), Args);
builtin_to_scode(Env, bytes_to_str, [_] = Args) -> builtin_to_scode(Env, bytes_to_str, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_to_str(?a, ?a), Args); call_to_scode(Env, aeb_fate_ops:bytes_to_str(?a, ?a), Args);
builtin_to_scode(Env, bytes_concat, [_, _] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_concat(?a, ?a, ?a), Args);
builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
builtin_to_scode(Env, abort, [_] = Args) -> builtin_to_scode(Env, abort, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:abort(?a), Args); call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
builtin_to_scode(Env, chain_spend, [_, _] = Args) -> builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
@@ -606,6 +557,7 @@ op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a);
op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a);
op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a);
op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a);
op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a);
op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a); op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a);
op_to_scode(crypto_verify_sig_secp256k1) -> aeb_fate_ops:verify_sig_secp256k1(?a, ?a, ?a, ?a); op_to_scode(crypto_verify_sig_secp256k1) -> aeb_fate_ops:verify_sig_secp256k1(?a, ?a, ?a, ?a);
op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a); op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a);
@@ -619,7 +571,7 @@ op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a).
%% PUSH and STORE ?a are the same, so we use STORE to make optimizations %% PUSH and STORE ?a are the same, so we use STORE to make optimizations
%% easier, and specialize to PUSH (which is cheaper) at the end. %% easier, and specialize to PUSH (which is cheaper) at the end.
push(A) -> aeb_fate_ops:store(?a, A). push(A) -> {'STORE', ?a, A}.
tuple(0) -> push(?i({tuple, {}})); tuple(0) -> push(?i({tuple, {}}));
tuple(N) -> aeb_fate_ops:tuple(?a, N). tuple(N) -> aeb_fate_ops:tuple(?a, N).
@@ -642,23 +594,23 @@ flatten_s(I) -> I.
optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) -> optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
Code0 = flatten(Code), Code0 = flatten(Code),
debug(opt, Options, "Optimizing ~s\n", [Name]), ?debug(opt, Options, "Optimizing ~s\n", [Name]),
Code1 = simpl_loop(0, Code0, Options), Code1 = simpl_loop(0, Code0, Options),
Code2 = desugar(Code1), Code2 = desugar(Code1),
{Attrs, Sig, Code2}. {Attrs, Sig, Code2}.
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS -> simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]), ?debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
Code; Code;
simpl_loop(N, Code, Options) -> simpl_loop(N, Code, Options) ->
ACode = annotate_code(Code), ACode = annotate_code(Code),
[ debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ], [ ?debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ],
Code1 = simplify(ACode, Options), Code1 = simplify(ACode, Options),
[ debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ], [ ?debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ],
Code2 = unannotate(Code1), Code2 = unannotate(Code1),
case Code == Code2 of case Code == Code2 of
true -> true ->
debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n", ?debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n",
[N, if N /= 1 -> "s"; true -> "" end]), [N, if N /= 1 -> "s"; true -> "" end]),
Code2; Code2;
false -> simpl_loop(N + 1, Code2, Options) false -> simpl_loop(N + 1, Code2, Options)
@@ -678,80 +630,77 @@ pp_ann(Ind, [{switch, Arg, Type, Alts, Def} | Code]) ->
|| {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing], || {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing],
[[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing], [[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing],
pp_ann(Ind, Code)]; pp_ann(Ind, Code)];
pp_ann(Ind, [switch_body | Code]) ->
[Ind, "SWITCH-BODY\n", pp_ann(Ind, Code)];
pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) -> pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
Fmt = fun([]) -> "()"; Fmt = fun([]) -> "()";
(Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ") (Xs) -> string:join([lists:flatten(pp_arg(X)) || X <- Xs], " ")
end, end,
Op = [Ind, pp_op(I)], Op = [Ind, pp_op(desugar_args(I))],
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []], Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
[io_lib:format("~-40s~s\n", [Op, Ann]), [io_lib:format("~-40s~s\n", [Op, Ann]),
pp_ann(Ind, Code)]; pp_ann(Ind, Code)];
pp_ann(_, []) -> []. pp_ann(_, []) -> [].
pp_op(switch_body) -> "SWITCH-BODY";
pp_op(loop) -> "LOOP"; pp_op(loop) -> "LOOP";
pp_op(I) -> pp_op(I) ->
aeb_fate_pp:format_op(I, #{}). aeb_fate_pp:format_op(I, #{}).
pp_arg(?i(I)) -> io_lib:format("~w", [I]); pp_arg(?i(I)) -> io_lib:format("~w", [I]);
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]); pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
pp_arg({var, N}) when N < 0 -> io_lib:format("store~p", [-N]); pp_arg({store, N}) -> io_lib:format("store~p", [N]);
pp_arg({var, N}) -> io_lib:format("var~p", [N]); pp_arg({var, N}) -> io_lib:format("var~p", [N]);
pp_arg(?a) -> "a". pp_arg(?a) -> "a".
%% -- Analysis -- %% -- Analysis --
annotate_code(Code) -> annotate_code(Code) ->
{WCode, _} = ann_writes(Code, ordsets:new(), []), annotate_code(5, [], Code).
{RCode, _} = ann_reads(WCode, ordsets:new(), []),
RCode.
%% Reverses the code annotate_code(Fuel, LiveTop, Code) ->
ann_writes(missing, Writes, []) -> {missing, Writes}; {Code1, LiveIn} = ann_live(LiveTop, Code, []),
ann_writes([switch_body | Code], Writes, Acc) -> case LiveIn == LiveTop of
ann_writes(Code, Writes, [switch_body | Acc]); true -> Code1;
ann_writes([{switch, Arg, Type, Alts, Def} | Code], Writes, Acc) -> false when Fuel =< 0 ->
{Alts1, WritesAlts} = lists:unzip([ ann_writes(Alt, Writes, []) || Alt <- Alts ]), code_error(liveness_analysis_out_of_fuel);
{Def1, WritesDef} = ann_writes(Def, Writes, []), false -> annotate_code(Fuel - 1, LiveIn, Code)
Writes1 = ordsets:union(Writes, ordsets:intersection([WritesDef | WritesAlts])), end.
ann_writes(Code, Writes1, [{switch, Arg, Type, Alts1, Def1} | Acc]);
ann_writes([I | Code], Writes, Acc) ->
Ws = [ W || W <- var_writes(I), not ?IsState(W) ],
Writes1 = ordsets:union(Writes, Ws),
Ann = #{ writes_in => Writes, writes_out => Writes1 },
ann_writes(Code, Writes1, [{i, Ann, I} | Acc]);
ann_writes([], Writes, Acc) ->
{Acc, Writes}.
%% Takes reversed code and unreverses it. ann_live(_LiveTop, missing, _LiveOut) -> {missing, []};
ann_reads(missing, Reads, []) -> {missing, Reads}; ann_live(_LiveTop, [], LiveOut) -> {[], LiveOut};
ann_reads([switch_body | Code], Reads, Acc) -> ann_live(LiveTop, [I | Is], LiveOut) ->
ann_reads(Code, Reads, [switch_body | Acc]); {Is1, LiveMid} = ann_live(LiveTop, Is, LiveOut),
ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) -> {I1, LiveIn} = ann_live1(LiveTop, I, LiveMid),
{Alts1, ReadsAlts} = lists:unzip([ ann_reads(Alt, Reads, []) || Alt <- Alts ]), {[I1 | Is1], LiveIn}.
{Def1, ReadsDef} = ann_reads(Def, Reads, []),
Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]),
ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]);
ann_reads([{i, Ann, I} | Code], Reads, Acc) ->
#{ writes_in := WritesIn, writes_out := WritesOut } = Ann,
#{ read := Rs, write := W, pure := Pure } = attributes(I),
Reads1 =
case {W, Pure andalso not ordsets:is_element(W, Reads)} of
%% This is a little bit dangerous: if writing to a dead variable, we ignore
%% the reads. Relies on dead writes to be removed by the
%% optimisations below (r_write_to_dead_var).
{{var, _}, true} -> Reads;
_ -> ordsets:union(Reads, Rs)
end,
LiveIn = ordsets:intersection(Reads1, WritesIn),
LiveOut = ordsets:intersection(Reads, WritesOut),
Ann1 = #{ live_in => LiveIn, live_out => LiveOut },
ann_reads(Code, Reads1, [{i, Ann1, I} | Acc]);
ann_reads([], Reads, Acc) -> {Acc, Reads}.
%% Instruction attributes: reads, writes and purity (pure means no side-effects ann_live1(_LiveTop, switch_body, LiveOut) ->
%% aside from the reads and writes). Ann = #{ live_in => LiveOut, live_out => LiveOut },
{{i, Ann, switch_body}, LiveOut};
ann_live1(LiveTop, loop, _LiveOut) ->
Ann = #{ live_in => LiveTop, live_out => [] },
{{i, Ann, loop}, LiveTop};
ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) ->
Read = [Arg || is_reg(Arg)],
{Alts1, LiveAlts} = lists:unzip([ ann_live(LiveTop, Alt, LiveOut) || Alt <- Alts ]),
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
ann_live1(_LiveTop, I, LiveOut) ->
#{ read := Reads0, write := W } = attributes(I),
Reads = lists:filter(fun is_reg/1, Reads0),
%% If we write it here it's not live in (unless we also read it)
LiveIn = ordsets:union(LiveOut -- [W], Reads),
Ann = #{ live_in => LiveIn, live_out => LiveOut },
{{i, Ann, I}, LiveIn}.
is_reg(?a) -> false;
is_reg(none) -> false;
is_reg(pc) -> false;
is_reg({immediate, _}) -> false;
is_reg({arg, _}) -> true;
is_reg({store, _}) -> true;
is_reg({var, _}) -> true.
%% Instruction attributes: reads, writes and purity (pure means no writing to the chain).
attributes(I) -> attributes(I) ->
Set = fun(L) when is_list(L) -> ordsets:from_list(L); Set = fun(L) when is_list(L) -> ordsets:from_list(L);
(X) -> ordsets:from_list([X]) end, (X) -> ordsets:from_list([X]) end,
@@ -760,12 +709,13 @@ attributes(I) ->
Impure = fun(W, R) -> Attr(W, R, false) end, Impure = fun(W, R) -> Attr(W, R, false) end,
case I of case I of
loop -> Impure(pc, []); loop -> Impure(pc, []);
switch_body -> Pure(none, []);
'RETURN' -> Impure(pc, []); 'RETURN' -> Impure(pc, []);
{'RETURNR', A} -> Impure(pc, A); {'RETURNR', A} -> Impure(pc, A);
{'CALL', _} -> Impure(?a, []); {'CALL', A} -> Impure(?a, [A]);
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]); {'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
{'CALL_T', _} -> Impure(pc, []); {'CALL_T', A} -> Impure(pc, [A]);
{'CALL_VALUE', A} -> Pure(A, []); {'CALL_VALUE', A} -> Pure(A, []);
{'JUMP', _} -> Impure(pc, []); {'JUMP', _} -> Impure(pc, []);
{'JUMPIF', A, _} -> Impure(pc, A); {'JUMPIF', A, _} -> Impure(pc, A);
@@ -844,29 +794,32 @@ attributes(I) ->
{'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]);
{'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]);
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
{'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]);
{'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX_HASH', A} -> Pure(A, []);
{'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_INT', A, B} -> Pure(A, [B]);
{'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_TO_STR', A, B} -> Pure(A, [B]);
{'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]); {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]);
{'IS_ORACLE', A, B} -> Impure(A, [B]); {'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]);
{'IS_CONTRACT', A, B} -> Impure(A, [B]); {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
{'IS_PAYABLE', A, B} -> Impure(A, [B]); {'IS_ORACLE', A, B} -> Pure(A, [B]);
{'IS_CONTRACT', A, B} -> Pure(A, [B]);
{'IS_PAYABLE', A, B} -> Pure(A, [B]);
{'CREATOR', A} -> Pure(A, []); {'CREATOR', A} -> Pure(A, []);
{'ADDRESS', A} -> Pure(A, []); {'ADDRESS', A} -> Pure(A, []);
{'BALANCE', A} -> Impure(A, []); {'BALANCE', A} -> Pure(A, []);
{'BALANCE_OTHER', A, B} -> Impure(A, [B]); {'BALANCE_OTHER', A, B} -> Pure(A, [B]);
{'ORIGIN', A} -> Pure(A, []); {'ORIGIN', A} -> Pure(A, []);
{'CALLER', A} -> Pure(A, []); {'CALLER', A} -> Pure(A, []);
{'GASPRICE', A} -> Pure(A, []); {'GASPRICE', A} -> Pure(A, []);
{'BLOCKHASH', A, B} -> Impure(A, [B]); {'BLOCKHASH', A, B} -> Pure(A, [B]);
{'BENEFICIARY', A} -> Pure(A, []); {'BENEFICIARY', A} -> Pure(A, []);
{'TIMESTAMP', A} -> Pure(A, []); {'TIMESTAMP', A} -> Pure(A, []);
{'GENERATION', A} -> Pure(A, []); {'GENERATION', A} -> Pure(A, []);
{'MICROBLOCK', A} -> Pure(A, []); {'MICROBLOCK', A} -> Pure(A, []);
{'DIFFICULTY', A} -> Pure(A, []); {'DIFFICULTY', A} -> Pure(A, []);
{'GASLIMIT', A} -> Pure(A, []); {'GASLIMIT', A} -> Pure(A, []);
{'GAS', A} -> Impure(?a, A); {'GAS', A} -> Pure(A, []);
{'LOG0', A} -> Impure(none, [A]); {'LOG0', A} -> Impure(none, [A]);
{'LOG1', A, B} -> Impure(none, [A, B]); {'LOG1', A, B} -> Impure(none, [A, B]);
{'LOG2', A, B, C} -> Impure(none, [A, B, C]); {'LOG2', A, B, C} -> Impure(none, [A, B, C]);
@@ -878,10 +831,10 @@ attributes(I) ->
{'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]); {'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]);
{'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]); {'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
{'ORACLE_EXTEND', A, B, C} -> Impure(none, [A, B, C]); {'ORACLE_EXTEND', A, B, C} -> Impure(none, [A, B, C]);
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Impure(A, [B, C, D, E]); {'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Impure(A, [B, C, D, E]); {'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]);
{'ORACLE_QUERY_FEE', A, B} -> Impure(A, [B]); {'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]);
{'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]); {'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]);
{'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]); {'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]);
{'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]); {'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
'AENS_UPDATE' -> Impure(none, []);%% TODO 'AENS_UPDATE' -> Impure(none, []);%% TODO
@@ -896,15 +849,17 @@ var_writes({i, _, I}) -> var_writes(I);
var_writes(I) -> var_writes(I) ->
#{ write := W } = attributes(I), #{ write := W } = attributes(I),
case W of case W of
{var, _} -> [W]; {var, _} -> [W];
_ -> [] {arg, _} -> [W];
{store, _} -> [W];
{stack, _} -> [];
none -> [];
pc -> []
end. end.
-spec independent(sinstr_a(), sinstr_a()) -> boolean(). -spec independent(sinstr_a(), sinstr_a()) -> boolean().
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging %% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
independent(_, {switch, _, _, _, _}) -> false; independent(_, {switch, _, _, _, _}) -> false;
%% independent(switch_body, _) -> true;
independent(_, switch_body) -> true;
independent({i, _, I}, {i, _, J}) -> independent({i, _, I}, {i, _, J}) ->
#{ write := WI, read := RI, pure := PureI } = attributes(I), #{ write := WI, read := RI, pure := PureI } = attributes(I),
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J), #{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
@@ -915,6 +870,7 @@ independent({i, _, I}, {i, _, J}) ->
if WI == pc; WJ == pc -> false; %% no jumps if WI == pc; WJ == pc -> false; %% no jumps
not (PureI or PureJ) -> false; %% at least one is pure not (PureI or PureJ) -> false; %% at least one is pure
StackI and StackJ -> false; %% cannot both use the stack StackI and StackJ -> false; %% cannot both use the stack
WI == WJ -> false; %% cannot write to the same register
true -> true ->
%% and cannot write to each other's inputs %% and cannot write to each other's inputs
not lists:member(WI, RJ) andalso not lists:member(WI, RJ) andalso
@@ -925,8 +881,6 @@ merge_ann(#{ live_in := LiveIn }, #{ live_out := LiveOut }) ->
#{ live_in => LiveIn, live_out => LiveOut }. #{ live_in => LiveIn, live_out => LiveOut }.
%% Swap two instructions. Precondition: the instructions are independent/2. %% Swap two instructions. Precondition: the instructions are independent/2.
swap_instrs(I, switch_body) -> {switch_body, I};
%% swap_instrs(switch_body, I) -> {I, switch_body}; %% Commented due to Dialyzer whinging
swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := Live3 }, J}) -> swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := Live3 }, J}) ->
%% Since I and J are independent the J can't read or write anything in %% Since I and J are independent the J can't read or write anything in
%% that I writes. %% that I writes.
@@ -938,17 +892,16 @@ swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out :=
{{i, #{ live_in => Live1, live_out => Live2_ }, J}, {{i, #{ live_in => Live1, live_out => Live2_ }, J},
{i, #{ live_in => Live2_, live_out => Live3 }, I}}. {i, #{ live_in => Live2_, live_out => Live3 }, I}}.
live_in(R, _) when ?IsState(R) -> true; live_in({store, _}, _) -> true;
live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn); live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn);
live_in(R, {i, Ann, _}) -> live_in(R, Ann); live_in(R, {i, Ann, _}) -> live_in(R, Ann);
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I); live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
live_in(R, [switch_body | Code]) -> live_in(R, Code);
live_in(R, [{switch, A, _, Alts, Def} | _]) -> live_in(R, [{switch, A, _, Alts, Def} | _]) ->
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]); R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
live_in(_, missing) -> false; live_in(_, missing) -> false;
live_in(_, []) -> false. live_in(_, []) -> false.
live_out(R, _) when ?IsState(R) -> true; live_out({store, _}, _) -> true;
live_out(R, #{ live_out := LiveOut }) -> ordsets:is_element(R, LiveOut). live_out(R, #{ live_out := LiveOut }) -> ordsets:is_element(R, LiveOut).
%% -- Optimizations -- %% -- Optimizations --
@@ -970,7 +923,7 @@ simpl_top(I, Code, Options) ->
simpl_top(?SIMPL_FUEL, I, Code, Options). simpl_top(?SIMPL_FUEL, I, Code, Options).
simpl_top(0, I, Code, _Options) -> simpl_top(0, I, Code, _Options) ->
error({out_of_fuel, I, Code}); code_error({optimizer_out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) -> simpl_top(Fuel, I, Code, Options) ->
apply_rules(Fuel, rules(), I, Code, Options). apply_rules(Fuel, rules(), I, Code, Options).
@@ -982,7 +935,7 @@ apply_rules(Fuel, Rules, I, Code, Options) ->
case is_debug(opt_rules, Options) of case is_debug(opt_rules, Options) of
true -> true ->
{OldCode, NewCode} = drop_common_suffix([I | Code], New ++ Rest), {OldCode, NewCode} = drop_common_suffix([I | Code], New ++ Rest),
debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]); ?debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]);
false -> ok false -> ok
end, end,
lists:foldr(Cons, Rest, New) lists:foldr(Cons, Rest, New)
@@ -1012,6 +965,7 @@ rules() ->
?RULE(r_swap_write), ?RULE(r_swap_write),
?RULE(r_constant_propagation), ?RULE(r_constant_propagation),
?RULE(r_prune_impossible_branches), ?RULE(r_prune_impossible_branches),
?RULE(r_single_successful_branch),
?RULE(r_inline_store), ?RULE(r_inline_store),
?RULE(r_float_switch_body) ?RULE(r_float_switch_body)
]. ].
@@ -1034,8 +988,9 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
true -> false end; true -> false end;
r_push_consume(_, _) -> false. r_push_consume(_, _) -> false.
inline_push(Ann, Arg, Stack, [switch_body | Code], Acc) -> inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
inline_push(Ann, Arg, Stack, Code, [switch_body | Acc]); {AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
inline_push(Ann1, Arg, Stack, Code, [AI1 | Acc]);
inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) -> inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) ->
case op_view(I) of case op_view(I) of
{Op, R, As} -> {Op, R, As} ->
@@ -1109,8 +1064,9 @@ r_swap_write(I = {i, _, _}, [J | Code]) ->
end; end;
r_swap_write(_, _) -> false. r_swap_write(_, _) -> false.
r_swap_write(Pre, I, [switch_body | Code]) -> r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
r_swap_write([switch_body | Pre], I, Code); {J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code);
r_swap_write(Pre, I, Code0 = [J | Code]) -> r_swap_write(Pre, I, Code0 = [J | Code]) ->
case apply_rules_once(merge_rules(), I, Code0) of case apply_rules_once(merge_rules(), I, Code0) of
{_Rule, New, Rest} -> {_Rule, New, Rest} ->
@@ -1126,18 +1082,20 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
r_swap_write(_, _, _) -> false. r_swap_write(_, _, _) -> false.
%% Precompute instructions with known values %% Precompute instructions with known values
r_constant_propagation(Cons = {i, _, {'CONS', R, _, _}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(false)}}, Store = {i, Ann, {'STORE', S, ?i(false)}},
case R of Cons1 = case R of
?a -> {[Store], Code}; ?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
_ -> {[Cons, Store], Code} _ -> Cons
end; end,
r_constant_propagation(Cons = {i, _, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> {[Cons1, Store], Code};
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(true)}}, Store = {i, Ann, {'STORE', S, ?i(true)}},
case R of Nil1 = case R of
?a -> {[Store], Code}; ?a -> {i, Ann1, {'NIL', ?void}};
_ -> {[Cons, Store], Code} _ -> Nil
end; end,
{[Nil1, Store], Code};
r_constant_propagation({i, Ann, I}, Code) -> r_constant_propagation({i, Ann, I}, Code) ->
case op_view(I) of case op_view(I) of
false -> false; false -> false;
@@ -1154,20 +1112,21 @@ r_constant_propagation({i, Ann, I}, Code) ->
end; end;
r_constant_propagation(_, _) -> false. r_constant_propagation(_, _) -> false.
eval_op('ADD', [X, Y]) -> X + Y; eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
eval_op('SUB', [X, Y]) -> X - Y; eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
eval_op('MUL', [X, Y]) -> X * Y; eval_op('MUL', [X, Y]) when is_integer(X), is_integer(Y) -> X * Y;
eval_op('DIV', [X, Y]) when Y /= 0 -> X div Y; eval_op('DIV', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X div Y;
eval_op('MOD', [X, Y]) when Y /= 0 -> X rem Y; eval_op('MOD', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X rem Y;
eval_op('POW', [_, _]) -> no_eval; eval_op('POW', [_, _]) -> no_eval;
eval_op('LT', [X, Y]) -> X < Y; eval_op('LT', [X, Y]) -> X < Y;
eval_op('GT', [X, Y]) -> X > Y; eval_op('GT', [X, Y]) -> X > Y;
eval_op('EQ', [X, Y]) -> X =:= Y; eval_op('EQ', [X, Y]) -> X =:= Y;
eval_op('ELT', [X, Y]) -> X =< Y; eval_op('ELT', [X, Y]) -> X =< Y;
eval_op('EGT', [X, Y]) -> X >= Y; eval_op('EGT', [X, Y]) -> X >= Y;
eval_op('NEQ', [X, Y]) -> X =/= Y; eval_op('NEQ', [X, Y]) -> X =/= Y;
eval_op('NOT', [X]) -> not X; eval_op('NOT', [true]) -> false;
eval_op(_, _) -> no_eval. %% TODO: bits? eval_op('NOT', [false]) -> true;
eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches %% Prune impossible branches from switches
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) -> r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
@@ -1175,7 +1134,7 @@ r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
false -> false; false -> false;
Alt -> {Alt, Code} Alt -> {Alt, Code}
end; end;
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) -> r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
Alts1 = [if V -> missing; true -> False end, Alts1 = [if V -> missing; true -> False end,
if V -> True; true -> missing end], if V -> True; true -> missing end],
case Alts == Alts1 of case Alts == Alts1 of
@@ -1187,7 +1146,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
end end
end; end;
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}}, r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
[{switch, R, Type, Alts, missing} | Code]) -> [{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
case {R, lists:nth(Tag + 1, Alts)} of case {R, lists:nth(Tag + 1, Alts)} of
{_, missing} -> {_, missing} ->
Alts1 = [missing || _ <- Alts], Alts1 = [missing || _ <- Alts],
@@ -1204,7 +1163,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
end; end;
r_prune_impossible_branches(_, _) -> false. r_prune_impossible_branches(_, _) -> false.
pick_branch(boolean, V, [False, True]) -> pick_branch(boolean, V, [False, True]) when V == true; V == false ->
Alt = if V -> True; true -> False end, Alt = if V -> True; true -> False end,
case Alt of case Alt of
missing -> false; missing -> false;
@@ -1213,10 +1172,62 @@ pick_branch(boolean, V, [False, True]) ->
pick_branch(_Type, _V, _Alts) -> pick_branch(_Type, _V, _Alts) ->
false. false.
%% If there's a single branch that doesn't abort we can push the code for that
%% out of the switch.
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
case push_code_out_of_switch([Def | Alts]) of
{_, none} -> false;
{_, many} -> false;
{_, [{i, _, switch_body}]} -> false;
{[Def1 | Alts1], PushedOut} ->
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
end;
r_single_successful_branch(_, _) -> false.
push_code_out_of_switch([]) -> {[], none};
push_code_out_of_switch([Alt | Alts]) ->
{Alt1, PushedAlt} = push_code_out_of_alt(Alt),
{Alts1, PushedAlts} = push_code_out_of_switch(Alts),
Pushed =
case {PushedAlt, PushedAlts} of
{none, _} -> PushedAlts;
{_, none} -> PushedAlt;
_ -> many
end,
{[Alt1 | Alts1], Pushed}.
push_code_out_of_alt(missing) -> {missing, none};
push_code_out_of_alt([Body = {i, _, switch_body} | Code]) ->
case does_abort(Code) of
true -> {[Body | Code], none};
false -> {[Body], [Body | Code]} %% Duplicate the switch_body, in case we apply this in the middle of a switch
end;
push_code_out_of_alt([{switch, R, Type, Alts, Def}]) ->
{[Def1 | Alts1], Pushed} = push_code_out_of_switch([Def | Alts]),
{[{switch, R, Type, Alts1, Def1}], Pushed};
push_code_out_of_alt(Code) ->
{Code, many}. %% Conservative
does_abort([I | Code]) ->
does_abort(I) orelse does_abort(Code);
does_abort({i, _, {'ABORT', _}}) -> true;
does_abort({i, _, {'EXIT', _}}) -> true;
does_abort(missing) -> true;
does_abort({switch, _, _, Alts, Def}) ->
lists:all(fun does_abort/1, [Def | Alts]);
does_abort(_) -> false.
%% STORE R A, SWITCH R --> SWITCH A %% STORE R A, SWITCH R --> SWITCH A
r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
Ann1 =
case is_reg(A) of
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
false -> Ann
end,
Store = {i, Ann1, {'STORE', R, A}},
Switch = {switch, A, Type, Alts, Def}, Switch = {switch, A, Type, Alts, Def},
case R of case R of
A -> false;
?a -> {[Switch], Code}; ?a -> {[Switch], Code};
{var, _} -> {var, _} ->
case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of
@@ -1229,8 +1240,9 @@ r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts,
r_inline_switch_target(_, _) -> false. r_inline_switch_target(_, _) -> false.
%% Float switch-body to closest switch %% Float switch-body to closest switch
r_float_switch_body(I = {i, _, _}, [switch_body | Code]) -> r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
{[], [switch_body, I | Code]}; {J1, I1} = swap_instrs(I, J),
{[], [J1, I1 | Code]};
r_float_switch_body(_, _) -> false. r_float_switch_body(_, _) -> false.
%% Inline stores %% Inline stores
@@ -1239,43 +1251,47 @@ r_inline_store({i, _, {'STORE', R, R}}, Code) ->
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) -> r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
%% Not when A is var unless updating the annotations properly. %% Not when A is var unless updating the annotations properly.
Inline = case A of Inline = case A of
{arg, _} -> true; {arg, _} -> true;
?i(_) -> true; ?i(_) -> true;
_ -> false {store, _} -> true;
_ -> false
end, end,
if Inline -> r_inline_store([I], R, A, Code); if Inline -> r_inline_store([I], false, R, A, Code);
true -> false end; true -> false end;
r_inline_store(_, _) -> false. r_inline_store(_, _) -> false.
r_inline_store(Acc, R, A, [switch_body | Code]) -> r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
r_inline_store([switch_body | Acc], R, A, Code); r_inline_store([I | Acc], Progress, R, A, Code);
r_inline_store(Acc, R, A, [{i, Ann, I} | Code]) -> r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
#{ write := W, pure := Pure } = attributes(I), #{ write := W } = attributes(I),
Inl = fun(X) when X == R -> A; (X) -> X end, Inl = fun(X) when X == R -> A; (X) -> X end,
case not live_in(R, Ann) orelse not Pure orelse lists:member(W, [R, A]) of case live_in(R, Ann) of
true -> false; false -> false; %% No more reads of R
false -> true ->
case op_view(I) of {I1, Progress1} =
{Op, S, As} -> case op_view(I) of
case lists:member(R, As) of {Op, S, As} ->
true -> case lists:member(R, As) of
Acc1 = [{i, Ann, from_op_view(Op, S, lists:map(Inl, As))} | Acc], true -> {from_op_view(Op, S, lists:map(Inl, As)), true};
case r_inline_store(Acc1, R, A, Code) of false -> {I, Progress}
false -> {lists:reverse(Acc1), Code}; end;
{_, _} = Res -> Res _ -> {I, Progress}
end; end,
false -> Acc1 = [{i, Ann, I1} | Acc],
r_inline_store([{i, Ann, I} | Acc], R, A, Code) %% Stop if write to R or A
end; case lists:member(W, [R, A]) of
_ -> r_inline_store([{i, Ann, I} | Acc], R, A, Code) true when Progress1 -> {lists:reverse(Acc1), Code};
true -> false;
false -> r_inline_store(Acc1, Progress1, R, A, Code)
end end
end; end;
r_inline_store(_Acc, _, _, _) -> false. r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
r_inline_store(_, false, _, _, _) -> false.
%% Shortcut write followed by final read %% Shortcut write followed by final read
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) -> r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
case op_view(I) of case op_view(I) of
{Op, R, As} -> {Op, R = {var, _}, As} ->
Copy = case J of Copy = case J of
{'STORE', S, R} -> {write_to, S}; {'STORE', S, R} -> {write_to, S};
_ -> false _ -> false
@@ -1292,8 +1308,9 @@ r_one_shot_var(_, _) -> false.
%% Remove writes to dead variables %% Remove writes to dead variables
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
r_write_to_dead_var({i, Ann, I}, Code) -> r_write_to_dead_var({i, Ann, I}, Code) ->
#{ pure := Pure } = attributes(I),
case op_view(I) of case op_view(I) of
{_Op, R = {var, _}, As} -> {_Op, R, As} when R /= ?a, Pure ->
case live_out(R, Ann) of case live_out(R, Ann) of
false -> false ->
%% Subtle: we still have to pop the stack if any of the arguments %% Subtle: we still have to pop the stack if any of the arguments
@@ -1305,21 +1322,24 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
end; end;
r_write_to_dead_var(_, _) -> false. r_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view(T) when is_tuple(T) -> op_view(T) when is_tuple(T) ->
case tuple_to_list(T) of [Op, R | As] = tuple_to_list(T),
[Op, R | As] when ?IsOp(Op) -> CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
{Op, R, As}; case attributes(list_to_tuple([Op, dst | [src || _ <- As]])) of
_ -> false #{ write := dst, read := Rs } -> CheckReads(Rs, {Op, R, As});
#{ write := none, read := Rs } -> CheckReads(Rs, {Op, none, [R | As]});
_ -> false
end; end;
op_view(_) -> false. op_view(_) -> false.
from_op_view(Op, none, As) -> list_to_tuple([Op | As]);
from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]). from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
%% Desugar and specialize and remove annotations %% Desugar and specialize and remove annotations
-spec unannotate(scode_a()) -> scode(); -spec unannotate(scode_a()) -> scode();
(sinstr_a()) -> sinstr(); (sinstr_a()) -> sinstr();
(missing) -> missing. (missing) -> missing.
unannotate(switch_body) -> [switch_body];
unannotate({switch, Arg, Type, Alts, Def}) -> unannotate({switch, Arg, Type, Alts, Def}) ->
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}]; [{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
unannotate(missing) -> missing; unannotate(missing) -> missing;
@@ -1329,18 +1349,27 @@ unannotate({i, _Ann, I}) -> [I].
%% Desugar and specialize %% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()]; desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()];
desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(A)]; desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(desugar_arg(A))];
desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()]; desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()];
desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(A)]; desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(desugar_arg(A))];
desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()]; desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()];
desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(A)]; desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(desugar_arg(A))];
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(A)]; desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
desugar({switch, Arg, Type, Alts, Def}) -> desugar({switch, Arg, Type, Alts, Def}) ->
[{switch, Arg, Type, [desugar(A) || A <- Alts], desugar(Def)}]; [{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
desugar(missing) -> missing; desugar(missing) -> missing;
desugar(Code) when is_list(Code) -> desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code); lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I]. desugar(I) -> [desugar_args(I)].
desugar_args(I) when is_tuple(I) ->
[Op | Args] = tuple_to_list(I),
list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]);
desugar_args(I) -> I.
desugar_arg({store, N}) -> {var, -N};
desugar_arg(A) -> A.
%% -- Phase III -------------------------------------------------------------- %% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks %% Constructing basic blocks
@@ -1405,11 +1434,13 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
{DefRef, DefBlk} = {DefRef, DefBlk} =
case Default of case Default of
missing when Catchall == none -> missing when Catchall == none ->
FreshBlk([aeb_fate_ops:exit(?i(<<"Incomplete patterns">>))], none); FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
missing -> {Catchall, []}; missing -> {Catchall, []};
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall) _ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
%% ^ fall-through to the outer catchall %% ^ fall-through to the outer catchall
end, end,
%% If we don't generate a switch, we need to pop the argument if on the stack.
Pop = [{'POP', ?void} || Arg == ?a],
{Blk1, Code1, AltBlks} = {Blk1, Code1, AltBlks} =
case Type of case Type of
boolean -> boolean ->
@@ -1425,7 +1456,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
_ -> FalseCode ++ [{jump, RestRef}] _ -> FalseCode ++ [{jump, RestRef}]
end, end,
case lists:usort(Alts) == [missing] of case lists:usort(Alts) == [missing] of
true -> {Blk#blk{code = [{jump, DefRef}]}, [], []}; true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
false -> false ->
case Arg of case Arg of
?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk}; ?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk};
@@ -1435,18 +1466,28 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
end; end;
tuple -> tuple ->
[TCode] = Alts, [TCode] = Alts,
{Blk#blk{code = TCode ++ [{jump, RestRef}]}, [], []}; case TCode of
missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
_ -> {Blk#blk{code = Pop ++ TCode ++ [{jump, RestRef}]}, [], []}
end;
{variant, [_]} -> {variant, [_]} ->
%% [SINGLE_CON_SWITCH] Single constructor switches don't need a %% [SINGLE_CON_SWITCH] Single constructor switches don't need a
%% switch instruction. %% switch instruction.
[AltCode] = Alts, [AltCode] = Alts,
{Blk#blk{code = AltCode ++ [{jump, RestRef}]}, [], []}; case AltCode of
missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
_ -> {Blk#blk{code = Pop ++ AltCode ++ [{jump, RestRef}]}, [], []}
end;
{variant, _Ar} -> {variant, _Ar} ->
MkBlk = fun(missing) -> {DefRef, []}; case lists:usort(Alts) == [missing] of
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
end, false ->
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)), MkBlk = fun(missing) -> {DefRef, []};
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)} (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
end,
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
end
end, end,
Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref
block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc); block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc);
@@ -1462,9 +1503,10 @@ optimize_blocks(Blocks) ->
RBlockMap = maps:from_list(RBlocks), RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []), RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ], RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
RBlocks3 = remove_dead_blocks(RBlocks2), RBlocks3 = shortcut_jump_chains(RBlocks2),
RBlocks4 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks3 ], RBlocks4 = remove_dead_blocks(RBlocks3),
Rev(RBlocks4). RBlocks5 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks4 ],
Rev(RBlocks5).
%% Choose the next block based on the final jump. %% Choose the next block based on the final jump.
reorder_blocks([], Acc) -> reorder_blocks([], Acc) ->
@@ -1500,6 +1542,21 @@ inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
end; end;
inline_block(_, _, Code) -> Code. inline_block(_, _, Code) -> Code.
%% Shortcut jumps to blocks with a single jump
shortcut_jump_chains(RBlocks) ->
Subst = lists:foldl(fun({L1, [{jump, L2}]}, Sub) ->
Sub#{ L1 => maps:get(L2, Sub, L2) };
(_, Sub) -> Sub end, #{}, RBlocks),
[ {Ref, update_labels(Subst, Code)} || {Ref, Code} <- RBlocks ].
update_labels(Sub, Ref) when is_reference(Ref) ->
maps:get(Ref, Sub, Ref);
update_labels(Sub, L) when is_list(L) ->
lists:map(fun(X) -> update_labels(Sub, X) end, L);
update_labels(Sub, T) when is_tuple(T) ->
list_to_tuple(update_labels(Sub, tuple_to_list(T)));
update_labels(_, X) -> X.
%% Remove unused blocks %% Remove unused blocks
remove_dead_blocks(Blocks = [{Top, _} | _]) -> remove_dead_blocks(Blocks = [{Top, _} | _]) ->
BlockMap = maps:from_list(Blocks), BlockMap = maps:from_list(Blocks),
+6
View File
@@ -16,6 +16,7 @@
set_payable/2, set_payable/2,
enter_namespace/2, enter_namespace/2,
get_namespace/1, get_namespace/1,
in_main_contract/1,
qualify/2, qualify/2,
set_functions/2, set_functions/2,
map_typerep/2, map_typerep/2,
@@ -74,6 +75,7 @@ builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
#{ "bool" => Word #{ "bool" => Word
, "int" => Word , "int" => Word
, "char" => Word
, "bits" => Word , "bits" => Word
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
@@ -120,6 +122,10 @@ enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
enter_namespace(NS, Icode) -> enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }. Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon(). -spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false). get_namespace(Icode) -> maps:get(namespace, Icode, false).
+2 -2
View File
@@ -27,7 +27,7 @@ convert(#{ contract_name := _ContractName
}, },
_Options) -> _Options) ->
%% Create a function dispatcher %% Create a function dispatcher
DispatchFun = {"_main", [], [{"arg", "_"}], DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"}, {switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun), [{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]}, {tuple, make_args(Args)}]},
@@ -44,7 +44,7 @@ convert(#{ contract_name := _ContractName
%% taken from the stack %% taken from the stack
StopLabel = make_ref(), StopLabel = make_ref(),
StatefulStopLabel = make_ref(), StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "_main"), MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType), StateTypeValue = aeso_ast_to_icode:type_value(StateType),
+60 -4
View File
@@ -9,12 +9,14 @@
-module(aeso_parse_lib). -module(aeso_parse_lib).
-export([parse/2, -export([parse/2,
return/1, fail/0, fail/1, map/2, bind/2, return/1, fail/0, fail/1, fail/2, map/2, bind/2,
lazy/1, choice/1, choice/2, tok/1, layout/0, lazy/1, choice/1, choice/2, tok/1, layout/0,
left/2, right/2, between/3, optional/1, left/2, right/2, between/3, optional/1,
many/1, many1/1, sep/2, sep1/2, many/1, many1/1, sep/2, sep1/2,
infixl/2, infixr/2]). infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
%% -- Types ------------------------------------------------------------------ %% -- Types ------------------------------------------------------------------
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
@@ -98,6 +100,10 @@ apply_p(X, K) -> K(X).
-spec lazy(fun(() -> parser(A))) -> parser(A). -spec lazy(fun(() -> parser(A))) -> parser(A).
lazy(Delayed) -> ?lazy(Delayed). lazy(Delayed) -> ?lazy(Delayed).
%% @doc A parser that always fails at a known location.
-spec fail(pos(), term()) -> parser(none()).
fail(Pos, Err) -> ?fail({Pos, Err}).
%% @doc A parser that always fails. %% @doc A parser that always fails.
-spec fail(term()) -> parser(none()). -spec fail(term()) -> parser(none()).
fail(Err) -> ?fail(Err). fail(Err) -> ?fail(Err).
@@ -155,7 +161,7 @@ layout() -> ?layout.
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}. -spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
parse(P, S) -> parse(P, S) ->
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
{[], {Pos, Err}} -> {error, {Pos, parse_error, flatten_error(Err)}}; {[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
{[A], _} -> {ok, A}; {[A], _} -> {ok, A};
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}} {As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
end. end.
@@ -285,7 +291,7 @@ parse1({tok_bind, Map}, Ts, Acc, Err) ->
%% y + y)(4) %% y + y)(4)
case maps:get(vclose, Map, '$not_found') of case maps:get(vclose, Map, '$not_found') of
'$not_found' -> '$not_found' ->
{Acc, unexpected_token_error(Ts, T)}; {Acc, unexpected_token_error(Ts, maps:keys(Map), T)};
F -> F ->
VClose = {vclose, pos(T)}, VClose = {vclose, pos(T)},
Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }), Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }),
@@ -322,12 +328,52 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T);
current_pos(#ts{ last = T }) -> end_pos(pos(T)). current_pos(#ts{ last = T }) -> end_pos(pos(T)).
-spec mk_error(#ts{}, term()) -> error(). -spec mk_error(#ts{}, term()) -> error().
mk_error(_Ts, {Pos, Err}) ->
{Pos, Err};
mk_error(Ts, Err) -> mk_error(Ts, Err) ->
{current_pos(Ts), Err}. {current_pos(Ts), Err}.
-spec unexpected_token_error(#ts{}, token()) -> error(). -spec unexpected_token_error(#ts{}, token()) -> error().
unexpected_token_error(Ts, T) -> unexpected_token_error(Ts, T) ->
mk_error(Ts, io_lib:format("Unexpected token ~p", [tag(T)])). unexpected_token_error(Ts, [], T).
unexpected_token_error(Ts, Expect, {Tag, _}) when Tag == vclose; Tag == vsemi ->
Braces = [')', ']', '}'],
Fix = case lists:filter(fun(T) -> lists:member(T, Braces) end, Expect) of
[] -> " Probable causes:\n"
" - something is missing in the previous statement, or\n"
" - this line should be indented more.";
[T | _] -> io_lib:format(" Did you forget a ~p?", [T])
end,
Msg = io_lib:format("Unexpected indentation.~s", [Fix]),
mk_error(Ts, Msg);
unexpected_token_error(Ts, Expect, T) ->
ExpectCon = lists:member(con, Expect),
ExpectId = lists:member(id, Expect),
Fix = case T of
{id, _, X} when ExpectCon, hd(X) /= $_ -> io_lib:format(" Did you mean ~s?", [mk_upper(X)]);
{con, _, X} when ExpectId -> io_lib:format(" Did you mean ~s?", [mk_lower(X)]);
{qcon, _, Xs} when ExpectCon -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
{qid, _, Xs} when ExpectId -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
_ -> ""
end,
mk_error(Ts, io_lib:format("Unexpected ~s.~s", [describe(T), Fix])).
mk_upper([C | Rest]) -> string:to_upper([C]) ++ Rest.
mk_lower([C | Rest]) -> string:to_lower([C]) ++ Rest.
describe({id, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({con, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({qid, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({qcon, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({tvar, _, X}) -> io_lib:format("type variable ~s", [X]);
describe({char, _, _}) -> "character literal";
describe({string, _, _}) -> "string literal";
describe({hex, _, _}) -> "integer literal";
describe({int, _, _}) -> "integer literal";
describe({bytes, _, _}) -> "bytes literal";
describe(T) -> io_lib:format("token '~s'", [tag(T)]).
%% Get the next token from a token stream. Inserts layout tokens if necessary. %% Get the next token from a token stream. Inserts layout tokens if necessary.
-spec next_token(#ts{}) -> false | {token(), #ts{}}. -spec next_token(#ts{}) -> false | {token(), #ts{}}.
@@ -411,3 +457,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1)) end, Map2, maps:to_list(Map1))
end. end.
%% Current source file
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
add_current_file({L, C}) -> {current_file(), L, C};
add_current_file(Pos) -> Pos.
+1 -1
View File
@@ -19,7 +19,7 @@
-import(aeso_parse_lib, -import(aeso_parse_lib,
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2, [tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0, infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
fail/0, fail/1, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2, fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
left/2, right/2, optional/1]). left/2, right/2, optional/1]).
+69 -24
View File
@@ -11,10 +11,9 @@
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-type parse_result() :: {ok, aeso_syntax:ast()} -type parse_result() :: aeso_syntax:ast() | none().
| {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}.
-type include_hash() :: {string(), binary()}. -type include_hash() :: {string(), binary()}.
@@ -33,21 +32,50 @@ string(String, Opts) ->
string(String, Included, Opts) -> string(String, Included, Opts) ->
case parse_and_scan(file(), String, Opts) of case parse_and_scan(file(), String, Opts) of
{ok, AST} -> {ok, AST} ->
expand_includes(AST, Included, Opts); case expand_includes(AST, Included, Opts) of
Err = {error, _} -> {ok, AST1} -> AST1;
Err {error, Err} -> parse_error(Err)
end;
{error, Err} ->
parse_error(Err)
end. end.
type(String) -> type(String) ->
parse_and_scan(type(), String, []). case parse_and_scan(type(), String, []) of
{ok, AST} -> {ok, AST};
{error, Err} -> {error, [mk_error(Err)]}
end.
parse_and_scan(P, S, Opts) -> parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)), set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error {error, {{Input, Pos}, _}} ->
{error, {Pos, scan_error, Input}}
end. end.
-dialyzer({nowarn_function, parse_error/1}).
parse_error(Err) ->
aeso_errors:throw(mk_error(Err)).
mk_p_err(Pos, Msg) ->
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
mk_error({Pos, scan_error, Input}) ->
mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
mk_error({Pos, parse_error, Err}) ->
Msg = io_lib:format("~s\n", [Err]),
mk_p_err(Pos, Msg);
mk_error({Pos, ambiguous_parse, As}) ->
Msg = io_lib:format("Ambiguous parse result: ~p\n", [As]),
mk_p_err(Pos, Msg);
mk_error({Pos, include_error, File}) ->
Msg = io_lib:format("Couldn't find include file '~s'\n", [File]),
mk_p_err(Pos, Msg).
mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col);
mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col).
%% -- Parsing rules ---------------------------------------------------------- %% -- Parsing rules ----------------------------------------------------------
file() -> choice([], block(decl())). file() -> choice([], block(decl())).
@@ -60,6 +88,7 @@ decl() ->
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) , ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, pragma()
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -77,6 +106,16 @@ decl() ->
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])). ])).
pragma() ->
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
version() ->
?RULE(token(int), many({tok('.'), token(int)}), mk_version(_1, _2)).
mk_version({int, _, Maj}, Rest) ->
[Maj | [N || {_, {int, _, N}} <- Rest]].
fun_or_entry() -> fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}), choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]). ?RULE(keyword(entrypoint), {entrypoint, _1})]).
@@ -285,7 +324,7 @@ map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
elim(E, []) -> E; elim(E, []) -> E;
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es); elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
elim(E, [{app, Ann, Args} | Es]) -> elim({app, Ann, E, Args}, Es); elim(E, [{app, _Ann, Args} | Es]) -> elim({app, aeso_syntax:get_ann(E), E, Args}, Es);
elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es); elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es);
elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es); elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es);
elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es). elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es).
@@ -379,7 +418,7 @@ token(Tag) ->
id(Id) -> id(Id) ->
?LET_P({id, A, X} = Y, id(), ?LET_P({id, A, X} = Y, id(),
if X == Id -> Y; if X == Id -> Y;
true -> fail({A, "expected 'bytes'"}) true -> fail({A, "expected '" ++ Id ++ "'"})
end). end).
id_or_addr() -> id_or_addr() ->
@@ -425,12 +464,6 @@ bracket_list(P) -> brackets(comma_sep(P)).
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(file, Ann), {proplists:get_value(file, Ann),
proplists:get_value(line, Ann), proplists:get_value(line, Ann),
@@ -533,14 +566,9 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}. {field, Ann, F, parse_pattern(E)}.
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()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). fail(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
@@ -591,16 +619,33 @@ read_file(File, Opts) ->
case maps:get(binary_to_list(File), Files, not_found) of case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found}; not_found -> {error, not_found};
Src -> {ok, Src} Src -> {ok, Src}
end;
escript ->
try
Escript = escript:script_name(),
{ok, Sections} = escript:extract(Escript, []),
Archive = proplists:get_value(archive, Sections),
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
{ok, [{_, Src}]} -> {ok, Src};
_ -> {error, not_found}
end
catch _:_ ->
{error, not_found}
end end
end. end.
stdlib_options() -> stdlib_options() ->
[{include, {file_system, [aeso_stdlib:stdlib_include_path()]}}]. StdLibDir = aeso_stdlib:stdlib_include_path(),
case filelib:is_dir(StdLibDir) of
true -> [{include, {file_system, [StdLibDir]}}];
false -> [{include, escript}]
end.
get_include_code(File, Ann, Opts) -> get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, _}, {ok,_ }} -> {{ok, _}, {ok,_ }} ->
return_error(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
{_, {ok, Bin}} -> {_, {ok, Bin}} ->
{ok, binary_to_list(Bin)}; {ok, binary_to_list(Bin)};
{{ok, Bin}, _} -> {{ok, Bin}, _} ->
+5
View File
@@ -149,6 +149,7 @@ decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) -> decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({pragma, _, Pragma}) -> pragma(Pragma);
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
@@ -170,6 +171,10 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]); hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D). decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec pragma(aeso_syntax:pragma()) -> doc().
pragma({compiler, Op, Ver}) ->
text("@compiler " ++ atom_to_list(Op) ++ " " ++ string:join([integer_to_list(N) || N <- Ver], ".")).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
with_options(Options, fun() -> expr(E) end). with_options(Options, fun() -> expr(E) end).
+20 -9
View File
@@ -13,14 +13,15 @@
override/2, push/2, pop/1]). override/2, push/2, pop/1]).
lexer() -> lexer() ->
Number = fun(Digit) -> [Digit, "+(_", Digit, "+)*"] end,
DIGIT = "[0-9]", DIGIT = "[0-9]",
HEXDIGIT = "[0-9a-fA-F]", HEXDIGIT = "[0-9a-fA-F]",
LOWER = "[a-z_]", LOWER = "[a-z_]",
UPPER = "[A-Z]", UPPER = "[A-Z]",
CON = [UPPER, "[a-zA-Z0-9_]*"], CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"], INT = Number(DIGIT),
HEX = ["0x", HEXDIGIT, "+"], HEX = ["0x", Number(HEXDIGIT)],
BYTES = ["#", HEXDIGIT, "+"], BYTES = ["#", Number(HEXDIGIT)],
WS = "[\\000-\\ ]+", WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"], ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID], TVAR = ["'", ID],
@@ -53,7 +54,7 @@ lexer() ->
, {CHAR, token(char, fun parse_char/1)} , {CHAR, token(char, fun parse_char/1)}
, {STRING, token(string, fun parse_string/1)} , {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)} , {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)} , {INT, token(int, fun parse_int/1)}
, {BYTES, token(bytes, fun parse_bytes/1)} , {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!) %% Identifiers (qualified first!)
@@ -95,9 +96,11 @@ parse_char([$', C, $']) -> C.
unescape(Str) -> unescape(Str, []). unescape(Str) -> unescape(Str, []).
%% TODO: numeric escapes
unescape([$"], Acc) -> unescape([$"], Acc) ->
list_to_binary(lists:reverse(Acc)); list_to_binary(lists:reverse(Acc));
unescape([$\\, $x, D1, D2 | Chars ], Acc) ->
C = list_to_integer([D1, D2], 16),
unescape(Chars, [C | Acc]);
unescape([$\\, Code | Chars], Acc) -> unescape([$\\, Code | Chars], Acc) ->
Ok = fun(C) -> unescape(Chars, [C | Acc]) end, Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
case Code of case Code of
@@ -115,10 +118,18 @@ unescape([$\\, Code | Chars], Acc) ->
unescape([C | Chars], Acc) -> unescape([C | Chars], Acc) ->
unescape(Chars, [C | Acc]). unescape(Chars, [C | Acc]).
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). strip_underscores(S) ->
lists:filter(fun(C) -> C /= $_ end, S).
parse_bytes("#" ++ Chars) -> parse_hex("0x" ++ S) ->
N = list_to_integer(Chars, 16), list_to_integer(strip_underscores(S), 16).
Digits = (length(Chars) + 1) div 2,
parse_int(S) ->
list_to_integer(strip_underscores(S)).
parse_bytes("#" ++ S0) ->
S = strip_underscores(S0),
N = list_to_integer(S, 16),
Digits = (length(S) + 1) div 2,
<<N:Digits/unit:8>>. <<N:Digits/unit:8>>.
+6 -1
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -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([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]). -export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]). -export_type([decl/0, letbind/0, typedef/0, pragma/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/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([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]). -export_type([ast/0]).
@@ -36,11 +36,16 @@
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
| letbind(). | letbind().
-type compiler_version() :: [non_neg_integer()].
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letbind() -type letbind()
:: {letval, ann(), id(), type(), expr()} :: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}. | {letfun, ann(), id(), [arg()], type(), expr()}.
+27 -5
View File
@@ -18,9 +18,14 @@ from_aevm(word, {id, _, "address"}, N) -> address_literal(ac
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1}; from_aevm(word, {id, _, "int"}, N0) ->
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N}); <<N:256/signed>> = <<N0:256>>,
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 -> from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>, <<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>}; {bytes, [], <<Bytes:Len/unit:8>>};
@@ -55,6 +60,7 @@ from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
VmTypes = lists:nth(Tag + 1, VmCons), VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons), ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args); from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args) from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types), when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) -> length(VmTypes) == length(Args) ->
@@ -70,8 +76,10 @@ from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin}); from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N}; from_fate({id, _, "int"}, N) when is_integer(N) ->
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B}; from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S}; from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) -> from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
@@ -105,9 +113,23 @@ from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
from_fate(ConType, ArgList); from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia) _ -> throw(cannot_translate_to_sophia)
end; end;
from_fate({constr_t, _, Con, []}, []) -> Con;
from_fate({constr_t, _, Con, Types}, Args) from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) -> when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg) {app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]}; || {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) -> from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia). throw(cannot_translate_to_sophia).
make_bits(N) ->
Id = fun(F) -> {qid, [], ["Bits", F]} end,
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
true -> make_bits(Id("set"), Id("none"), 0, N) end.
make_bits(_Set, Zero, _I, 0) -> Zero;
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
make_bits(Set, Zero, I + 1, N div 2);
make_bits(Set, Zero, I, N) ->
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "4.0.0-rc1"}, {vsn, "4.1.0-rc1"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+61 -7
View File
@@ -62,7 +62,7 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"), ok = Check("int", "- 42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
@@ -72,6 +72,58 @@ encode_decode_sophia_test() ->
ok = Check("r", "{x = (\"foo\", 0), y = Red}"), ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok. ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
"when checking the application at line 5, column 34 of\n"
" x : (int) => string\nto arguments\n true : bool\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
ok.
decode_calldata_neg_test() ->
Code1 = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
ok.
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract MakeCall =\n"
@@ -80,11 +132,11 @@ encode_decode_sophia_string(SophiaType, String) ->
, " record r = {x : an_alias(int), y : variant}\n" , " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n" , " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ], , " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
{ok, _, {[Type], _}, [Arg]} -> {ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]), io:format("Type ~p~n", [Type]),
Data = encode(Arg), Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, []) of case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
{ok, Sophia} -> {ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))])); lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} -> {error, Err} ->
@@ -152,7 +204,7 @@ oracle_test() ->
" Oracle.get_question(o, q)\n", " Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], []), "oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
ok. ok.
@@ -161,8 +213,10 @@ permissive_literals_fail_test() ->
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n" " stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n", " Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} = {error, [Err]} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []), aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)),
?assertEqual(type_error, aeso_errors:type(Err)),
ok. ok.
encode_decode_calldata(FunName, Types, Args) -> encode_decode_calldata(FunName, Types, Args) ->
@@ -174,7 +228,7 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetVMType) -> encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []), {ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]), {ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
?assertEqual(RetType, RetVMType), ?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]}, CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata), {ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
+2 -2
View File
@@ -107,11 +107,11 @@ aci_test_contract(Name) ->
check_stub(Stub, Options) -> check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} -> Ast ->
try try
%% io:format("AST: ~120p\n", [Ast]), %% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, []) aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} -> catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]), io:format("Type error:\n~s\n", [TE]),
error(TE); error(TE);
_:R -> _:R ->
+28 -12
View File
@@ -29,11 +29,10 @@ calldata_test_() ->
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]); true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() -> calldata_aci_test_() ->
@@ -53,19 +52,34 @@ calldata_aci_test_() ->
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]); true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST).
strip_ann(T) when is_tuple(T) ->
strip_ann1(setelement(2, T, []));
strip_ann(X) -> strip_ann1(X).
strip_ann1({map, [], KVs}) ->
{map, [], [{strip_ann(K), strip_ann(V)} || {K, V} <- KVs]};
strip_ann1(T) when is_tuple(T) ->
list_to_tuple(strip_ann1(tuple_to_list(T)));
strip_ann1(L) when is_list(L) ->
lists:map(fun strip_ann/1, L);
strip_ann1(X) -> X.
ast_exprs(ContractString, Fun, Args, Opts) -> ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)), {ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)), {ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)), ?assert(is_list(Exprs)),
Exprs. strip_ann(Exprs).
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -85,7 +99,9 @@ compilable_contracts() ->
{"maps", "init", []}, {"maps", "init", []},
{"funargs", "menot", ["false"]}, {"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]}, {"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]}, {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test
{"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
+540 -163
View File
@@ -12,62 +12,92 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
run_test(Test) ->
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
[ begin
io:format("~s\n", [Label]),
Fun()
end || {Label, Fun} <- ?MODULE:TestFun() ],
ok.
%% Very simply test compile the given contracts. Only basic checks %% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
fun() -> fun() ->
case compile(Backend, ContractName) of case compile(Backend, ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} when Backend == aevm ->
?assertMatch(Code when is_binary(Code), ByteCode); ?assertMatch(Code when is_binary(Code), ByteCode);
#{fate_code := Code} when Backend == fate -> #{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code}); ?assertMatch({X, X}, {Code1, Code});
ErrBin -> ErrBin ->
io:format("\n~s", [ErrBin]), io:format("\n~s", [ErrBin]),
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++ not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Test file not found error",
fun() -> fun() ->
case compile(aevm, ContractName) of {error, Errors} = aeso_compiler:file("does_not_exist.aes"),
<<"Type errors\n", ErrorString/binary>> -> ExpErr = <<"File error:\ndoes_not_exist.aes: no such file or directory">>,
check_errors(lists:sort(ExpectedErrors), ErrorString); check_errors([ExpErr], Errors)
<<"Parse errors\n", ErrorString/binary>> -> end} ] ++
check_errors(lists:sort(ExpectedErrors), ErrorString) [ {"Testing error messages of " ++ ContractName,
end fun() ->
end} || Errors = compile(aevm, ContractName),
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ check_errors(ExpectedErrors, Errors)
[ {"Testing include with explicit files", end} ||
fun() -> {ContractName, ExpectedErrors} <- failing_contracts() ] ++
FileSystem = maps:from_list( [ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName,
[ begin fun() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), Errors = compile(Backend, ContractName),
{File, Bin} Expect =
end || File <- ["included.aes", "../contracts/included2.aes"] ]), case is_binary(ExpectedError) of
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), true -> [ExpectedError];
#{byte_code := Code2} = compile(aevm, "include"), false ->
?assertMatch(true, Code1 == Code2) case proplists:get_value(Backend, ExpectedError, no_error) of
end} ] ++ no_error -> no_error;
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend), Err -> [Err]
fun() -> end
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"), end,
#{ byte_code := DeadCode } = compile(Backend, "deadcode"), check_errors(Expect, Errors)
SizeNoDeadCode = byte_size(NoDeadCode), end} ||
SizeDeadCode = byte_size(DeadCode), {ContractName, ExpectedError} <- failing_code_gen_contracts(),
Delta = if Backend == aevm -> 40; Backend <- [aevm, fate] ] ++
Backend == fate -> 20 end, [ {"Testing include with explicit files",
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), fun() ->
ok FileSystem = maps:from_list(
end} || Backend <- [aevm, fate] ]. [ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend),
fun() ->
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"),
#{ byte_code := DeadCode } = compile(Backend, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
Delta = if Backend == aevm -> 40;
Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok
end} || Backend <- [aevm, fate] ] ++
[].
check_errors(Expect, ErrorString) -> check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
%% This removes the final single \n as well. check_errors(Expect, #{}) ->
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]), ?assertEqual({error, Expect}, ok);
check_errors(Expect0, Actual0) ->
Expect = lists:sort(Expect0),
Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ],
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@@ -80,9 +110,10 @@ compile(Backend, Name) ->
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {backend, Backend} | Options]) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} -> ErrorString {error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@@ -120,6 +151,7 @@ compilable_contracts() ->
"address_chain", "address_chain",
"namespace_bug", "namespace_bug",
"bytes_to_x", "bytes_to_x",
"bytes_concat",
"aens", "aens",
"tuple_match", "tuple_match",
"cyclic_include", "cyclic_include",
@@ -127,7 +159,9 @@ compilable_contracts() ->
"double_include", "double_include",
"manual_stdlib_include", "manual_stdlib_include",
"list_comp", "list_comp",
"payable" "payable",
"unapplied_builtins",
"underscore_number_literals"
]. ].
not_yet_compilable(fate) -> []; not_yet_compilable(fate) -> [];
@@ -135,242 +169,585 @@ not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
-define(Pos(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " error in '",
(list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n").
-define(Pos(Line, Col), ?Pos(__Kind, __File, Line, Col)).
-define(ERROR(Kind, Name, Errs),
(fun() ->
__Kind = Kind,
__File = ??Name,
{__File, Errs}
end)()).
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
failing_contracts() -> failing_contracts() ->
[ {"name_clash", {ok, V} = aeso_compiler:numeric_version(),
[<<"Duplicate definitions of abort at\n" Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
%% Parse errors
[ ?PARSE_ERROR(field_parse_error,
[<<?Pos(5, 26)
"Cannot use nested fields or keys in record construction: p.x">>])
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
%% Type errors
, ?TYPE_ERROR(name_clash,
[<<?Pos(14, 3)
"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n" <<?Pos(15, 3)
"Duplicate definitions of require at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 15, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<?Pos(11, 3)
"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n" <<?Pos(5, 3)
"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n" " - line 4, column 3\n"
" - line 5, column 3">>, " - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n" <<?Pos(8, 3)
"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n" " - line 7, column 3\n"
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<?Pos(16, 3)
"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 16, column 3">>,
<<"Duplicate definitions of state at\n" <<?Pos(17, 3)
"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 17, column 3">>])
, {"type_errors", , ?TYPE_ERROR(type_errors,
[<<"Unbound variable zz at line 17, column 23">>, [<<?Pos(17, 23)
<<"Cannot unify int\n" "Unbound variable zz at line 17, column 23">>,
<<?Pos(26, 9)
"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int">>, " x : int">>,
<<"Cannot unify string\n" <<?Pos(9, 48)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 48)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<?Pos(34, 47)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 47\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<?Pos(34, 52)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 52\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(32, 18)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(11, 58)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 58\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify int\n" <<?Pos(38, 13)
"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<?Pos(22, 40)
"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 40)">>,
<<"Not a record type: string\n" <<?Pos(21, 44)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 44)">>,
<<"Not a record type: string\n" <<?Pos(20, 40)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 40)">>,
<<"Not a record type: string\n" <<?Pos(19, 37)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 37)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<?Pos(13, 27)
"Ambiguous record type with field y (at line 13, column 27) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n" <<?Pos(26, 7)
"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>, " x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 14).">>, <<?Pos(44, 14)
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, "Repeated argument x to function repeated_arg (at line 44, column 14).">>,
<<"No record type with fields y, z (at line 14, column 24)">>, <<?Pos(44, 14)
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>, "Repeated argument y to function repeated_arg (at line 44, column 14).">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>, <<?Pos(14, 24)
<<"Let binding at line 47, column 5 must be followed by an expression">>, "No record type with fields y, z (at line 14, column 24)">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>, <<?Pos(15, 26)
<<"Let binding at line 54, column 5 must be followed by an expression">>, "The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]} <<?Pos(15, 24)
, {"init_type_error", "Record type r2 does not have field y (at line 15, column 24)">>,
[<<"Cannot unify string\n" <<?Pos(47, 5)
"Let binding at line 47, column 5 must be followed by an expression">>,
<<?Pos(50, 5)
"Let binding at line 50, column 5 must be followed by an expression">>,
<<?Pos(54, 5)
"Let binding at line 54, column 5 must be followed by an expression">>,
<<?Pos(58, 5)
"Let binding at line 58, column 5 must be followed by an expression">>,
<<?Pos(63, 5)
"Cannot unify int\n"
" and bool\n"
"when checking the type of the expression at line 63, column 5\n"
" id(n) : int\n"
"against the expected type\n"
" bool">>])
, ?TYPE_ERROR(init_type_error,
[<<?Pos(7, 3)
"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>])
, {"missing_state_type", , ?TYPE_ERROR(missing_state_type,
[<<"Cannot unify string\n" [<<?Pos(5, 3)
"Cannot unify string\n"
" and unit\n" " and unit\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>])
, {"missing_fields_in_record_expression", , ?TYPE_ERROR(missing_fields_in_record_expression,
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<?Pos(7, 42)
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, "The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<?Pos(8, 42)
, {"namespace_clash", "The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>,
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} <<?Pos(6, 42)
, {"bad_events", "The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>])
[<<"The indexed type string (at line 9, column 25) is not a word type">>, , ?TYPE_ERROR(namespace_clash,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} [<<?Pos(4, 10)
, {"bad_events2", "The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>])
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, , ?TYPE_ERROR(bad_events,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} [<<?Pos(9, 25)
, {"type_clash", "The indexed type string (at line 9, column 25) is not a word type">>,
[<<"Cannot unify int\n" <<?Pos(10, 25)
"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>])
, ?TYPE_ERROR(bad_events2,
[<<?Pos(9, 7)
"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<?Pos(10, 7)
"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>])
, ?TYPE_ERROR(type_clash,
[<<?Pos(12, 42)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the record projection at line 12, column 42\n" "when checking the record projection at line 12, column 42\n"
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>])
, {"bad_include_and_ns", , ?TYPE_ERROR(bad_include_and_ns,
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>, [<<?Pos(2, 11)
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]} "Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
, {"bad_address_literals", <<?Pos(3, 13)
[<<"The type bytes(32) is not a contract type\n" "Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>])
"when checking that the contract literal at line 32, column 5\n" , ?TYPE_ERROR(bad_address_literals,
[<<?Pos(32, 5)
"The type bytes(32) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" bytes(32)">>, " bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n" <<?Pos(30, 5)
"when checking that the contract literal at line 30, column 5\n" "The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"The type address is not a contract type\n" <<?Pos(28, 5)
"when checking that the contract literal at line 28, column 5\n" "The type address is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<?Pos(25, 5)
"Cannot unify oracle_query('a, 'b)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('a, 'b)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<?Pos(23, 5)
"Cannot unify oracle_query('c, 'd)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('c, 'd)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<?Pos(21, 5)
"Cannot unify oracle_query('e, 'f)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('e, 'f)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<?Pos(18, 5)
"Cannot unify oracle('g, 'h)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('g, 'h)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<?Pos(16, 5)
"Cannot unify oracle('i, 'j)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('i, 'j)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<?Pos(14, 5)
"Cannot unify oracle('k, 'l)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('k, 'l)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(11, 5)
"Cannot unify address\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n" "when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(9, 5)
"Cannot unify address\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 9, column 5\n" "when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify address\n" <<?Pos(7, 5)
"Cannot unify address\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n" "when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>]} " bytes(32)">>,
, {"stateful", <<?Pos(34, 5),
[<<"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>, "The type address is not a contract type\n"
<<"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>, "when checking that the call to\n"
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>, " Address.to_contract\n"
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>, "has the type\n"
<<"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>, " address">>])
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>, , ?TYPE_ERROR(stateful,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>, [<<?Pos(13, 35)
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]} "Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
, {"bad_init_state_access", <<?Pos(14, 35)
[<<"The init function should return the initial state as its result and cannot write the state,\n" "Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
<<?Pos(16, 15)
"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<?Pos(20, 31)
"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<?Pos(35, 47)
"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
<<?Pos(48, 57)
"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<?Pos(49, 56)
"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
<<?Pos(52, 17)
"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>])
, ?TYPE_ERROR(bad_init_state_access,
[<<?Pos(11, 5)
"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n" "but it calls\n"
" - set_state (at line 11, column 5), which calls\n" " - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 38), which calls\n" " - roundabout (at line 8, column 38), which calls\n"
" - put (at line 7, column 39)">>, " - put (at line 7, column 39)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(12, 5)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - new_state (at line 12, column 5), which calls\n" " - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 29)">>, " - state (at line 5, column 29)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(13, 13)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - state (at line 13, column 13)">>]} " - state (at line 13, column 13)">>])
, {"field_parse_error", , ?TYPE_ERROR(modifier_checks,
[<<"line 6, column 1: In field_parse_error at 5:26:\n" [<<?Pos(11, 3)
"Cannot use nested fields or keys in record construction: p.x\n">>]} "The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
, {"modifier_checks", <<?Pos(3, 3)
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>, "Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>, <<?Pos(5, 10)
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>, "The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>, <<?Pos(12, 3)
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>, "The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>, <<?Pos(6, 3)
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]} "Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
, {"list_comp_not_a_list", <<?Pos(10, 3)
[<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">> "Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
]} <<?Pos(6, 3)
, {"list_comp_if_not_bool", "Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>])
[<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">> , ?TYPE_ERROR(list_comp_not_a_list,
]} [<<?Pos(2, 36)
, {"list_comp_bad_shadow", "Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
[<<"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">> ])
]} , ?TYPE_ERROR(list_comp_if_not_bool,
[<<?Pos(2, 44)
"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
])
, ?TYPE_ERROR(list_comp_bad_shadow,
[<<?Pos(2, 53)
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
])
, ?TYPE_ERROR(map_as_map_key,
[<<?Pos(5, 25)
"Invalid key type\n"
" map(int, int)\n"
"Map keys cannot contain other maps.">>,
<<?Pos(6, 25)
"Invalid key type\n"
" lm\n"
"Map keys cannot contain other maps.">>])
, ?TYPE_ERROR(calling_init_function,
[<<?Pos(7, 28)
"The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.">>])
, ?TYPE_ERROR(bad_top_level_decl,
[<<?Pos(1, 1) "The definition of 'square' must appear inside a contract or namespace.">>])
, ?TYPE_ERROR(missing_event_type,
[<<?Pos(3, 5)
"Unbound variable Chain.event at line 3, column 5\n"
"Did you forget to define the event type?">>])
, ?TYPE_ERROR(bad_bytes_concat,
[<<?Pos(12, 40)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - 'g (at line 12, column 20)\n"
" - 'h (at line 12, column 23)\n"
"and result type\n"
" - bytes(10) (at line 12, column 28)">>,
<<?Pos(13, 28)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - 'd (at line 13, column 20)\n"
" - 'e (at line 13, column 23)\n"
"and result type\n"
" - 'f (at line 13, column 14)">>,
<<?Pos(15, 5)
"Cannot unify bytes(26)\n"
" and bytes(25)\n"
"at line 15, column 5">>,
<<?Pos(17, 5)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - bytes(6) (at line 16, column 24)\n"
" - 'b (at line 16, column 34)\n"
"and result type\n"
" - 'c (at line 16, column 39)">>,
<<?Pos(19, 25)
"Cannot resolve length of byte array.">>])
, ?TYPE_ERROR(bad_bytes_split,
[<<?Pos(13, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - 'f (at line 12, column 20)\n"
"and result types\n"
" - 'e (at line 13, column 5)\n"
" - bytes(20) (at line 12, column 29)">>,
<<?Pos(16, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - bytes(15) (at line 15, column 24)\n"
"and result types\n"
" - 'c (at line 16, column 5)\n"
" - 'd (at line 16, column 5)">>,
<<?Pos(19, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - 'b (at line 18, column 20)\n"
"and result types\n"
" - bytes(20) (at line 18, column 25)\n"
" - 'a (at line 19, column 5)">>])
, ?TYPE_ERROR(wrong_compiler_version,
[<<?Pos(1, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " < 1.0">>,
<<?Pos(2, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
, ?TYPE_ERROR(multiple_contracts,
[<<?Pos(2, 3)
"Only the main contract can contain defined functions or entrypoints.\n"
"Fix: replace the definition of 'foo' by a type signature.">>])
, ?TYPE_ERROR(contract_as_namespace,
[<<?Pos(5, 28)
"Invalid call to contract entrypoint 'Foo.foo'.\n"
"It must be called as 'c.foo' for some c : Foo.">>])
]. ].
-define(Path(File), "code_errors/" ??File).
-define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
-define(SAME(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
-define(AEVM(File, Line, Col, Err), {?Path(File), [{aevm, ?Msg(File, Line, Col, Err)}]}).
-define(FATE(File, Line, Col, Err), {?Path(File), [{fate, ?Msg(File, Line, Col, Err)}]}).
-define(BOTH(File, Line, Col, ErrAEVM, ErrFATE),
{?Path(File), [{aevm, ?Msg(File, Line, Col, ErrAEVM)},
{fate, ?Msg(File, Line, Col, ErrFATE)}]}).
failing_code_gen_contracts() ->
[ ?SAME(last_declaration_must_be_contract, 1, 1,
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
, ?SAME(missing_definition, 2, 14,
"Missing definition of function 'foo'.")
, ?AEVM(polymorphic_entrypoint, 2, 17,
"The argument\n"
" x : 'a\n"
"of entrypoint 'id' has a polymorphic (contains type variables) type.\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?AEVM(polymorphic_entrypoint_return, 2, 3,
"The return type\n"
" 'a\n"
"of entrypoint 'fail' is polymorphic (contains type variables).\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?SAME(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?SAME(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?SAME(missing_init_function, 1, 10,
"Missing init function for the contract 'MissingInitFunction'.\n"
"The 'init' function can only be omitted if the state type is 'unit'.")
, ?SAME(parameterised_state, 3, 8,
"The state type cannot be parameterized.")
, ?SAME(parameterised_event, 3, 12,
"The event type cannot be parameterized.")
, ?SAME(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n"
" 'a\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?SAME(bad_aens_resolve, 6, 5,
"Invalid return type of AENS.resolve:\n"
" list(int)\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?AEVM(polymorphic_compare, 4, 5,
"Cannot compare values of type\n"
" 'a\n"
"The AEVM only supports '==' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare, 4, 5,
"Cannot compare values of type\n"
" (string * int)\n"
"The AEVM only supports '!=' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare_leq, 4, 5,
"Cannot compare values of type\n"
" (int * int)\n"
"The AEVM only supports '=<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(higher_order_compare, 4, 5,
"Cannot compare values of type\n"
" (int) => int\n"
"The AEVM only supports '<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(unapplied_contract_call, 6, 19,
"The AEVM does not support unapplied contract call to\n"
" r : Remote\n"
"Use FATE if you need this.")
, ?AEVM(unapplied_named_arg_builtin, 4, 15,
"The AEVM does not support unapplied use of Oracle.register.\n"
"Use FATE if you need this.")
, ?AEVM(polymorphic_map_keys, 4, 34,
"Invalid map key type\n"
" 'a\n"
"Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.")
, ?AEVM(higher_order_map_keys, 4, 42,
"Invalid map key type\n"
" (int) => int\n"
"Map keys cannot be higher-order.")
, ?SAME(polymorphic_query_type, 3, 5,
"Invalid oracle type\n"
" oracle('a, 'b)\n"
"The query type must not be polymorphic (contain type variables).")
, ?SAME(polymorphic_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, 'r)\n"
"The response type must not be polymorphic (contain type variables).")
, ?SAME(higher_order_query_type, 3, 5,
"Invalid oracle type\n"
" oracle((int) => int, string)\n"
"The query type must not be higher-order (contain function types).")
, ?SAME(higher_order_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, (int) => int)\n"
"The response type must not be higher-order (contain function types).")
, ?AEVM(higher_order_state, 3, 3,
"Invalid state type\n"
" {f : (int) => int}\n"
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
].
validation_test_() ->
[{"Validation fail: " ++ C1 ++ " /= " ++ C2,
fun() ->
Actual = case validate(C1, C2) of
{error, Errs} -> Errs;
ok -> #{}
end,
check_errors(Expect, Actual)
end} || {C1, C2, Expect} <- validation_fails()] ++
[{"Validation of " ++ C,
fun() ->
?assertEqual(ok, validate(C, C))
end} || C <- compilable_contracts()].
validation_fails() ->
[{"deadcode", "nodeadcode",
[<<"Data error:\n"
"Byte code does not match source code.\n"
"- Functions in the source code but not in the byte code:\n"
" .MyList.map2">>]},
{"validation_test1", "validation_test2",
[<<"Data error:\n"
"Byte code does not match source code.\n"
"- The implementation of the function code_fail is different.\n"
"- The attributes of the function attr_fail differ:\n"
" Byte code: payable\n"
" Source code: \n"
"- The type of the function type_fail differs:\n"
" Byte code: integer => integer\n"
" Source code: {tvar,0} => {tvar,0}">>]},
{"validation_test1", "validation_test3",
[<<"Data error:\n"
"Byte code contract is not payable, but source code contract is.">>]}].
validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source,
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
+2 -5
View File
@@ -39,7 +39,7 @@ simple_contracts_test_() ->
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end, RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
NonAssoc = fun(Op) -> NonAssoc = fun(Op) ->
OpAtom = list_to_atom(Op), OpAtom = list_to_atom(Op),
?assertError({error, {_, parse_error, _}}, ?assertThrow({error, [_]},
parse_expr(NoPar({a, Op, {b, Op, c}}))) end, parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
Stronger = fun(Op1, Op2) -> Stronger = fun(Op1, Op2) ->
CheckParens({{a, Op1, b}, Op2, c}), CheckParens({{a, Op1, b}, Op2, c}),
@@ -74,10 +74,7 @@ roundtrip_contract(Name) ->
parse_string(Text) -> parse_string(Text, []). parse_string(Text) -> parse_string(Text, []).
parse_string(Text, Opts) -> parse_string(Text, Opts) ->
case aeso_parser:string(Text, Opts) of aeso_parser:string(Text, Opts).
{ok, Contract} -> Contract;
Err -> error(Err)
end.
parse_expr(Text) -> parse_expr(Text) ->
[{letval, _, _, _, Expr}] = [{letval, _, _, _, Expr}] =
+2
View File
@@ -11,4 +11,6 @@ contract AddressLiterals =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote = entrypoint contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr_addr() : Remote =
Address.to_contract(addr())
+2
View File
@@ -30,4 +30,6 @@ contract AddressLiterals =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) = entrypoint contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr4() : address =
Address.to_contract(Contract.address)
+19
View File
@@ -0,0 +1,19 @@
contract BytesConcat =
entrypoint test1(x : bytes(10), y : bytes(20)) =
Bytes.concat(x, y)
entrypoint test2(x : bytes(10), y) : bytes(15) =
Bytes.concat(x, y)
entrypoint test3(x, y : bytes(20)) : bytes(25) =
Bytes.concat(x, y)
entrypoint fail1(x, y) : bytes(10) = Bytes.concat(x, y)
entrypoint fail2(x, y) = Bytes.concat(x, y)
entrypoint fail3(x : bytes(6), y : bytes(20)) : bytes(25) =
Bytes.concat(x, y)
entrypoint fail4(x : bytes(6), y) : _ =
Bytes.concat(x, y)
entrypoint fail5(x) = Bytes.to_str(x)
+20
View File
@@ -0,0 +1,20 @@
contract BytesSplit =
entrypoint test1(x) : bytes(10) * bytes(20) =
Bytes.split(x)
entrypoint test2(x : bytes(15)) : bytes(10) * _ =
Bytes.split(x)
entrypoint test3(x : bytes(25)) : _ * bytes(20) =
Bytes.split(x)
entrypoint fail1(x) : _ * bytes(20) =
Bytes.split(x)
entrypoint fail2(x : bytes(15)) : _ =
Bytes.split(x)
entrypoint fail3(x) : bytes(20) * _ =
Bytes.split(x)
+3
View File
@@ -0,0 +1,3 @@
function square(x) = x ^ 2
contract Main =
entrypoint main() = square(10)
+2
View File
@@ -15,3 +15,5 @@ contract BasicAuth =
entrypoint to_sign(h : hash, n : int) = entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
entrypoint weird_string() : string =
"\x19Weird String\x42\nMore\n"
+4
View File
@@ -0,0 +1,4 @@
contract BytesConcat =
entrypoint rot(a : bytes(3)) =
switch (Bytes.split(a))
(b, c) => Bytes.concat(c : bytes(2), b)
+7
View File
@@ -0,0 +1,7 @@
contract CallingInitFunction =
type state = int * int
entrypoint init() = (1, 2)
entrypoint call_init() = init()
@@ -0,0 +1,9 @@
contract BadAENSresolve =
type t('a) = option(list('a))
function fail() : t(int) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()
@@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : string * int) =
("foo", 1) != x
@@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : int) =
(1, 2) =< (x, x + 1)
@@ -0,0 +1,8 @@
contract HigherOrderCompare =
function cmp(x : int => int, y) : bool =
x < y
entrypoint test() =
let f(x) = (y) => x + y
cmp(f(1), f(2))
@@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint apply(f : int => int, x : int) = f(x)
@@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint add(x : int) = (y) => x + y
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t(int => int) = {[m] = 0}
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract HigherOrderQueryType =
stateful function foo(o) : oracle_query(_, string ) =
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract HigherOrderResponseType =
stateful function foo(o, q : oracle_query(string, _)) =
Oracle.respond(o, q, (x) => x + 1)
entrypoint main() = ()
@@ -0,0 +1,7 @@
contract HigherOrderState =
record state = {f : int => int}
entrypoint init() = {f = (x) => x}
entrypoint apply(n) = state.f(n)
stateful entrypoint inc() = put(state{ f = (x) => state.f(x + 1) })
@@ -0,0 +1,2 @@
namespace LastDeclarationIsNotAContract =
function add(x, y) = x + y
@@ -0,0 +1,3 @@
contract MissingDefinition =
entrypoint foo : int => int
entrypoint main() = foo(0)
@@ -0,0 +1,3 @@
contract MissingInitFunction =
type state = int * int
@@ -0,0 +1,4 @@
contract ParameterisedEvent =
datatype event('a) = Event(int)
@@ -0,0 +1,4 @@
contract ParameterisedState =
type state('a) = list('a)
@@ -0,0 +1,7 @@
contract PolymorphicAENSresolve =
function fail() : option('a) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()
@@ -0,0 +1,7 @@
contract PolymorphicCompare =
function cmp(x : 'a, y : 'a) : bool =
x == y
entrypoint test() =
cmp(4, 6) && cmp(true, false)
@@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint id(x : 'a) : 'a = x
@@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint fail() : 'a = abort("fail")
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t('a) = {[m] = 0}
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract PolymorphicQueryType =
stateful function is_oracle(o) =
Oracle.check(o)
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract PolymorphicResponseType =
function is_oracle(o : oracle(string, 'r)) =
Oracle.check(o)
entrypoint main(o : oracle(string, int)) = is_oracle(o)
@@ -0,0 +1,9 @@
contract Remote =
entrypoint foo : int => int
contract UnappliedContractCall =
function f(r) = r.foo
entrypoint test(r) = f(r)(0)
@@ -0,0 +1,5 @@
contract UnappliedNamedArgBuiltin =
// Allowed in FATE, but not AEVM
stateful entrypoint main(s) =
let reg = Oracle.register
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
+6
View File
@@ -0,0 +1,6 @@
contract Foo =
entrypoint foo : () => int
contract Fail =
entrypoint bad() : int = Foo.foo()
+3
View File
@@ -0,0 +1,3 @@
contract IndentFail =
entrypoint twoSpace() = ()
entrypoint oneSpace() = ()
+6
View File
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
type lm = list(map(int, int))
entrypoint foo(m) : t(map(int, int)) = {[m] = 0}
entrypoint bar(m) : t(lm) = Map.delete(m, {})
+3
View File
@@ -0,0 +1,3 @@
contract MissingEventType =
entrypoint main() =
Chain.event("MAIN")
+5
View File
@@ -0,0 +1,5 @@
contract ContractOne =
entrypoint foo() = "foo"
contract ContractTwo =
entrypoint bar() = "bar"
+5
View File
@@ -57,3 +57,8 @@ contract Test =
let f() = 0 let f() = 0
let g() = f() let g() = f()
function id(x : 'a) : 'a = x
entrypoint wrong_return(n : int) : bool =
id(n)
+63
View File
@@ -0,0 +1,63 @@
// Builtins without named arguments can appear unapplied in both AEVM and FATE.
// Named argument builtins are:
// Oracle.register
// Oracle.respond
// AENS.preclaim
// AENS.claim
// AENS.transfer
// AENS.revoke
// Oracle.extend
contract UnappliedBuiltins =
entrypoint main() = ()
type o = oracle(int, int)
type t = list(int * string)
type m = map(int, int)
datatype event = Event(int)
stateful function chain_spend() = Chain.spend
function chain_event() = Chain.event
function chain_balance() = Chain.balance
function chain_block_hash() = Chain.block_hash
function call_gas_left() = Call.gas_left
function b_abort() = abort
function b_require() = require
function oracle_query_fee() = Oracle.query_fee
stateful function oracle_query() = Oracle.query : (o, _, _, _, _) => _
function oracle_get_question() = Oracle.get_question : (o, _) => _
function oracle_get_answer() = Oracle.get_answer : (o, _) => _
function oracle_check() = Oracle.check : o => _
function oracle_check_query() = Oracle.check_query : (o, _) => _
function aens_resolve() = AENS.resolve : (_, _) => option(string)
function map_lookup() = Map.lookup : (_, m) => _
function map_lookup_default() = Map.lookup_default : (_, m, _) => _
function map_member() = Map.member : (_, m) => _
function map_size() = Map.size : m => _
function map_delete() = Map.delete : (_, m) => _
function map_from_list() = Map.from_list : _ => m
function map_to_list() = Map.to_list : m => _
function crypto_verify_sig() = Crypto.verify_sig
function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1
function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1
function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1
function crypto_sha3() = Crypto.sha3 : t => _
function crypto_sha256() = Crypto.sha256 : t => _
function crypto_blake2b() = Crypto.blake2b : t => _
function string_sha256() = String.sha256
function string_blake2b() = String.blake2b
function string_length() = String.length
function string_concat() = String.concat
function string_sha3() = String.sha3
function bits_test() = Bits.test
function bits_set() = Bits.set
function bits_clear() = Bits.clear
function bits_union() = Bits.union
function bits_intersection() = Bits.intersection
function bits_difference() = Bits.difference
function bits_sum() = Bits.sum
function int_to_str() = Int.to_str
function address_to_str() = Address.to_str
function address_is_oracle() = Address.is_oracle
function address_is_contract() = Address.is_contract
function address_is_payable() = Address.is_payable
function bytes_to_int() = Bytes.to_int : bytes(10) => int
function bytes_to_str() = Bytes.to_str : bytes(99) => string
@@ -0,0 +1,13 @@
contract UnderscoreNumberLiterals =
entrypoint ints() : list(int) =
[ 1_999_000_000,
19_99_00_00_00,
0xfff_FFF_010 ]
entrypoint bytes() : list(bytes(4)) =
[ #abcd_ef_00,
#01_02_03_04,
#aaaa_FFFF ]
+4
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1
+4
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
entrypoint attr_fail() = ()
entrypoint type_fail(x) = x
entrypoint code_fail(x) = x - 1
+4
View File
@@ -0,0 +1,4 @@
payable contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1
+4
View File
@@ -0,0 +1,4 @@
contract VClose =
entrypoint missing_bracket() =
let x = [1, 2, 3
entrypoint bar() = ()
+3
View File
@@ -0,0 +1,3 @@
contract VSemi =
record missing_brace = { x : int
entrypoint foo() = ()
@@ -0,0 +1,7 @@
@compiler < 1.0
@compiler == 9.9.9
@compiler >= 0.1
@compiler =< 100.0.1
contract Fail =
entrypoint foo() = ()