Compare commits

...

465 Commits

Author SHA1 Message Date
Hans Svensson fd6cde535e Merge pull request #296 from davidyuk/patch-3
docs: Use consistent event definitions between examples
2021-06-24 09:30:08 +02:00
Hans Svensson b25339bb8f Merge pull request #303 from aeternity/radrow-patch-1
Mention `init` quirk with Call.value
2021-04-29 18:51:13 +02:00
Hans Svensson cf793667ca Merge pull request #304 from aeternity/clarify-aesocompiler
Clarify aeso_compiler use
2021-04-29 18:50:32 +02:00
Artur Puzio c54a0cec3d Clarify aeso_compiler use 2021-03-24 10:34:39 +00:00
Radosław Rowicki bc47c25138 Mention init quirk with Call.value 2021-03-22 10:26:34 +01:00
Radosław Rowicki 3b2ce63fa7 Merge pull request #300 from aeternity/erlps-lima
Trampoline in parser
2021-03-08 13:33:34 +01:00
radrow 8b4a1aaf0d Trampoline 2021-03-08 12:45:21 +01:00
Radosław Rowicki c6e7db2381 Merge pull request #299 from aeternity/fix-ets
Fix constraints ordering
2021-03-05 10:42:16 +01:00
radrow 4e60d019ca Fix constraints ordering 2021-02-23 11:05:02 +01:00
Radosław Rowicki b8002029cf Merge pull request #294 from aeternity/mergesort
Upgrade sorting function
2021-02-23 08:58:36 +01:00
Denis Davidyuk 4630f8a09b Use consistent event definitions between examples 2021-02-16 14:52:37 +03:00
radrow 1a14602f36 Upgrade sorting function 2021-02-09 14:18:42 +01:00
Hans Svensson e2ef95d6fd Merge pull request #293 from aeternity/GH-292-desugar_error
Properly handle type errors during desugar
2021-02-05 11:14:47 +01:00
Hans Svensson 22aaeceba8 Properly handle type errors during desugar 2021-01-25 21:28:10 +01:00
Radosław Rowicki f1d95484a5 Merge pull request #288 from aeternity/expose-interface-fix-lima
Fix interface exposure (lima)
2020-10-21 14:01:21 +02:00
radrow 7e65f26211 Fix interface exposure 2020-10-21 12:42:42 +02:00
Radosław Rowicki 0b83422189 Merge pull request #284 from aeternity/hermetization-turnoff
Debug mode to turn off hermetization
2020-10-12 13:17:40 +02:00
radrow 1a5017ce2b Debug mode turns off hermetization
Added tests and fixed bugs
2020-10-09 18:41:30 +02:00
Grzegorz Uriasz 25fa365c29 Merge pull request #280 from aeternity/optionally_generate_aci
Provide the ACI along with the bytecode
2020-09-10 10:05:51 +02:00
Grzegorz Uriasz bb728db51b Provide the ACI along with the bytecode 2020-09-09 18:39:02 +02:00
Grzegorz Uriasz 1fee306daa Merge pull request #279 from aeternity/optimize_calldata_generation
Improve call data encoding times by 35%
2020-09-09 15:50:36 +02:00
Grzegorz Uriasz c4eaf2249a Don't regenerate the AST 2020-09-09 15:33:34 +02:00
Hans Svensson 6c23fd0d41 Merge pull request #278 from aeternity/contains-fun
Added `contains` functions in List and Option. Fixed one type error
2020-08-27 11:25:38 +02:00
radrow 3d73e52d48 Fix tests 2020-08-26 15:56:21 +02:00
radrow 89b3ec3d17 minor optimization 2020-08-26 12:10:24 +02:00
radrow 7e32ef57c2 Added contains functions in List and Option. Fixed one type error catch 2020-08-26 11:56:18 +02:00
Hans Svensson ed5447e430 Merge pull request #273 from aeternity/GH272-singleton_record_calldata_decode
Fix singleton record calldata decode + test
2020-08-11 07:58:36 +02:00
Hans Svensson db4de5d926 Fix singleton record calldata decode + test 2020-08-10 16:37:09 +02:00
Radosław Rowicki 6b60fde2df Merge pull request #267 from aeternity/option-fix
Fixed `force` function
2020-06-18 15:05:55 +02:00
Radosław Rowicki dd8eea0d55 Merge pull request #261 from aeternity/fixes
Ban empty contracts, ban function blocks with mismatched declarations
2020-06-07 18:01:03 +02:00
Radosław Rowicki eb71abc665 Fixed force function 2020-06-06 14:36:46 +02:00
Hans Svensson eff1ad4688 Merge pull request #265 from aeternity/improve_docs
Make the network id an explicit part of the signature material
2020-06-05 13:00:42 +02:00
Hans Svensson cb2588fae2 Make the network id an explicit part of the signature material 2020-05-29 08:47:01 +02:00
Hans Svensson 08261a319b Merge pull request #264 from aeternity/update_readme
Make README less outdated
2020-05-29 08:45:23 +02:00
Hans Svensson f21717a9c0 Make README less outdated 2020-05-29 08:35:19 +02:00
Hans Svensson 9753f90034 Merge pull request #262 from aeternity/fix_types
Fix AENS types + whitespace
2020-05-27 08:10:18 +02:00
Hans Svensson 8f240a7ddf Fix AENS types + whitespace 2020-05-27 08:06:06 +02:00
radrow 54e43764ca Ban empty contracts, ban function blocks with mismatched declarations 2020-05-15 19:09:08 +02:00
Radosław Rowicki 962ddf5303 Version push – 4.3.0 (#255)
* Version push – 4.3.0

.

* Fixed ordering of diffs in CHANGELOG
2020-04-02 15:10:53 +02:00
Radosław Rowicki 85b151aa65 Prepare 4.3.0 (#254) 2020-04-02 13:59:22 +02:00
Radosław Rowicki 93341dc13b Prepared for REPL usage (#218)
* Prepared for REPL usage

Exposed expr parsing

ets init in constant

Exposing and fixing

exposed expr

This will be squashed either

Expose letdef

Error handling

exposed autoimport

remove unnecessary changes

Fix types

Parser update

Expose body parser

remove map_get warning

make dialyzer happy

* Formatting

Co-Authored-By: Hans Svensson <hanssv@gmail.com>

Co-authored-by: Hans Svensson <hanssv@gmail.com>
2020-04-02 12:29:14 +02:00
Radosław Rowicki 98036eff65 Update TOC in sophia.md (#245) 2020-04-01 13:11:02 +02:00
Radosław Rowicki dc977f7354 Fixed example (#249) 2020-03-31 13:14:13 +02:00
Radosław Rowicki 4f554acee6 Fix error messages for some illegal constructions, fix absolute path includes (#251)
* Updated tests, banned type decls and toplevel letvals

* Properly ban nested contracts

* Fix including by path

* Fix error message test

* Fix prettpr attr display. Make dialyzer happy

* More tests

* Fixed type printing

* Updated docs
2020-03-30 14:52:16 +02:00
Radosław Rowicki 48b52cb501 Enchanted Frac library a bit (#253) 2020-03-29 17:14:01 +02:00
Ulf Norell 515838e2f9 Handle negative numbers in aci (#247)
* Handle negative numbers in aci

Fixes aeternity/aesophia_http#59

* Updated CHANGELOG

Updated CHANGELOG1

Co-authored-by: radrow <radrowicki@gmail.com>
2020-03-23 18:11:00 +01:00
Radosław Rowicki 83e03f3013 Added documentation (#239)
* Added documentation

* Update readme

* Update readme

* Format fix

* Events

* Stdlib mention

* Frac doc

* Frac doc comparison warning

* Typos

* Format fix, TOC added

* Fixed link

* Update editor message

* Split TOC

* Moved out AEVM ABI

* Minor format

Co-Authored-By: Hans Svensson <hanssv@gmail.com>

* Typo

Co-Authored-By: Hans Svensson <hanssv@gmail.com>

* Grammar

Co-Authored-By: Hans Svensson <hanssv@gmail.com>

* Language

Co-authored-by: Hans Svensson <hanssv@gmail.com>
2020-03-10 12:39:39 +01:00
Radosław Rowicki d7fa4d65ec More comments in stdlib (#237) 2020-02-25 12:56:51 +01:00
Radosław Rowicki bd7ed2ef8c Instant unification error on arguments count mismatch (#225)
* Instant unification error on arguments count mismatch

* add testcase

* Add newline
2020-02-21 10:28:55 +01:00
Radosław Rowicki 2bf65cfd98 Add Frac (#222)
Fix bugs in Frac

Added optimizer
2020-02-13 11:02:47 +01:00
Hans Svensson efd45df820 Merge pull request #212 from aeternity/GH-211-prepare_release_4_2_0
Prepare release 4.2.0
2020-01-15 11:58:47 +01:00
Hans Svensson a6f51d23f3 Bump version to 4.2.0 and fix CHANGELOG 2020-01-15 11:39:25 +01:00
Ulf Norell 4d4a14a9ab GH-196 pattern matching lhs (#210)
* Allow block with separate type signature and definition of a function

For instance,
```
function
  add : (int, int) => int
  add(x, y) = x + y
```

cc #196

* Allow pattern matching in left-hand sides

* Changelog

* Fix type spec

* partial case-on-constructor

* Changelog for pattern-matching lets
2020-01-15 09:41:03 +01:00
Ulf Norell f7abaf07fa Add list comprehension match to test case 2019-12-16 17:04:49 +01:00
Ulf Norell d019e44924 Compile values to immediates when possible 2019-12-16 17:04:49 +01:00
Ulf Norell ad54134961 Parse negative literal patterns 2019-12-16 17:04:49 +01:00
Ulf Norell b51a79b5e1 Allow patterns in lets and list comprehension binds 2019-12-16 17:04:49 +01:00
Ulf Norell d844c4d276 Fix missing type annotation in list comprehension body 2019-12-12 09:39:13 +01:00
Ulf Norell 64e2fff91a Handle list comprehensions in pretty printer 2019-12-12 09:39:13 +01:00
Ulf Norell d4f291f252 Handle qualified constructors in patterns 2019-12-12 09:34:26 +01:00
Ulf Norell b9f585ebaf Merge pull request #205 from aeternity/fate-flatten-store
FATE backend optimisations
2019-12-12 09:22:37 +01:00
Ulf Norell 954af13f59 Fix debug printing of store registers 2019-12-12 09:14:34 +01:00
Ulf Norell 2e4558b3b4 Changelog 2019-12-10 12:57:52 +01:00
Ulf Norell a403a9d227 Unbox singleton tuples and records 2019-12-10 12:24:05 +01:00
Ulf Norell c7b846cbfe Merge pull request #190 from aeternity/GH-189-parse-error-crash
Fix parse errors causing crashes instead of nice errors
2019-12-09 10:27:20 +01:00
Ulf Norell bf5e2e2443 Fix parse errors causing crashes instead of nice errors 2019-12-09 08:45:55 +01:00
Ulf Norell 46a30b118f Get rid of unnecessary return instruction after tail-call 2019-11-26 13:33:11 +01:00
Ulf Norell bb1a45c557 Improve case-on-constructor optimisation 2019-11-26 13:10:58 +01:00
Ulf Norell 0a22c7a34a More let-floating 2019-11-26 13:10:58 +01:00
Ulf Norell c8153f94a6 More aggressive freshening to avoid shadowing issues 2019-11-26 13:10:58 +01:00
Ulf Norell 63d51baaa3 Dialyzer issues 2019-11-26 13:10:58 +01:00
Ulf Norell cb045b0256 whitespace 2019-11-26 13:10:58 +01:00
Ulf Norell c84064da7f Inline local functions and simplify case-on-constructor 2019-11-26 13:10:58 +01:00
Ulf Norell ad88797cef Proper handling of lets in term_to_fate 2019-11-26 13:10:58 +01:00
Ulf Norell 6c3932b10c Flattened state layout
... with necessary optimizations.
2019-11-26 13:10:56 +01:00
Ulf Norell 8d7c637241 Don't confuse variables and store registers in fate asm generation 2019-11-26 13:10:04 +01:00
Ulf Norell a8119f1219 Track state layout
... but only default layout still.
2019-11-26 13:10:04 +01:00
Ulf Norell d0fdd06d66 Change get_state and set_state fcode primitives to take a register 2019-11-26 13:10:04 +01:00
Ulf Norell 99ecda4b7b Fix warnings in test suites 2019-11-26 13:10:04 +01:00
Ulf Norell e645a8d034 Optimize before lambda lifting
(lambdas are either in dead code or not dead, so dead code elimination won't be affected)
2019-11-26 13:10:04 +01:00
Ulf Norell 499e2f8200 Handle records and type aliases correctly in fcode 2019-11-26 13:10:04 +01:00
Ulf Norell 5465b74ac9 Allow specifying store register in FATE backend 2019-11-26 13:10:04 +01:00
Hans Svensson 6ca63e4b40 Merge pull request #184 from aeternity/GH-181-prepare-4.1.0
Bump version to 4.1.0
2019-11-26 09:02:56 +01:00
Ulf Norell 08b6148223 Bump version to 4.1.0 2019-11-26 09:02:26 +01:00
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
Thomas Arts a50730155f Merge pull request #138 from aeternity/aens-at-full-node-ver-ta
Compile name fee in contracts
2019-09-02 11:07:59 +02:00
Thomas Arts e9f717a17b Update src/aeso_ast_to_icode.erl
Co-Authored-By: Ulf Norell <ulf.norell@gmail.com>
2019-09-02 10:21:35 +02:00
Ulf Norell 97ff1aac23 Merge pull request #136 from radrow/stdlib-extensions
Updated some functions, renamed some stuff, added from_to IN STDLIB
2019-09-02 09:56:56 +02:00
sennui 1ee5a57924 change aebytecode version, aeserialization and add enacl 2019-09-02 08:54:38 +02:00
Thomas Arts cf91a27fb2 Keep sign last 2019-09-01 10:58:49 +02:00
sennui 83d06977f9 add extra argument to claim for bidding 2019-09-01 10:58:49 +02:00
Ulf Norell 41e59506ba Merge pull request #137 from aeternity/polymorpism-checks
Polymorphism checks
2019-08-30 15:48:34 +02:00
Ulf Norell 062309e578 Type variables mentioned in local functions should not be flexible
(cc #112)
2019-08-30 14:22:31 +02:00
Radosław Rowicki 6408969cd3 Remove from_to_ 2019-08-30 14:06:46 +02:00
Radosław Rowicki 71a556ce81 nth update 2019-08-30 13:46:02 +02:00
Radosław Rowicki 256aadd575 [......]
Co-Authored-By: Ulf Norell <ulf.norell@gmail.com>
2019-08-30 13:44:26 +02:00
Ulf Norell f27ba528d8 aebytecode commit 2019-08-30 11:21:26 +02:00
Ulf Norell 6fd39d4cb1 Add checks for polymorphic/higher order oracles and higher order entrypoints (AEVM) 2019-08-30 11:18:20 +02:00
Ulf Norell 1ce95b32ac Add checks for polymorphic/higher order oracles and higher order entrypoints (FATE) 2019-08-30 11:18:20 +02:00
radrow 076d635dbe Fix errors 2019-08-29 15:32:10 +02:00
Radosław Rowicki 6d87960147 Merge pull request #135 from aeternity/radrow-patch-3
Remove find_all from stdlib
2019-08-29 15:25:22 +02:00
radrow 1d962f2001 Updated some functions, renamed, added from_to 2019-08-29 13:41:04 +02:00
Radosław Rowicki cce243e513 Remove find_all from stdlib
It was just a duplicated `filter`
2019-08-28 14:17:30 +02:00
Ulf Norell 60528e9128 Merge pull request #134 from aeternity/unit-to-typerep
Add missing case for builtin unit type
2019-08-28 10:19:10 +02:00
Ulf Norell 80075a9d36 Add missing case for builtin unit type 2019-08-28 09:45:25 +02:00
Ulf Norell d26fcace41 Merge pull request #133 from aeternity/stdlib-overhaul
Stdlib overhaul
2019-08-28 08:38:40 +02:00
Ulf Norell c51531f620 please dialyzer 2019-08-27 18:04:32 +02:00
Ulf Norell 3b2daf8cd6 Better errors when using old tuple type syntax 2019-08-27 15:08:56 +02:00
Ulf Norell 3ff93c5c89 Fix bug in include chasing
... making it possible for the same file to be included multiple times
2019-08-27 14:29:24 +02:00
Ulf Norell 850221aaf3 Remove no_implicit_stdlib option 2019-08-27 14:10:40 +02:00
Ulf Norell 3f1c23ace3 Use .. in list comprehension test 2019-08-27 14:00:23 +02:00
Ulf Norell 0efbcf302c Fix roundtrip test to ignore ListInternal 2019-08-27 14:00:02 +02:00
Ulf Norell 7705138ab2 auto-import ListInternal when using list comprehensions or [a..b] 2019-08-27 13:59:36 +02:00
Ulf Norell 5f733e01dd Implement [a..b] 2019-08-27 13:59:01 +02:00
Ulf Norell 79a928e530 Fix bad type specs 2019-08-27 13:56:02 +02:00
Ulf Norell d23208c191 Fix bugs in dependency analysis 2019-08-27 13:55:45 +02:00
Ulf Norell e7d3a5b9f2 Put flat_map in ListInternal.aes 2019-08-27 11:33:43 +02:00
Ulf Norell 02af75aa34 Move stdlib code to priv dir and don't do any implicit includes 2019-08-27 11:33:29 +02:00
Ulf Norell 9eed18f812 Merge pull request #132 from aeternity/fate-compiler-optimizations
Fate compiler optimizations
2019-08-26 08:25:40 +02:00
Ulf Norell 07cf162703 Fix performance problem in FATE optimiser caused by debug printing 2019-08-23 10:07:43 +02:00
Hans Svensson d4c6187739 Merge pull request #130 from aeternity/PT-168026424-prepare_sophia_4_0_RC1
Preparing 4.0.0-rc1
2019-08-22 16:02:06 +02:00
Hans Svensson 2620aa64b4 Add some no_implicit_stdlib for now 2019-08-22 15:21:41 +02:00
Ulf Norell 20064b72fa Compile tail-calls to current function to jumps 2019-08-22 14:50:15 +02:00
Ulf Norell a942561907 Improved optimizations of FATE code 2019-08-22 14:49:48 +02:00
Ulf Norell a9617a025f Merge pull request #129 from aeternity/deadcode-elim
Deadcode elimination for FATE
2019-08-22 14:25:31 +02:00
Hans Svensson bde76c8580 Preparing 4.0.0-rc1 2019-08-22 13:30:00 +02:00
Ulf Norell e94b5379ed Deadcode elimination pass 2019-08-21 12:19:01 +02:00
Ulf Norell cbc8909954 Add default init function in fcode pass instead of in assembler 2019-08-21 11:51:36 +02:00
Ulf Norell cfd036b199 Test deadcode elimination for FATE backend 2019-08-21 11:51:36 +02:00
Ulf Norell bbf043f4ee Merge pull request #126 from radrow/listcompfixes
Fixed comprehension dependencies
2019-08-21 11:51:16 +02:00
Ulf Norell ba41ab457d Merge pull request #127 from aeternity/radrow-patch-1
Fixed intersperse in stdlib
2019-08-21 11:51:04 +02:00
Hans Svensson 49634a6024 Merge pull request #128 from aeternity/PT-167996886-a_proper_ecverify
PT-167996886 A proper ecverify
2019-08-21 11:19:48 +02:00
Hans Svensson 2dbef80249 aebytecode commit 2019-08-21 11:09:44 +02:00
Hans Svensson ebdd38c505 Change ecverify into verify_sig and then add an actual ecverify 2019-08-21 09:29:40 +02:00
Radosław Rowicki 5dbca47d34 Fixed intersperse in stdlib 2019-08-20 20:16:52 +02:00
radrow 79d491e4a8 Fixed comprehension dependencies 2019-08-20 18:44:47 +02:00
Hans Svensson 73b9a54172 Merge pull request #125 from aeternity/PT-162578406-payable_modifier
PT-162578406 Add payable modifier
2019-08-19 16:27:18 +02:00
Hans Svensson bb0c3b54df ACI should also track payable 2019-08-19 08:57:31 +02:00
Hans Svensson d0485304b6 Please dialyzer 2019-08-19 08:57:31 +02:00
Hans Svensson 86aeaa40ef Set aebytecode commit 2019-08-19 08:57:31 +02:00
Hans Svensson e9505e240f Add Address.is_payable(address) 2019-08-19 08:57:31 +02:00
Hans Svensson f27d37d624 Add payable modifier for contracts and entrypoints 2019-08-19 08:57:31 +02:00
Ulf Norell e566186800 Merge pull request #123 from aeternity/PT-167221635-remote-type-check
PT-167221635 remote type check
2019-08-16 09:22:30 +02:00
Ulf Norell 956b78fb01 aebytecode commit 2019-08-16 09:14:51 +02:00
Ulf Norell 522d977be9 Remote calls now take typerep arguments 2019-08-16 09:12:04 +02:00
Tino Breddin dd26649f7d [PT-167805291] Add opcode for ecrecover (#122)
* Add opcode for ecrecover

* Update aebytecode

* Extend signature bytes type used for ecrecover

* Add ecrecover to changelog

* Add some type specs

* Please dialyzer
2019-08-14 21:02:46 +02:00
Radosław Rowicki b669d2df1e Added list comprehensions and standard List, Option, Func, Pair, and Triple library (#105)
* Added standard List library and list comprehensions

Added List library.

Flatmaps WIP

Fixed dependency in flat_map

fcode generation

Updated tests to use custom list lib

Added comprehension test

Added stdlib

sanity

Test

* Extended stdlib for lists. Added error message for redefinition of stdlibx

* Fixed type template

* Improved stdlib

* More functions

* Fixed cyclic includes

* Refixed imports and added few tests

* Added fail test

* Undelete removed type spec

* Remove typo

* Fix iter function

* Fixed typo

* Added if guards and let statements in list comp

* Added more fail tests

* Option stliv

* 2 and 3 tuple stdlib

* Updated stdlib to new syntax. Added recursor and changed all/any functions

* Fixed performance issues. Changed include management

* Fixed hash type
2019-08-14 13:53:58 +02:00
Ulf Norell 69ad8ce9bc Merge pull request #121 from aeternity/PT-166788647-fate-efficient-maps
Don't generate remote tail calls
2019-08-14 08:58:54 +02:00
Ulf Norell 3877174acb aebytecode commit 2019-08-13 15:53:49 +02:00
Ulf Norell 448adb8890 Don't generate remote tail calls 2019-08-13 15:52:47 +02:00
Hans Svensson 864a94c59e Merge pull request #119 from radrow/patch-2
Mention tuple syntax change in changelog
2019-08-12 16:59:58 +02:00
Radosław Rowicki 518ae8e659 Mention tuple syntax change in changelog 2019-08-12 16:42:34 +02:00
Thomas Arts df12f6af91 Merge pull request #118 from aeternity/PT-167126818
Pt 167126818
2019-08-06 15:30:47 +02:00
Thomas Arts 6aed5dfacb Upgrade to newer aebytecode version 2019-08-06 13:20:03 +02:00
Thomas Arts cecc977898 Compiler returns abi_version 2019-08-06 13:11:31 +02:00
Ulf Norell 74933b0616 Merge pull request #117 from aeternity/PT-167701356-split-store
PT-167701356 split store
2019-08-06 12:27:18 +02:00
Hans Svensson 6a27c4a68b Merge pull request #116 from aeternity/another_no_code
to_sophia_value should also use no_code option
2019-08-05 15:21:27 +02:00
Hans Svensson d526e55c26 to_sophia_value should also use no_code option 2019-08-05 15:19:22 +02:00
Hans Svensson 6f7f5fa13c Merge pull request #115 from aeternity/PT-166731397-add_compiler_backend
PT-166731397 Add compiler backend
2019-08-05 15:00:32 +02:00
Ulf Norell 2d6381dc6f Generate INIT function which writes the state instead of returning it 2019-08-05 14:36:59 +02:00
Hans Svensson 3663b4e5d0 Add no_code option to aeso_compile (for encode/decode calldata) 2019-08-05 14:33:53 +02:00
Hans Svensson 4478fee6e6 Add ACI calldata test 2019-08-05 14:33:53 +02:00
Ulf Norell 79ae92a068 Add missing error message 2019-08-05 14:01:35 +02:00
Ulf Norell eb968d3cb9 Merge pull request #114 from aeternity/latest-aebytecode
Update to latest aebytecode
2019-08-05 11:54:01 +02:00
Ulf Norell 054a5a4867 Update to latest aebytecode 2019-08-05 11:51:10 +02:00
Ulf Norell e198dd8311 Merge pull request #113 from aeternity/test-fixes
Update test case
2019-08-05 09:31:31 +02:00
Ulf Norell 46a996ead8 Update test case 2019-08-05 09:20:09 +02:00
Hans Svensson 2bf6ab7655 Merge pull request #110 from radrow/patch-1
Covered qid case in ast_typerep
2019-08-05 09:15:12 +02:00
Ulf Norell 5ff7aa5821 Merge pull request #109 from radrow/tuple-type
Change tuple typing syntax
2019-08-05 09:13:30 +02:00
Radosław Rowicki 045df292be Fixed pretty printing and pattern split (#111) 2019-07-29 14:27:01 +03:00
Radosław Rowicki c97eb99921 Fixed double parens in ACI pp
Co-Authored-By: Ulf Norell <ulf.norell@gmail.com>
2019-07-29 11:01:38 +02:00
Radosław Rowicki 4c78ab3aee Covered qid case in ast_typerep
Because `qid` was not accepted the following code 

```
contract Test = 
   datatype myOption('a) = MyNone | MySome('a)
   entrypoint optionFn(v: myOption(string)): myOption(string) = v
```
Used to lead to `function_clause` error.

Bug copyright (c) @nduchak
2019-07-26 15:31:06 +02:00
radrow 5ff983b0b3 Updated tests 2019-07-22 13:56:45 +02:00
Hans Svensson 4bf382a997 Merge pull request #107 from radrow/master
Changed 'require' function return type to Unit
2019-07-20 21:05:40 +02:00
radrow 4c72045a86 Changed tuple type parsing rule 2019-07-20 19:00:53 +02:00
radrow 7daf218b2a Changed 'require' function return type to Unit 2019-07-19 18:10:54 +02:00
radrow 973850e6a6 Fix pat split 2019-07-11 18:28:50 +02:00
Tobias Lindahl 19948c6aad Merge pull request #104 from aeternity/PT-166868668-decode-fate-revert
Properly decode revert strings for fate as well
2019-07-09 16:52:35 +02:00
Tobias Lindahl c4660fe0cf Properly decode revert strings for fate as well 2019-07-09 16:24:56 +02:00
Tobias Lindahl e326908623 Merge pull request #103 from aeternity/PT-166868668-fate-abort
Use EXIT for internal errors to burn all gas
2019-07-09 15:47:46 +02:00
Tobias Lindahl 9be528a579 Use EXIT for internal errors to burn all gas 2019-07-09 14:36:09 +02:00
Tobias Lindahl f67d7354a2 Merge pull request #102 from aeternity/PT-166927306-names-as-strings
Use a name string rather than a name hash in transfer and revoke
2019-07-01 10:55:38 +02:00
Tobias Lindahl 6f873e45b8 Use a name string rather than a name hash in transfer and revoke 2019-07-01 07:42:08 +02:00
Hans Svensson 8c3b675b0d Merge pull request #101 from aeternity/release-3.2
Prepare version 3.2.0
2019-06-28 12:24:44 +02:00
Hans Svensson 41011d15cc Prepare 3.2.0 2019-06-28 11:51:51 +02:00
Ulf Norell 9e0f84ec67 Update changelog 2019-06-28 11:51:51 +02:00
Ulf Norell 8b4f471d42 Merge pull request #100 from aeternity/private-function-revamp
Private function revamp
2019-06-28 10:55:10 +02:00
Ulf Norell dc5fd74934 Fix include path not being added if giving explicit options 2019-06-28 10:28:16 +02:00
Ulf Norell 6a59e455ce Update tests for entrypoints 2019-06-28 09:42:28 +02:00
Ulf Norell 85408a12a2 Update ACI to new entrypoint declarations
also make ACI understand namespaces
2019-06-28 09:36:07 +02:00
Ulf Norell 79137e058e Revamp private/public functions
Problem: having public as the default makes it very easy to accidentally
export local function by forgetting the `private` modifier.

Solution: functions are private by default and must be declared as `entrypoint`s
to be exported. So `entrypoint foo() = ...` instead of `function foo() = ...`.

We still accept the `private` modifier although it is redundant.
2019-06-28 09:36:07 +02:00
Hans Svensson dd5fc17554 Merge pull request #99 from aeternity/PT-166897066-run_http_contracts_on_fate
Pt 166897066 run http contracts on fate
2019-06-28 07:49:04 +02:00
Hans Svensson a617a6469d Change Chain.block_hash to return option(hash) 2019-06-27 14:19:39 +02:00
Hans Svensson 502a4e6464 Fix to_sophia_value for FATE backend 2019-06-27 10:42:31 +02:00
Hans Svensson c647a2cd34 Merge pull request #98 from aeternity/namespace-fix
Namespace fix
2019-06-27 09:34:48 +02:00
Ulf Norell 292d1aa65b Fix namespace bug
Don't unfold record types until all contracts/namespaces have been checked
2019-06-27 09:15:27 +02:00
Ulf Norell 259bae1720 Test case for another name space bug 2019-06-27 09:15:27 +02:00
Hans Svensson a47fa59f5b Merge pull request #97 from aeternity/PT-166899532-static_hashing_for_events
Pre-compute and switch to Blake2b for event name hash
2019-06-26 14:22:43 +02:00
Hans Svensson 2bf5e59e2b Pre-compute and switch to Blake2b for event name hash 2019-06-26 13:34:57 +02:00
Tobias Lindahl 02ba4b265b Merge pull request #96 from aeternity/PT-166233700-fate-nameservice
Implement aens instructions for fate
2019-06-26 13:33:47 +02:00
Tobias Lindahl c26ace6c2c Implement aens instructions for fate 2019-06-26 13:24:48 +02:00
Hans Svensson cfb1605a76 Merge pull request #95 from aeternity/PT-166233670-fate-events
PT-166233670 FATE events
2019-06-26 08:48:44 +02:00
Ulf Norell 20085301ef aebytecode commit 2019-06-25 19:58:14 +02:00
Ulf Norell 3c8d9561a0 More thorough test for different event types 2019-06-25 19:58:14 +02:00
Ulf Norell 523d6b03a9 Allow bytes(N) as indices if N =< 32 and payload if N > 32 2019-06-25 19:58:14 +02:00
Ulf Norell 961f557215 Events now compile to FATE 2019-06-25 19:58:14 +02:00
Ulf Norell 0cf6a52b26 Compile events to FATE 2019-06-25 19:58:14 +02:00
Ulf Norell a3efaf71a7 Compile oracle check functions in FATE backend 2019-06-25 16:27:48 +02:00
Ulf Norell c7a8a4af22 Merge pull request #94 from aeternity/bytes-to-x
Add Bytes.to_int and Bytes.to_str
2019-06-24 14:56:20 +02:00
Ulf Norell 0ef7c59771 Fix issues discovered by dialyzer 2019-06-24 14:29:20 +02:00
Ulf Norell 894ae19435 aebytecode commit 2019-06-24 14:25:08 +02:00
Ulf Norell cee8a4ecf3 Compile bytes_to_X in AEVM backend 2019-06-24 14:09:20 +02:00
Ulf Norell bde5a3c071 Compile bytes_to_X in FATE backend 2019-06-24 11:44:23 +02:00
Ulf Norell 6612c29758 Type check Bytes.to_X builtins 2019-06-24 11:44:07 +02:00
Ulf Norell 2e0c44862c Merge pull request #93 from aeternity/PT-166788837-bytes
PT-166788837 bytes
2019-06-24 07:18:33 +02:00
Ulf Norell 7fa98892a8 Fix compiler crash on missing let body 2019-06-21 14:16:26 +02:00
Ulf Norell d38367e023 Fix bug in type checker 2019-06-20 16:02:19 +02:00
Ulf Norell 592869bf75 aebytecode commit 2019-06-20 15:39:55 +02:00
Ulf Norell 4f9d4e5c07 Update compiler for bytes 2019-06-20 14:36:08 +02:00
Thomas Arts 20aeade545 Merge pull request #91 from aeternity/PT-166696064-add-calldata-decode
Pt 166696064 add calldata decode
2019-06-20 14:26:44 +02:00
Thomas Arts c745827c53 Update src/aeso_vm_decode.erl
Co-Authored-By: Hans Svensson <hanssv@gmail.com>
2019-06-20 14:19:39 +02:00
Thomas Arts 389e931674 Unit type instead of bool 2019-06-20 13:13:28 +02:00
Thomas Arts d571993405 Fix type spec 2019-06-20 13:13:25 +02:00
Thomas Arts ff11943576 Add test contract 2019-06-20 13:10:08 +02:00
Thomas Arts 66528c8a6a Move translate_vm to aeso_vm_decode 2019-06-20 13:10:08 +02:00
Thomas Arts 46c746da1c Refactor string_to_code 2019-06-20 13:10:08 +02:00
Thomas Arts d3ce5010d0 Update tests 2019-06-20 13:10:08 +02:00
Thomas Arts 1c346af85e whitespaces 2019-06-20 13:10:08 +02:00
Tobias Lindahl 69fa03ca9f Merge pull request #92 from aeternity/PT-166786424-check-oracle-types
Add type information to oracle instructions
2019-06-20 10:31:04 +02:00
Tobias Lindahl 03c6ae1c74 Add type information to oracle instructions 2019-06-20 09:23:09 +02:00
Tobias Lindahl 990df562e0 Handle oracle operation in FATE (#90)
* Handle oracle operation in FATE

Keep oracle type information on fcode level

Introduce typereps as values

Handle oracle registration

Handle oracle query object and oracle_query op

Handle oracle get question

Handle oracle query fee

Handle oracle get answer

Handle oracle respond

Handle oracle extend

* Address review comment
2019-06-18 14:32:08 +02:00
Hans Svensson fc82b1646c Merge pull request #89 from aeternity/PT-164629640-auth_tx_hash_for_fatge
Pt 164629640 auth tx hash for FATE
2019-06-18 08:51:13 +02:00
Hans Svensson 81f277127d Handle signature, hash and map in create_calldata for FATE 2019-06-14 16:08:59 +02:00
Hans Svensson 11dc632927 Add handling of auth_tx_hash 2019-06-14 13:14:24 +02:00
Thomas Arts e81439779c Merge pull request #87 from aeternity/PT-166636909-avoid-calldata-collision
Pt 166636909 avoid calldata collision
2019-06-14 13:09:25 +02:00
Thomas Arts e5c64a5fad Add lit_to_fate and fix cons translation 2019-06-14 12:41:36 +02:00
Thomas Arts a34558412d Arguments are in the body of the call, use term_to_fate before serializing them 2019-06-14 12:41:21 +02:00
Thomas Arts 95c41b8eee Avoid hash collisions in calldata creation 2019-06-14 12:41:21 +02:00
Thomas Arts 3a6337d8ca Testing that fate calldata is immune to existing __call in contract 2019-06-14 12:41:21 +02:00
Luca Favatella 5a7d352b11 Merge pull request #84 from aeternity/PT-166546345-upgrade-rebar3
Upgrade rebar3 to latest stable one
2019-06-14 11:18:46 +01:00
Ulf Norell 9eab558642 Merge pull request #88 from aeternity/PT-166694614-require
Add require builtin
2019-06-14 11:09:03 +02:00
Ulf Norell 17a1fd8095 Add require builtin 2019-06-14 10:27:07 +02:00
Thomas Arts 5628cf90b8 Merge pull request #86 from aeternity/PT-166602172-create-calldata-fate
Pt 166602172 create calldata fate
2019-06-11 15:46:30 +02:00
Thomas Arts 46963a8326 Update aeso_compiler 2019-06-11 15:25:01 +02:00
Thomas Arts 5513c4de1b New version of aebytecode 2019-06-11 15:24:58 +02:00
Hans Svensson ab6d7fbf56 Merge pull request #85 from aeternity/fate-crypto-ops
Fate crypto ops
2019-06-11 12:08:10 +02:00
Hans Svensson a14fa93920 Add contract_to_address to FATE 2019-06-11 12:05:58 +02:00
Luca Favatella c411f11fd0 Upgrade rebar3 to latest stable one
... without deviations from upstream.

Fetched using:
```
curl -sSLO https://github.com/aeternity/rebar3/releases/download/3.11.1-aeternity.1/rebar3
```

That tag is commit c5eecfc.
2019-06-11 09:44:43 +01:00
Hans Svensson 3e2281a834 Make fate-backend also return a Map 2019-06-10 15:13:34 +02:00
Ulf Norell 7b5db76c13 aebytecode commit 2019-06-05 18:20:20 +02:00
Ulf Norell 95f1262b21 Missing compiler cases for crypto ops 2019-06-05 18:19:53 +02:00
Ulf Norell 2bbb16654f Compile crypto ops 2019-06-05 14:16:33 +02:00
Ulf Norell 093a5ff766 Merge pull request #83 from aeternity/PT-166407568-polymoprhic-functions
PT-166407568 polymorphic functions in FATE
2019-06-05 12:58:04 +02:00
Ulf Norell 66c392e8af Make dialyzer bugger off 2019-06-05 12:16:19 +02:00
Ulf Norell a64b72d04b aebytecode commit 2019-06-05 12:11:49 +02:00
Ulf Norell 6236b33115 Crash with a nicer error on r.address
Fate doesn't have a contract to address instruction yet
2019-06-05 12:11:49 +02:00
Ulf Norell 3b352d8093 Generate type variables for polymorphic functions 2019-06-03 13:41:31 +02:00
Ulf Norell ed3ed6ded6 Check (de)serialize roundtrip in fate compiler tests 2019-06-03 13:41:07 +02:00
Ulf Norell a60d04d794 Fix minor code generation issues 2019-06-03 13:41:07 +02:00
Ulf Norell 10d9c62d53 Skeleton for inliner 2019-06-03 13:41:07 +02:00
Tobias Lindahl e3950f6c1d Merge pull request #82 from aeternity/fix-map-update-and-delete-for-fate
Fix arg order for map_delete and renaming of vars for map_update
2019-06-03 13:26:27 +02:00
Tobias Lindahl 344ec74eaa Fix arg order for map_delete and renaming of vars for map_update 2019-06-03 13:20:03 +02:00
Hans Svensson 4b0a3e53d6 Merge pull request #81 from aeternity/PT-166406414-v3.1.0
Prepare v3.1.0
2019-06-03 11:14:20 +02:00
Hans Svensson 07c445082a Prepare v3.1.0 2019-06-03 10:58:28 +02:00
Ulf Norell 4f612650e3 Merge pull request #80 from aeternity/fix-basic-block-bug
Fix bug in basic block generation
2019-06-03 10:35:18 +02:00
Ulf Norell 80ed24a4f6 Fix bug in basic block generation
(JUMPIF ends a basic block)
2019-06-03 10:30:25 +02:00
Hans Svensson 05256eeb60 Merge pull request #79 from aeternity/negative_literals_in_calldata
Allow negative literals in calldata and result
2019-06-03 09:21:22 +02:00
Ulf Norell 7592390059 Merge pull request #78 from radrow/builtins
Popularized use of builtin_call function
2019-06-03 09:19:03 +02:00
Hans Svensson bb4ef61a50 Allow negative literals in calldata and result 2019-06-03 09:08:53 +02:00
radrow bb5a710626 Popularized use of builtin_call function 2019-05-31 12:21:48 +02:00
Hans Svensson 758fecbb9b Merge pull request #75 from aeternity/fix_aci
Restructure and improve ACI
2019-05-31 11:33:07 +02:00
Ulf Norell b1e882b115 Merge pull request #77 from aeternity/fate-compiler-improvements
Update to new TUPLE instruction
2019-05-28 19:00:57 +02:00
Ulf Norell bea524635b Add backend argument (aevm | fate) to aeso_compiler options
and test fate backend on (most) compilable contracts
2019-05-28 16:40:54 +02:00
Ulf Norell e44a890292 Fix bug in compilation of Map.lookup 2019-05-28 16:19:39 +02:00
Hans Svensson d3a13eafed A record should be _one_ object 2019-05-28 14:25:37 +02:00
Ulf Norell 0532c54ca0 Pretty print state variables 2019-05-28 14:18:38 +02:00
Ulf Norell 02d0025fd7 Don't use POP 2019-05-28 14:18:38 +02:00
Ulf Norell 0409a658b0 Update to new TUPLE instruction
... and minor fixes
2019-05-28 14:18:38 +02:00
Ulf Norell c045e5d653 Merge pull request #76 from aeternity/map-update-bug
Fix bug when compiling map updates with default values
2019-05-28 14:03:00 +02:00
Ulf Norell a95913e793 Fix bug when compiling map updates with default values 2019-05-28 13:47:22 +02:00
Hans Svensson ec678878fa Update aeso_aci.md 2019-05-28 13:26:22 +02:00
Hans Svensson 4b0837dc59 Leave state/event blank if not present 2019-05-28 13:07:24 +02:00
Tobias Lindahl ed96dc1d42 Merge pull request #74 from aeternity/PT-166282300-add-missing-fate-instructions
Add missing instructions for FATE
2019-05-28 13:05:59 +02:00
Tobias Lindahl 60d9581fae Add missing instructions for FATE 2019-05-28 11:35:24 +02:00
Hans Svensson 0ded431df8 Fix interface and use atoms instead of binaries 2019-05-28 11:18:21 +02:00
Hans Svensson e7419b79fd Put state and event types at the top level 2019-05-28 11:08:36 +02:00
Hans Svensson c60999edf0 Refactor aeso_aci with dont_unfold, etc. 2019-05-28 11:08:36 +02:00
Hans Svensson ea17dae93e Silence compiler warning 2019-05-28 11:08:36 +02:00
Hans Svensson 8a16bd4fa1 Add dont_unfold option to type inference function 2019-05-28 11:08:36 +02:00
Hans Svensson 1ed40f1cca Pretty print state variables 'a instead of '1 2019-05-28 11:08:36 +02:00
Hans Svensson 5c98317a5a Make 'indexed' keyword optional 2019-05-28 11:08:36 +02:00
Hans Svensson 33dbeeefad Consider namespaces when collecting used_types 2019-05-28 11:08:36 +02:00
Tobias Lindahl 098dac65e2 Merge pull request #73 from aeternity/PT-162805991-fate-state
The state is always live to prevent bad optimizations
2019-05-28 09:59:22 +02:00
Tobias Lindahl 9cf8733f77 The state is always live to prevent bad optimizations 2019-05-28 09:52:50 +02:00
Ulf Norell 98f349f67c Merge pull request #72 from aeternity/bad-record-compiler-crash
Bad record compiler crash
2019-05-27 12:18:24 +02:00
Ulf Norell 96547ea2ec Test for record field parse error 2019-05-27 12:04:38 +02:00
Ulf Norell ee03442ddf Return parse error instead of crashing the type checker 2019-05-27 11:58:18 +02:00
Tobias Lindahl 0fa09467f6 Pt 166148534 refactor fate code (#71)
* Change of module names aeb_fate_code -> aeb_fate_ops

* Add missing call instructions

* Use new adt for fate code

* Add default init function if not present and keep init function if present

* Fix BLOCKHASH function for fate

* Rewrite for clarity and Dialyzer
2019-05-23 14:01:41 +02:00
Hans Svensson dcae96ed21 Merge pull request #70 from aeternity/PT-166147620-prepare_v3
PT-166147620 Prepare for v3.0.0
2019-05-21 10:37:41 +02:00
Hans Svensson 94689dd0e9 Prepare for v3.0.0 2019-05-21 09:55:17 +02:00
Hans Svensson be7c0e1bd4 Remove escript aesophia 2019-05-21 09:54:42 +02:00
Ulf Norell 8eaafa736c Merge pull request #69 from aeternity/PT-162578475-stateful
Check stateful annotations
2019-05-15 15:44:37 +02:00
Ulf Norell cf5a8aeb5f changelog 2019-05-14 10:10:53 +02:00
Ulf Norell 9e555a3121 Fix type definition 2019-05-14 09:42:27 +02:00
Ulf Norell d051fa6c89 Remove bad code 2019-05-14 09:42:17 +02:00
Tobias Lindahl d4238c0bdc Merge pull request #68 from aeternity/PT-165964555-make-remote-calls-to-contract-objects
Keep the contract type in fate code
2019-05-14 09:41:31 +02:00
Ulf Norell 389072fb12 Add stateful to __call 2019-05-14 09:32:52 +02:00
Tobias Lindahl 1760593170 Keep the contract type in fate code 2019-05-14 09:29:52 +02:00
Ulf Norell d8dd6b900f Remove unused test contract 2019-05-13 17:51:56 +02:00
Ulf Norell 74d4048d9f Check that init doesn't read or write the state 2019-05-13 17:51:47 +02:00
Ulf Norell 6bd2b7c483 Remember source location when computing used names 2019-05-13 17:50:34 +02:00
Ulf Norell 5aed8b3ef5 Check stateful annotations
Functions must be annotated as `stateful` in order to
- Update the contract state (using `put`)
- Call `Chain.spend` or other primitive functions that cost tokens
- Call an Oracle or AENS function that requires a signature
- Make a remote call with a non-zero value
- Construct a lambda calling a stateful function

It does not need to be stateful to
- Read the contract state
- Call another contract with value=0, even when the remote function is stateful
2019-05-13 13:39:17 +02:00
Ulf Norell e1a798aef4 Check for repeated argument names to functions
(PT-159592825)
2019-05-10 15:51:42 +02:00
Ulf Norell 23cc8e1132 Letrec and letfun (#65)
* Type check and compile letfuns

* Minor code simplification

* Remove let rec from Sophia
2019-05-10 13:27:57 +02:00
Tobias Lindahl 691ae72fbb Merge pull request #66 from aeternity/PT-165857097-add-call-value-instruction
Add call value instruction in fate
2019-05-10 13:15:22 +02:00
Tobias Lindahl 8830095c7e Add call value instruction in fate 2019-05-10 12:38:58 +02:00
Tobias Lindahl 251b876495 Add value and gas to remote calls (#64)
* Add value and gas to remote calls
2019-05-10 09:00:36 +02:00
Hans Svensson f8ee8f7129 Merge pull request #63 from aeternity/PT-165440601-165713319-sophia_addons
Add Contract.creator and address checking primitives
2019-05-09 10:34:45 +02:00
Hans Svensson 192ec207a7 Add Contract.creator and address checking primitives 2019-05-09 09:54:04 +02:00
Ulf Norell 0aa1c89556 Fate compiler (#62)
* Update to changes in icode format

* Start on new intermediate code for FATE

* Compile `let` to FATE

* Fix and improve broken bytecode optimisations

* Basic tuple patterns

* Compile shallow matching on tuples

* Liveness analysis for local variables

* Fix minor bug

* Use RETURNR when possible

* Nicer debug printing

* Refactor optimization rules

* Compile tuple construction

* Improve instruction analysis and generalize some optimizations

* Compile nested pattern matching to case trees

(Only tuple and variable patterns so far)

* Reannotate and repeat optimization pass once it done

Could try hard to keep annotations more precise, but would be more error prone

* Get rid of unnecessary STORE instructions

* Keep better track of liveness annotations when swapping instructions

* Limit the number of iterations for the optimization loop

Should finish in one iteration, but we shouldn't loop if there are bugs
or corner cases where it doesn't.

* Pattern matching on booleans

* wip: rewrite case tree compiler to handle catch-alls

still with debug printing, and can't compile it yet

* Add missing case in renaming

* Compile case trees all the way to Fate assembly

* Simplify variables bindings in environment

* Shortcut let x = y in ...

* compile list literals

* Fix various bugs in pattern match compilation

* Pretty printer for fcode

* Fix renaming bug

* Another renaming bug

* Handle switch_body in optimizations

* Remove optimization for if-then-else

* Tag instructions in annotated scode

* Remove 'if' from fcode

* Fix dialyzer things

* Remove unused argument

* Compile pattern matching on integer literals

* Compile list patterns

* Use op_view in more places

* allow leaving out fields from record patterns

* compile records (patterns and construction)

* Compile record update

* Use SETELEMENT instruction

* Compile variants

* Remove incorrect push for tuple switches

* Optimize matching on single constructors datatypes

* Use the fact that SWITCH and JUMPIF can use args and vars

* string literals and pattern matching on the same

* Compile character literals

* Minor refactoring of op instruction handling

* compile address literals

* Get rid of unit in AST

* Unary operators

* Compile function calls

(to fully saturated top-level functions only)

* fix breakage after unary operators

* variables are now lists of names in fcode

* pretty printing for function calls

* use STORE ?a instead of PUSH during optimizations

* no-op fcode optimization pass

* some constant propagation optimizations

* Case on constructor optimization

* fix minor bugs

* Compile all the operators

* Compile maps

* Simplify JUMPIF on true/false

* Fixed left-over reference to STR_EQ

* Add compile-time evaluation for more operators

* Distinguish local vars and top-level names already in fcode

* Compile builtins

* Compile bytes(N)

Compile to FATE strings for now

* Improve inlining of PUSH

* Fix name resolution bug

* Change map_get/set to operators in fcode

* Compile lambdas and higher-order functions

* Optimize single variable closure envs

* Handle unapplied builtins and top-level functions

* Missing case in fcode pretty printer

* Fix variable binding bug in fcode compiler

* Compiler side of state updates

No support in FATE yet though

* Compile statements

* Compile events

But no FATE support for events yet

* Compile remote calls

* Clearer distinction between applied and unapplied top-level things (def/builtin) in fcode

* Tag for literals in fcode to make code cleaner

* We now have block hash at height in FATE

* Update aebytecode commit

* Get rid of catchall todos

* Jump some hoops to please Dialyzer
2019-05-07 15:48:47 +02:00
Ulf Norell 71b97cba62 Merge pull request #61 from aeternity/PT-165597438-equality-on-bytes
Support equality on bytes(N)
2019-04-26 08:47:14 +02:00
Ulf Norell 8a381e5ef1 Support equality on bytes(N) 2019-04-25 16:06:50 +02:00
Hans Svensson 386419f112 Merge pull request #60 from aeternity/PT-164629541-generic_hash_and_signature
Revert bytes(N) from icode/vm-types
2019-04-24 08:58:43 +02:00
Hans Svensson 45a62f0807 Simplify ast_typerep 2019-04-24 08:44:05 +02:00
Hans Svensson 3255c62e0e Revert bytes(N) from icode/vm-types 2019-04-23 17:47:50 +02:00
Hans Svensson 51b63f9559 Merge pull request #59 from aeternity/PT-164629541-generic_hash_and_signature
Add bytes(int), add address_literalsm add ecverify_secp256k1
2019-04-23 11:23:32 +02:00
Hans Svensson 5e6af18c7b Address review comment 2019-04-23 11:10:56 +02:00
Hans Svensson 4324bfd49e Add bytes(int), add address_literalsm add ecverify_secp25k1
hash -> bytes(32)
signature -> bytes(64)
address literals
2019-04-23 10:40:02 +02:00
Erik Stenman faa0ef9772 Merge pull request #57 from aeternity/PT-165312102-setelement
Point to latest aebytecode with setelement instruction.
2019-04-15 10:57:10 +02:00
Erik Stenman f07954f62c Point to latest aebytecode with setelement instruction. 2019-04-15 08:54:02 +02:00
185 changed files with 13685 additions and 2809 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
.rebar3
_*
_[^_]*
.eunit
*.o
*.beam
@@ -19,3 +19,5 @@ rebar3.crashdump
*.erl~
*.aes~
aesophia
.qcci
current_counterexample.eqc
+184 -1
View File
@@ -9,6 +9,182 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### Removed
## [4.3.0]
### Added
- Added documentation (moved from `protocol`)
- `Frac.aes` library for rational numbers
- Added some more meaningful error messages
- Exported several parsing functionalities
- With option `keep_included` it is possible to see which files were included during the parse
- There is a function `run_parser` that be used to evaluate any parsing rule
- Exported parsers: `body`, `type` and `decl`
### Changed
- Performance improvements in the standard library
- Fixed ACI encoder to handle `-` unary operator
- Fixed including by absolute path
- Fixed variant type printing in the ACI error messages
- Fixed pretty printing of combined function clauses
### Removed
- `let` definitions are no longer supported in the toplevel of the contract
- type declarations are no longer supported
## [4.2.0] - 2020-01-15
### Added
- Allow separate entrypoint/function type signature and definition, and pattern
matching in left-hand sides:
```
function
length : list('a) => int
length([]) = 0
length(x :: xs) = 1 + length(xs)
```
- Allow pattern matching in list comprehension generators (filtering out match
failures):
```
function somes(xs : list(option('a))) : list('a) =
[ x | Some(x) <- xs ]
```
- Allow pattern matching in let-bindings (aborting on match failures):
```
function test(m : map(int, int)) =
let Some(x) = Map.lookup(m, 0)
x
```
### Changed
- FATE code generator improvements.
- Bug fix: Handle qualified constructors in patterns.
- Bug fix: Allow switching also on negative numbers.
### Removed
## [4.1.0] - 2019-11-26
### 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/aesophia/blob/master/docs/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
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
for.
- New builtin functions `Crypto.ecrecover_secp256k1: (hash, bytes(65)) => option(bytes(20))`
and `Crypto.ecverify_secp256k1 : (hash, bytes(20), bytes(65)) => bool` for recovering
and verifying an Ethereum address for a message hash and a signature.
- Sophia supports list comprehensions known from languages like Python, Haskell or Erlang.
Example syntax:
```
[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
// yields [12,13,14,20,21,22,30,31,32]
```
- A new contract, and endpoint, modifier `payable` is introduced. Contracts, and enpoints,
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.
### 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
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,
so `(1, "a") : int * string`
- The `AENS.transfer` and `AENS.revoke` functions have been updated to take a name `string`
instead of a name `hash`.
- Fixed a bug where the `AEVM` backend complained about a missing `init` function when
trying to generate calldata from an ACI-generated interface.
- Compiler now returns the ABI-version in the compiler result map.
- Renamed `Crypto.ecverify` and `Crypto.ecverify_secp256k1` into `Crypto.verify_sig` and
`Crypto.verify_sig_secp256k1` respectively.
### Removed
## [3.2.0] - 2019-06-28
### Added
- New builtin function `require : (bool, string) => ()`. Defined as
```
function require(b, err) = if(!b) abort(err)
```
- New builtin functions
```
Bytes.to_str : bytes(_) => string
Bytes.to_int : bytes(_) => int
```
for converting a byte array to a hex string and interpreting it as a
big-endian encoded integer respectively.
### Changed
- Public contract functions must now be declared as *entrypoints*:
```
contract Example =
// Exported
entrypoint exported_fun(x) = local_fun(x)
// Not exported
function local_fun(x) = x
```
Functions in namespaces still use `function` (and `private function` for
private functions).
- The return type of `Chain.block_hash(height)` has changed, it used to
be `int`, where `0` denoted an incorrect height. New return type is
`option(hash)`, where `None` represents an incorrect height.
- Event name hashes now use BLAKE2b instead of Keccak256.
- Fixed bugs when defining record types in namespaces.
- Fixed a bug in include path handling when passing options to the compiler.
### Removed
## [3.1.0] - 2019-06-03
### Added
### Changed
- Keyword `indexed` is now optional for word typed (`bool`, `int`, `address`,
...) event arguments.
- State variable pretty printing now produce `'a, 'b, ...` instead of `'1, '2, ...`.
- ACI is restructured and improved:
- `state` and `event` types (if present) now appear at the top level.
- Namespaces and remote interfaces are no longer ignored.
- All type definitions are included in the interface rendering.
- API functions are renamed, new functions are `contract_interface`
and `render_aci_json`.
- Fixed a bug in `create_calldata`/`to_sophia_value` - it can now handle negative
literals.
### Removed
## [3.0.0] - 2019-05-21
### Added
- `stateful` annotations are now properly enforced. Functions must be marked stateful
in order to update the state or spend tokens.
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
- `Crypto.ecverify_secp256k1` has been added.
### Changed
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
not working properly and has been fixed.
### Removed
- `let rec` has been removed from the language, it has never worked.
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
been completely removed from `aesophia`.
## [2.1.0] - 2019-04-11
### Added
- Stubs (not yet wired up) for compilation to FATE
@@ -35,6 +211,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD
[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0
[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0
[4.0.0]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.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.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
+10 -6
View File
@@ -1,13 +1,17 @@
# aesophia
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code.
This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ to [FATE](https://github.com/aeternity/protocol/blob/master/contracts/fate.md) instructions.
For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md).
The compiler is currently being used three places
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system.
## Documentation
* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md).
* [Sophia Documentation](docs/sophia.md).
* [Sophia Standard Library](docs/sophia_stdlib.md).
## Versioning
+76 -55
View File
@@ -30,28 +30,14 @@ generates the following JSON structure representing the contract interface:
``` json
{
"contract": {
"name": "Answers",
"type_defs": [
{
"name": "state",
"vars": [],
"typedef": "{a : map(string,int)}"
},
{
"name": "answers",
"vars": [],
"typedef": "map(string,int)"
}
],
"functions": [
{
"name": "init",
"arguments": [],
"type": "{a : map(string,int)}",
"name": "init",
"returns": "Answers.state",
"stateful": true
},
{
"name": "new_answer",
"arguments": [
{
"name": "q",
@@ -62,9 +48,36 @@ generates the following JSON structure representing the contract interface:
"type": "int"
}
],
"type": "map(string,int)",
"name": "new_answer",
"returns": {
"map": [
"string",
"int"
]
},
"stateful": false
}
],
"name": "Answers",
"state": {
"record": [
{
"name": "a",
"type": "Answers.answers"
}
]
},
"type_defs": [
{
"name": "answers",
"typedef": {
"map": [
"string",
"int"
]
},
"vars": []
}
]
}
}
@@ -74,62 +87,70 @@ When that encoding is decoded the following include definition is generated:
```
contract Answers =
function new_answer : (string, int) => map(string,int)
record state = {a : Answers.answers}
type answers = map(string, int)
function init : () => Answers.state
function new_answer : (string, int) => map(string, int)
```
### Types
``` erlang
contract_string() = string() | binary()
json_string() = binary()
```erlang
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
```
### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
#### contract\_interface(aci\_type(), string()) -> {ok, json() | string()} | {error, term()}
Types
Generate the JSON encoding of the interface to a contract. The type definitions
and non-private functions are included in the JSON string.
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
#### render\_aci\_json(json() | json\_text()) -> string().
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
Take a JSON encoding of a contract interface and generate a contract interface
that can be included in another contract.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
This is an example of using the ACI generator from an Erlang shell. The file
called `aci_test.aes` contains the contract in the description from which we
want to generate files `aci_test.json` which is the JSON encoding of the
contract interface and `aci_test.include` which is the contract definition to
be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,Encoding} = aeso_aci:encode(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>>
3> file:write_file("aci_test.aci", Encoding).
2> {ok,JsonACI} = aeso_aci:contract_interface(json, Contract).
{ok,[#{contract =>
#{functions =>
[#{arguments => [],name => <<"init">>,
returns => <<"Answers.state">>,stateful => true},
#{arguments =>
[#{name => <<"q">>,type => <<"string">>},
#{name => <<"a">>,type => <<"int">>}],
name => <<"new_answer">>,
returns => #{<<"map">> => [<<"string">>,<<"int">>]},
stateful => false}],
name => <<"Answers">>,
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
3> file:write_file("aci_test.aci", jsx:encode(JsonACI)).
ok
4> Decoded = aeso_aci:decode(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">>
5> file:write_file("aci_test.include", Decoded).
4> {ok,InterfaceStub} = aeso_aci:render_aci_json(JsonACI).
{ok,<<"contract Answers =\n record state = {a : Answers.answers}\n type answers = map(string, int)\n function init "...>>}
5> file:write_file("aci_test.include", InterfaceStub).
ok
6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>>
6> jsx:prettify(jsx:encode(JsonACI)).
<<"[\n {\n \"contract\": {\n \"functions\": [\n {\n \"arguments\": [],\n \"name\": \"init\",\n "...>>
```
The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
The final call to `jsx:prettify(jsx:encode(JsonACI))` returns the encoding in a
more easily readable form. This is what is shown in the description above.
+1071
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
namespace Frac =
private function gcd(a : int, b : int) =
if (b == 0) a else gcd(b, a mod b)
private function abs_int(a : int) = if (a < 0) -a else a
datatype frac = Pos(int, int) | Zero | Neg(int, int)
/** Checks if the internal representation is correct.
* Numerator and denominator must be positive.
* Exposed for debug purposes
*/
function is_sane(f : frac) : bool = switch(f)
Pos(n, d) => n > 0 && d > 0
Zero => true
Neg(n, d) => n > 0 && d > 0
function num(f : frac) : int = switch(f)
Pos(n, _) => n
Neg(n, _) => -n
Zero => 0
function den(f : frac) : int = switch(f)
Pos(_, d) => d
Neg(_, d) => d
Zero => 1
function to_pair(f : frac) : int * int = switch(f)
Pos(n, d) => (n, d)
Neg(n, d) => (-n, d)
Zero => (0, 1)
function sign(f : frac) : int = switch(f)
Pos(_, _) => 1
Neg(_, _) => -1
Zero => 0
function to_str(f : frac) : string = switch(f)
Pos(n, d) => String.concat(Int.to_str(n), if (d == 1) "" else String.concat("/", Int.to_str(d)))
Neg(n, d) => String.concat("-", to_str(Pos(n, d)))
Zero => "0"
/** Reduce fraction to normal form
*/
function simplify(f : frac) : frac =
switch(f)
Neg(n, d) =>
let cd = gcd(n, d)
Neg(n / cd, d / cd)
Zero => Zero
Pos(n, d) =>
let cd = gcd(n, d)
Pos(n / cd, d / cd)
/** Integer to rational division
*/
function make_frac(n : int, d : int) : frac =
if (d == 0) abort("Zero denominator")
elif (n == 0) Zero
elif ((n < 0) == (d < 0)) simplify(Pos(abs_int(n), abs_int(d)))
else simplify(Neg(abs_int(n), abs_int(d)))
function one() : frac = Pos(1, 1)
function zero() : frac = Zero
function eq(a : frac, b : frac) : bool =
let (na, da) = to_pair(a)
let (nb, db) = to_pair(b)
(na == nb && da == db) || na * db == nb * da // they are more likely to be normalized
function neq(a : frac, b : frac) : bool =
let (na, da) = to_pair(a)
let (nb, db) = to_pair(b)
(na != nb || da != db) && na * db != nb * da
function geq(a : frac, b : frac) : bool = num(a) * den(b) >= num(b) * den(a)
function leq(a : frac, b : frac) : bool = num(a) * den(b) =< num(b) * den(a)
function gt(a : frac, b : frac) : bool = num(a) * den(b) > num(b) * den(a)
function lt(a : frac, b : frac) : bool = num(a) * den(b) < num(b) * den(a)
function min(a : frac, b : frac) : frac = if (leq(a, b)) a else b
function max(a : frac, b : frac) : frac = if (geq(a, b)) a else b
function abs(f : frac) : frac = switch(f)
Pos(n, d) => Pos(n, d)
Zero => Zero
Neg(n, d) => Pos(n, d)
function from_int(n : int) : frac =
if (n > 0) Pos(n, 1)
elif (n < 0) Neg(-n, 1)
else Zero
function floor(f : frac) : int = switch(f)
Pos(n, d) => n / d
Zero => 0
Neg(n, d) => -(n + d - 1) / d
function ceil(f : frac) : int = switch(f)
Pos(n, d) => (n + d - 1) / d
Zero => 0
Neg(n, d) => -n / d
function round_to_zero(f : frac) : int = switch(f)
Pos(n, d) => n / d
Zero => 0
Neg(n, d) => -n / d
function round_from_zero(f : frac) : int = switch(f)
Pos(n, d) => (n + d - 1) / d
Zero => 0
Neg(n, d) => -(n + d - 1) / d
/** Round towards nearest integer. If two integers are in the same
* distance, choose the even one.
*/
function round(f : frac) : int =
let fl = floor(f)
let cl = ceil(f)
let dif_fl = abs(sub(f, from_int(fl)))
let dif_cl = abs(sub(f, from_int(cl)))
if (gt(dif_fl, dif_cl)) cl
elif (gt(dif_cl, dif_fl)) fl
elif (fl mod 2 == 0) fl
else cl
function add(a : frac, b : frac) : frac =
let (na, da) = to_pair(a)
let (nb, db) = to_pair(b)
if (da == db) make_frac(na + nb, da)
else make_frac(na * db + nb * da, da * db)
function neg(a : frac) : frac = switch(a)
Neg(n, d) => Pos(n, d)
Zero => Zero
Pos(n, d) => Neg(n, d)
function sub(a : frac, b : frac) : frac = add(a, neg(b))
function inv(a : frac) : frac = switch(a)
Neg(n, d) => Neg(d, n)
Zero => abort("Inversion of zero")
Pos(n, d) => Pos(d, n)
function mul(a : frac, b : frac) : frac = make_frac(num(a) * num(b), den(a) * den(b))
function div(a : frac, b : frac) : frac = switch(b)
Neg(n, d) => mul(a, Neg(d, n))
Zero => abort("Division by zero")
Pos(n, d) => mul(a, Pos(d, n))
/** `b` to the power of `e`
*/
function int_exp(b : frac, e : int) : frac =
if (sign(b) == 0 && e == 0) abort("Zero to the zero exponentation")
elif (e < 0) inv(int_exp_(b, -e))
else int_exp_(b, e)
private function int_exp_(b : frac, e : int) =
if (e == 0) from_int(1)
elif (e == 1) b
else
let half = int_exp_(b, e / 2)
if (e mod 2 == 1) mul(mul(half, half), b)
else mul(half, half)
/** Reduces the fraction's in-memory size by dividing its components by two until the
* the error is bigger than `loss` value
*/
function optimize(f : frac, loss : frac) : frac =
require(geq(loss, Zero), "negative loss optimize")
let s = sign(f)
mul(from_int(s), run_optimize(abs(f), abs(f), loss))
private function run_optimize(orig : frac, f : frac, loss : frac) : frac =
let (n, d) = to_pair(f)
let t = make_frac((n+1)/2, (d+1)/2)
if(gt(abs(sub(t, orig)), loss)) f
elif (eq(t, f)) f
else run_optimize(orig, t, loss)
+77
View File
@@ -0,0 +1,77 @@
namespace Func =
function id(x : 'a) : 'a = x
function const(x : 'a) : 'b => 'a = (y) => x
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
function comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c = (x) => f(g(x))
function pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c = (x) => g(f(x))
function rapply(x : 'a, f : 'a => 'b) : 'b = f(x)
/** The Z combinator - replacement for local and anonymous recursion.
*/
function recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res =
(x) => f(recur(f), x)
/** n-times composition with itself
*/
function iter(n : int, f : 'a => 'a) : 'a => 'a = iter_(n, f, (x) => x)
private function iter_(n : int, f : 'a => 'a, acc : 'a => 'a) : 'a => 'a =
if(n == 0) acc
elif(n == 1) comp(f, acc)
else iter_(n / 2, comp(f, f), if(n mod 2 == 0) acc else comp(f, acc))
/** Turns an ugly, bad and disgusting arity-n function into
* a beautiful and sweet function taking the first argument
* and returning a function watiting for the remaining ones
* in the same manner
*/
function curry2(f : ('a, 'b) => 'x) : 'a => ('b => 'x) =
(x) => (y) => f(x, y)
function curry3(f : ('a, 'b, 'c) => 'x) : 'a => ('b => ('c => 'x)) =
(x) => (y) => (z) => f(x, y, z)
function curry4(f : ('a, 'b, 'c, 'd) => 'x) : 'a => ('b => ('c => ('d => 'x))) =
(x) => (y) => (z) => (w) => f(x, y, z, w)
function curry5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a => ('b => ('c => ('d => ('e => 'x)))) =
(x) => (y) => (z) => (w) => (q) => f(x, y, z, w, q)
/** Opposite of curry. Gross
*/
function uncurry2(f : 'a => ('b => 'x)) : ('a, 'b) => 'x =
(x, y) => f(x)(y)
function uncurry3(f : 'a => ('b => ('c => 'x))) : ('a, 'b, 'c) => 'x =
(x, y, z) => f(x)(y)(z)
function uncurry4(f : 'a => ('b => ('c => ('d => 'x)))) : ('a, 'b, 'c, 'd) => 'x =
(x, y, z, w) => f(x)(y)(z)(w)
function uncurry5(f : 'a => ('b => ('c => ('d => ('e => 'x))))) : ('a, 'b, 'c, 'd, 'e) => 'x =
(x, y, z, w, q) => f(x)(y)(z)(w)(q)
/** Turns an arity-n function into a function taking n-tuple
*/
function tuplify2(f : ('a, 'b) => 'x) : (('a * 'b)) => 'x =
(t) => switch(t)
(x, y) => f(x, y)
function tuplify3(f : ('a, 'b, 'c) => 'x) : 'a * 'b * 'c => 'x =
(t) => switch(t)
(x, y, z) => f(x, y, z)
function tuplify4(f : ('a, 'b, 'c, 'd) => 'x) : 'a * 'b * 'c * 'd => 'x =
(t) => switch(t)
(x, y, z, w) => f(x, y, z, w)
function tuplify5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a * 'b * 'c * 'd * 'e => 'x =
(t) => switch(t)
(x, y, z, w, q) => f(x, y, z, w, q)
/** Opposite of tuplify
*/
function untuplify2(f : 'a * 'b => 'x) : ('a, 'b) => 'x =
(x, y) => f((x, y))
function untuplify3(f : 'a * 'b * 'c => 'x) : ('a, 'b, 'c) => 'x =
(x, y, z) => f((x, y, z))
function untuplify4(f : 'a * 'b * 'c * 'd => 'x) : ('a, 'b, 'c, 'd) => 'x =
(x, y, z, w) => f((x, y, z, w))
function untuplify5(f : 'a * 'b * 'c * 'd * 'e => 'x) : ('a, 'b, 'c, 'd, 'e) => 'x =
(x, y, z, w, q) => f((x, y, z, w, q))
+307
View File
@@ -0,0 +1,307 @@
include "ListInternal.aes"
namespace List =
function is_empty(l : list('a)) : bool = switch(l)
[] => true
_ => false
function first(l : list('a)) : option('a) = switch(l)
[] => None
h::_ => Some(h)
function tail(l : list('a)) : option(list('a)) = switch(l)
[] => None
_::t => Some(t)
function last(l : list('a)) : option('a) = switch(l)
[] => None
[x] => Some(x)
_::t => last(t)
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None`
* if no such element exists.
*/
function find(p : 'a => bool, l : list('a)) : option('a) = switch(l)
[] => None
h::t => if(p(h)) Some(h) else find(p, t)
/** Returns list of all indices of elements from `l` that fulfill the predicate `p`.
*/
function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0, [])
private function find_indices_( p : 'a => bool
, l : list('a)
, n : int
, acc : list(int)
) : list(int) = switch(l)
[] => reverse(acc)
h::t => find_indices_(p, t, n+1, if(p(h)) n::acc else acc)
function nth(n : int, l : list('a)) : option('a) =
switch(l)
[] => None
h::t => if(n == 0) Some(h) else nth(n-1, t)
/* Unsafe version of `nth` */
function get(n : int, l : list('a)) : 'a =
switch(l)
[] => abort(if(n < 0) "Negative index get" else "Out of index get")
h::t => if(n == 0) h else get(n-1, t)
function length(l : list('a)) : int = length_(l, 0)
private function length_(l : list('a), acc : int) : int = switch(l)
[] => acc
_::t => length_(t, acc + 1)
/** Creates an ascending sequence of all integer numbers
* between `a` and `b` (including `a` and `b`)
*/
function from_to(a : int, b : int) : list(int) = [a..b]
/** Creates an ascending sequence of integer numbers betweeen
* `a` and `b` jumping by given `step`. Includes `a` and takes
* `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0.
*/
function from_to_step(a : int, b : int, s : int) : list(int) = from_to_step_(a, b, s, [])
private function from_to_step_(a, b, s, acc) =
if (a > b) reverse(acc) else from_to_step_(a + s, b, s, a :: acc)
/** Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow
*/
function replace_at(n : int, e : 'a, l : list('a)) : list('a) =
if(n<0) abort("insert_at underflow") else replace_at_(n, e, l, [])
private function replace_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
switch(l)
[] => abort("replace_at overflow")
h::t => if (n == 0) reverse(e::acc) ++ t
else replace_at_(n-1, e, t, h::acc)
/** Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow
*/
function insert_at(n : int, e : 'a, l : list('a)) : list('a) =
if(n<0) abort("insert_at underflow") else insert_at_(n, e, l, [])
private function insert_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) =
if (n == 0) reverse(e::acc) ++ l
else switch(l)
[] => abort("insert_at overflow")
h::t => insert_at_(n-1, e, t, h::acc)
/** Assuming that cmp represents `<` comparison, inserts `x` before
* the first element in the list `l` which is greater than it
*/
function insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
insert_by_(cmp, x, l, [])
private function insert_by_(cmp : (('a, 'a) => bool), x : 'a, l : list('a), acc : list('a)) : list('a) =
switch(l)
[] => reverse(x::acc)
h::t =>
if(cmp(x, h)) // x < h
reverse(acc) ++ (x::l)
else
insert_by_(cmp, x, t, h::acc)
function foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b = switch(l)
[] => nil
h::t => cons(h, foldr(cons, nil, t))
function foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b = switch(l)
[] => acc
h::t => foldl(rcons, rcons(acc, h), t)
function foreach(l : list('a), f : 'a => unit) : unit =
switch(l)
[] => ()
e::l' =>
f(e)
foreach(l', f)
function reverse(l : list('a)) : list('a) = foldl((lst, el) => el :: lst, [], l)
function map(f : 'a => 'b, l : list('a)) : list('b) = map_(f, l, [])
private function map_(f : 'a => 'b, l : list('a), acc : list('b)) : list('b) = switch(l)
[] => reverse(acc)
h::t => map_(f, t, f(h)::acc)
/** Effectively composition of `map` and `flatten`
*/
function flat_map(f : 'a => list('b), l : list('a)) : list('b) =
ListInternal.flat_map(f, l)
function filter(p : 'a => bool, l : list('a)) : list('a) = filter_(p, l, [])
private function filter_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => filter_(p, t, if(p(h)) h::acc else acc)
/** Take `n` first elements
*/
function take(n : int, l : list('a)) : list('a) =
if(n < 0) abort("Take negative number of elements") else take_(n, l, [])
private function take_(n : int, l : list('a), acc : list('a)) : list('a) =
if(n == 0) reverse(acc)
else switch(l)
[] => reverse(acc)
h::t => take_(n-1, t, h::acc)
/** Drop `n` first elements
*/
function drop(n : int, l : list('a)) : list('a) =
if(n < 0) abort("Drop negative number of elements")
elif (n == 0) l
else switch(l)
[] => []
h::t => drop(n-1, t)
/** Get the longest prefix of a list in which every element
* matches predicate `p`
*/
function take_while(p : 'a => bool, l : list('a)) : list('a) = take_while_(p, l, [])
private function take_while_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => if(p(h)) take_while_(p, t, h::acc) else reverse(acc)
/** Drop elements from `l` until `p` holds
*/
function drop_while(p : 'a => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => if(p(h)) drop_while(p, t) else l
/** Splits list into two lists of elements that respectively
* match and don't match predicate `p`
*/
function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = partition_(p, l, [], [])
private function partition_( p : 'a => bool
, l : list('a)
, acc_t : list('a)
, acc_f : list('a)
) : (list('a) * list('a)) = switch(l)
[] => (reverse(acc_t), reverse(acc_f))
h::t => if(p(h)) partition_(p, t, h::acc_t, acc_f) else partition_(p, t, acc_t, h::acc_f)
/** Flattens list of lists into a single list
*/
function flatten(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll)
function all(p : 'a => bool, l : list('a)) : bool = switch(l)
[] => true
h::t => if(p(h)) all(p, t) else false
function any(p : 'a => bool, l : list('a)) : bool = switch(l)
[] => false
h::t => if(p(h)) true else any(p, t)
function sum(l : list(int)) : int = foldl ((a, b) => a + b, 0, l)
function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l)
/** Zips two list by applying bimapping function on respective elements.
* Drops longer tail.
*/
function zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) = zip_with_(f, l1, l2, [])
private function zip_with_( f : ('a, 'b) => 'c
, l1 : list('a)
, l2 : list('b)
, acc : list('c)
) : list('c) = switch ((l1, l2))
(h1::t1, h2::t2) => zip_with_(f, t1, t2, f(h1, h2)::acc)
_ => reverse(acc)
/** Zips two lists into list of pairs. Drops longer tail.
*/
function zip(l1 : list('a), l2 : list('b)) : list('a * 'b) = zip_with((a, b) => (a, b), l1, l2)
function unzip(l : list('a * 'b)) : list('a) * list('b) = unzip_(l, [], [])
private function unzip_( l : list('a * 'b)
, acc_l : list('a)
, acc_r : list('b)
) : (list('a) * list('b)) = switch(l)
[] => (reverse(acc_l), reverse(acc_r))
(left, right)::t => unzip_(t, left::acc_l, right::acc_r)
/** Merges two sorted lists using `lt` comparator
*/
function
merge : (('a, 'a) => bool, list('a), list('a)) => list('a)
merge(lt, x::xs, y::ys) =
if(lt(x, y)) x::merge(lt, xs, y::ys)
else y::merge(lt, x::xs, ys)
merge(_, [], ys) = ys
merge(_, xs, []) = xs
/** Mergesort inspired by
* https://hackage.haskell.org/package/base-4.14.1.0/docs/src/Data.OldList.html#sort
*/
function
sort : (('a, 'a) => bool, list('a)) => list('a)
sort(_, []) = []
sort(lt, l) =
merge_all(lt, monotonic_subs(lt, l))
/** Splits list into compound increasing sublists
*/
private function
monotonic_subs : (('a, 'a) => bool, list('a)) => list(list('a))
monotonic_subs(lt, x::y::rest) =
if(lt(y, x)) desc(lt, y, [x], rest)
else asc(lt, y, [x], rest)
monotonic_subs(_, l) = [l]
/** Extracts the longest descending prefix and proceeds with monotonic split
*/
private function
desc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
desc(lt, x, acc, h::t) =
if(lt(x, h)) (x::acc) :: monotonic_subs(lt, h::t)
else desc(lt, h, x::acc, t)
desc(_, x, acc, []) = [x::acc]
/** Extracts the longest ascending prefix and proceeds with monotonic split
*/
private function
asc : (('a, 'a) => bool, 'a, list('a), list('a)) => list(list('a))
asc(lt, x, acc, h::t) =
if(lt(h, x)) List.reverse(x::acc) :: monotonic_subs(lt, h::t)
else asc(lt, h, x::acc, t)
asc(_, x, acc, []) = [List.reverse(x::acc)]
/** Merges list of sorted lists
*/
private function
merge_all : (('a, 'a) => bool, list(list('a))) => list('a)
merge_all(_, [part]) = part
merge_all(lt, parts) = merge_all(lt, merge_pairs(lt, parts))
/** Single round of `merge_all` pairs of lists in a list of list
*/
private function
merge_pairs : (('a, 'a) => bool, list(list('a))) => list(list('a))
merge_pairs(lt, x::y::rest) = merge(lt, x, y) :: merge_pairs(lt, rest)
merge_pairs(_, l) = l
/** Puts `delim` between every two members of the list
*/
function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, [])
private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
[e] => reverse(e::acc)
h::t => intersperse_(delim, t, delim::h::acc)
/** Effectively a zip with an infinite sequence of natural numbers
*/
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0, [])
private function enumerate_(l : list('a), n : int, acc : list(int * 'a)) : list(int * 'a) = switch(l)
[] => reverse(acc)
h::t => enumerate_(t, n + 1, (n, h)::acc)
+16
View File
@@ -0,0 +1,16 @@
namespace ListInternal =
// -- Flatmap ----------------------------------------------------------------
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
switch(xs)
[] => []
x :: xs => f(x) ++ flat_map(f, xs)
// -- From..to ---------------------------------------------------------------
function from_to(a : int, b : int) : list(int) = from_to_(a, b, [])
private function from_to_(a, b, acc) =
if (a > b) acc else from_to_(a, b - 1, b :: acc)
+98
View File
@@ -0,0 +1,98 @@
include "List.aes"
namespace Option =
function is_none(o : option('a)) : bool = switch(o)
None => true
Some(_) => false
function is_some(o : option('a)) : bool = switch(o)
None => false
Some(_) => true
/** Catamorphism on `option`. Also known as inlined pattern matching.
*/
function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o)
None => n
Some(x) => s(x)
/** Escape option providing default if `None`
*/
function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o)
/** Assume it is `Some`
*/
function force(o : option('a)) : 'a = switch(o)
None => abort("Forced None value")
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
function map(f : 'a => 'b, o : option('a)) : option('b) = switch(o)
None => None
Some(x) => Some(f(x))
function map2(f : ('a, 'b) => 'c
, o1 : option('a)
, o2 : option('b)
) : option('c) = switch((o1, o2))
(Some(x1), Some(x2)) => Some(f(x1, x2))
_ => None
function map3( f : ('a, 'b, 'c) => 'd
, o1 : option('a)
, o2 : option('b)
, o3 : option('c)
) : option('d) = switch((o1, o2, o3))
(Some(x1), Some(x2), Some(x3)) => Some(f(x1, x2, x3))
_ => None
/** Like `map`, but the function is in `option`
*/
function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o))
(Some(ff), Some(xx)) => Some(ff(xx))
_ => None
/** Monadic bind
*/
function flat_map(f : 'a => option('b), o : option('a)) : option('b) = switch(o)
None => None
Some(x) => f(x)
function to_list(o : option('a)) : list('a) = switch(o)
None => []
Some(x) => [x]
/** Turns list of options into a list of elements that are under `Some`s.
* Safe.
*/
function filter_options(l : list(option('a))) : list('a) = filter_options_(l, [])
private function filter_options_(l : list (option('a)), acc : list('a)) : list('a) = switch(l)
[] => List.reverse(acc)
None::t => filter_options_(t, acc)
Some(x)::t => filter_options_(t, x::acc)
/** Just like `filter_options` but requires all elements to be `Some` and returns
* None if any of them is not
*/
function seq_options(l : list (option('a))) : option (list('a)) = seq_options_(l, [])
private function seq_options_(l : list (option('a)), acc : list('a)) : option(list('a)) = switch(l)
[] => Some(List.reverse(acc))
None::t => None
Some(x)::t => seq_options_(t, x::acc)
/** Choose `Some` out of two if possible
*/
function choose(o1 : option('a), o2 : option('a)) : option('a) =
if(is_some(o1)) o1 else o2
/** Choose `Some` from list of options if possible
*/
function choose_first(l : list(option('a))) : option('a) = switch(l)
[] => None
None::t => choose_first(t)
Some(x)::_ => Some(x)
+26
View File
@@ -0,0 +1,26 @@
namespace Pair =
function fst(t : ('a * 'b)) : 'a = switch(t)
(x, _) => x
function snd(t : ('a * 'b)) : 'b = switch(t)
(_, y) => y
/** Map over first
*/
function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t)
(x, y) => (f(x), y)
/** Map over second
*/
function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t)
(x, y) => (x, f(y))
/** Map over both
*/
function bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) = switch(t)
(x, y) => (f(x), g(y))
function swap(t : ('a * 'b)) : ('b * 'a) = switch(t)
(x, y) => (y, x)
+49
View File
@@ -0,0 +1,49 @@
namespace Triple =
function fst(t : ('a * 'b * 'c)) : 'a = switch(t)
(x, _, _) => x
function snd(t : ('a * 'b * 'c)) : 'b = switch(t)
(_, y, _) => y
function thd(t : ('a * 'b * 'c)) : 'c = switch(t)
(_, _, z) => z
/** Map over first
*/
function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t)
(x, y, z) => (f(x), y, z)
/** Map over second
*/
function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t)
(x, y, z) => (x, f(y), z)
/** Map over third
*/
function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t)
(x, y, z) => (x, y, f(z))
/** Map over all elements
*/
function trimap( f : 'a => 'x
, g : 'b => 'y
, h : 'c => 'z
, t : ('a * 'b * 'c)
) : ('x * 'y * 'z) = switch(t)
(x, y, z) => (f(x), g(y), h(z))
function swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) = switch(t)
(x, y, z) => (z, y, x)
/** Right rotation
*/
function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t)
(x, y, z) => (z, x, y)
/** Left rotation
*/
function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t)
(x, y, z) => (y, z, x)
+3 -18
View File
@@ -2,35 +2,20 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref, "28f6c42"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
, {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! \n"}.
{provider_hooks, [{post, [{compile, escriptize}]}]}.
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
escriptize,
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
{"win32",
escriptize,
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
]}.
{dialyzer, [
{warnings, [unknown]},
{plt_apps, all_deps},
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "2.1.0"},
{relx, [{release, {aesophia, "4.3.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+7 -3
View File
@@ -1,17 +1,21 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
{ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},1},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
BIN
View File
Binary file not shown.
+257 -287
View File
@@ -9,284 +9,298 @@
-module(aeso_aci).
-export([encode/1,encode/2,decode/1]).
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
-export([ file/2
, file/3
, contract_interface/2
, contract_interface/3
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
, from_typed_ast/2
%% Top-level
-record(contract, {ann,con,decls}).
%% -record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
, render_aci_json/1
%% Types
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
, json_encode_expr/1
, json_encode_type/1]).
%% Tokens
-record(arg, {ann,id,type}).
-record(id, {ann,name}).
-record(con, {ann,name}).
-record(qid, {ann,names}).
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
%% Expressions
-record(bool, {ann,bool}).
-record(int, {ann,value}).
-record(string, {ann,bin}).
-record(hash, {ann,hash}).
-record(tuple, {ann,args}).
-record(list, {ann,args}).
-record(app, {ann,func,args}).
-record(typed, {ann,expr,type}).
-export_type([aci_type/0]).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
%% External API
-spec file(aci_type(), string()) -> {ok, json() | string()} | {error, term()}.
file(Type, File) ->
file(Type, File, []).
encode(ContractString) -> encode(ContractString, []).
encode(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options);
encode(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract.
Contract = lists:last(TypedAst),
Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) ||
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
file(Type, File, Options0) ->
Options = aeso_compiler:add_include_path(File, Options0),
case file:read_file(File) of
{ok, BinCode} ->
do_contract_interface(Type, binary_to_list(BinCode), Options);
{error, _} = Err -> Err
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-spec contract_interface(aci_type(), string()) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString) ->
contract_interface(Type, ContractString, []).
-spec contract_interface(aci_type(), string(), [term()]) ->
{ok, json() | string()} | {error, term()}.
contract_interface(Type, ContractString, CompilerOpts) ->
do_contract_interface(Type, ContractString, CompilerOpts).
-spec render_aci_json(json() | json_text()) -> {ok, binary()}.
render_aci_json(Json) ->
do_render_aci_json(Json).
-spec json_encode_expr(aeso_syntax:expr()) -> json().
json_encode_expr(Expr) ->
encode_expr(Expr).
-spec json_encode_type(aeso_syntax:type()) -> json().
json_encode_type(Type) ->
encode_type(Type).
%% Internal functions
do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, binary_to_list(Contract), Options);
do_contract_interface(Type, ContractString, Options) ->
try
Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
%% io:format("~p\n", [TypedAst]),
from_typed_ast(Type, TypedAst)
catch
throw:{error, Errors} -> {error, Errors}
end.
from_typed_ast(Type, TypedAst) ->
JArray = [ encode_contract(C) || C <- TypedAst ],
case Type of
json -> {ok, JArray};
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
FilterT = fun(N) -> fun(#{name := N1}) -> N == N1 end end,
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs},
C2 = case Es of
[] -> C1;
[#{typedef := ET}] -> C1#{event => ET}
end,
C3 = case Ss of
[] -> C2;
[#{typedef := ST}] -> C2#{state => ST}
end,
Fdefs = [ encode_function(F)
|| F <- sort_decls(contract_funcs(Contract)),
is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}};
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
%% encode_func(Function) -> JSON
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
encode_function(FDef = {letfun, _, {id, _, Name}, Args, Type, _}) ->
#{name => encode_name(Name),
arguments => encode_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDef),
payable => is_payable(FDef)};
encode_function(FDecl = {fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Type}}) ->
#{name => encode_name(Name),
arguments => encode_anon_args(Args),
returns => encode_type(Type),
stateful => is_stateful(FDecl),
payable => is_payable(FDecl)}.
encode_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, encode_name(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"returns">>, encode_type(Type)},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_anon_args(Types) ->
Anons = [ list_to_binary("_" ++ integer_to_list(X)) || X <- lists:seq(1, length(Types))],
[ #{name => V, type => encode_type(T)}
|| {V, T} <- lists:zip(Anons, Types) ].
%% encode_args(Args) -> [JSON].
%% encode_arg(Args) -> JSON.
encode_args(Args) -> [ encode_arg(A) || A <- Args ].
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_types(Types) -> [JSON].
%% encode_type(Type) -> JSON.
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
encode_type(#tvar{name=N}) -> encode_name(N);
encode_type(#id{name=N}) -> encode_name(N);
encode_type(#con{name=N}) -> encode_name(N);
encode_type(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_type(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
[{<<"tuple">>,Eas}];
encode_type(#record_t{fields=Fs}) ->
Efs = encode_fields(Fs),
[{<<"record">>,Efs}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
[{Name,Efs}];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
[{<<"variant">>,Ecs}];
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
[{Ec,Eas}];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
encode_name(Name) ->
list_to_binary(Name).
%% encode_fields(Fields) -> [JSON].
%% encode_field(Field) -> JSON.
%% Encode a record field.
encode_fields(Fs) ->
[ encode_field(F) || F <- Fs ].
encode_field(#field_t{id=Id,type=T}) ->
[{<<"name">>,encode_type(Id)},
{<<"type">>,[encode_type(T)]}].
%% encode_typedef(TypeDef) -> JSON.
encode_arg({typed, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, encode_name(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, encode_alias(Def)}].
Def = typedef_def(Type),
#{name => encode_name(Name),
vars => encode_tvars(Vars),
typedef => encode_type(Def)}.
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
[ #{name => encode_type(V)} || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, encode_name(N)}].
%% Encode type
encode_type({tvar, _, N}) -> encode_name(N);
encode_type({id, _, N}) -> encode_name(N);
encode_type({con, _, N}) -> encode_name(N);
encode_type({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_type({tuple_t, _, As}) -> #{tuple => encode_types(As)};
encode_type({bytes_t, _, Len}) -> #{bytes => Len};
encode_type({record_t, Fs}) -> #{record => encode_type_fields(Fs)};
encode_type({app_t, _, Id, Fs}) -> #{encode_type(Id) => encode_types(Fs)};
encode_type({variant_t, Cs}) -> #{variant => encode_types(Cs)};
encode_type({constr_t, _, C, As}) -> #{encode_type(C) => encode_types(As)};
encode_type({alias_t, Type}) -> encode_type(Type);
encode_type({fun_t, _, _, As, T}) -> #{function =>
#{arguments => encode_types(As),
returns => encode_type(T)}}.
encode_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
encode_types(Ts) -> [ encode_type(T) || T <- Ts ].
%% encode_stmt(Stmt) -> JSON.
encode_type_fields(Fs) -> [ encode_type_field(F) || F <- Fs ].
encode_stmt(E) ->
encode_expr(E).
encode_type_field({field_t, _, Id, T}) ->
#{name => encode_type(Id),
type => encode_type(T)}.
%% encode_exprs(Exprs) -> [JSON].
%% encode_expr(Expr) -> JSON.
encode_name(Name) when is_list(Name) ->
list_to_binary(Name);
encode_name(Name) when is_binary(Name) ->
Name.
encode_exprs(Es) ->
[ encode_expr(E) || E <- Es ].
%% Encode Expr
encode_exprs(Es) -> [ encode_expr(E) || E <- Es ].
encode_expr(#id{name=N}) -> encode_name(N);
encode_expr(#con{name=N}) -> encode_name(N);
encode_expr(#qid{names=Ns}) ->
encode_name(lists:join(".", Ns));
encode_expr(#qcon{names=Ns}) ->
encode_name(lists:join(".", Ns)); %?
encode_expr(#typed{expr=E}) ->
encode_expr(E);
encode_expr(#bool{bool=B}) -> B;
encode_expr(#int{value=V}) -> V;
encode_expr(#string{bin=B}) -> B;
encode_expr(#hash{hash=H}) -> H;
encode_expr(#tuple{args=As}) ->
Eas = encode_exprs(As),
[{<<"tuple">>,Eas}];
encode_expr(#list{args=As}) ->
Eas = encode_exprs(As),
[{<<"list">>,Eas}];
encode_expr(#app{func=F,args=As}) ->
encode_expr({id, _, N}) -> encode_name(N);
encode_expr({con, _, N}) -> encode_name(N);
encode_expr({qid, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_expr({qcon, _, Ns}) -> encode_name(lists:join(".", Ns));
encode_expr({typed, _, E}) -> encode_expr(E);
encode_expr({bool, _, B}) -> B;
encode_expr({int, _, V}) -> V;
encode_expr({string, _, S}) -> S;
encode_expr({tuple, _, As}) -> encode_exprs(As);
encode_expr({list, _, As}) -> encode_exprs(As);
encode_expr({bytes, _, B}) ->
Digits = byte_size(B),
<<N:Digits/unit:8>> = B,
list_to_binary(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id;
Lit == contract_pubkey; Lit == account_pubkey ->
aeser_api_encoder:encode(Lit, L);
encode_expr({app, _, {'-', _}, [{int, _, N}]}) ->
encode_expr({int, [], -N});
encode_expr({app, _, F, As}) ->
Ef = encode_expr(F),
Eas = encode_exprs(As),
[{<<"apply">>,[{<<"function">>,Ef},
{<<"arguments">>,Eas}]}];
#{Ef => Eas};
encode_expr({record, _, Flds}) -> maps:from_list(encode_fields(Flds));
encode_expr({map, _, KVs}) -> [ [encode_expr(K), encode_expr(V)] || {K, V} <- KVs ];
encode_expr({Op,_Ann}) ->
list_to_binary(atom_to_list(Op)).
error({encode_expr_todo, Op}).
%% decode(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
encode_fields(Flds) -> [ encode_field(F) || F <- Flds ].
decode(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)).
encode_field({field, _, [{proj, _, {id, _, Fld}}], Val}) ->
{encode_name(Fld), encode_expr(Val)}.
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts),
decode_funcs(Fs)].
do_render_aci_json(Json) ->
Contracts =
case Json of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
JText when is_binary(JText) ->
case jsx:decode(Json, [{labels, atom}, return_maps]) of
JArray when is_list(JArray) -> JArray;
JObject when is_map(JObject) -> [JObject];
_ -> error(bad_aci_json)
end
end,
DecodedContracts = [ decode_contract(C) || C <- Contracts ],
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name,
payable := Payable,
type_defs := Ts0,
functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
%% decode_func(#{name := init}) -> [];
decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) ->
[" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ",
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
decode_arg(#{type := T}) -> decode_type(T).
decode_types(Ets) ->
[ decode_type(Et) || Et <- Ets ].
decode_type(#{<<"tuple">> := Ets}) ->
decode_type(#{tuple := Ets}) ->
Ts = decode_types(Ets),
[$(,lists:join(",", Ts),$)];
decode_type(#{<<"record">> := Efs}) ->
case Ts of
[] -> ["unit"];
_ -> [$(,lists:join(" * ", Ts),$)]
end;
decode_type(#{record := Efs}) ->
Fs = decode_fields(Efs),
[${,lists:join(",", Fs),$}];
decode_type(#{<<"list">> := [Et]}) ->
decode_type(#{list := [Et]}) ->
T = decode_type(Et),
["list",$(,T,$)];
decode_type(#{<<"map">> := Ets}) ->
decode_type(#{map := Ets}) ->
Ts = decode_types(Ets),
["map",$(,lists:join(",", Ts),$)];
decode_type(#{<<"variant">> := Ets}) ->
decode_type(#{bytes := Len}) ->
["bytes(", integer_to_list(Len), ")"];
decode_type(#{variant := Ets}) ->
Ts = decode_types(Ets),
lists:join(" | ", Ts);
decode_type(#{function := #{arguments := Args, returns := R}}) ->
[decode_type(#{tuple => Args}), " => ", decode_type(R)];
decode_type(Econs) when is_map(Econs) -> %General constructor
[{Ec,Ets}] = maps:to_list(Econs),
C = decode_name(Ec),
Ts = decode_types(Ets),
[C,$(,lists:join(",", Ts),$)];
AppName = decode_name(Ec),
AppArgs = decode_types(Ets),
case AppArgs of
[] -> [AppName];
_ -> [AppName,$(,lists:join(", ", AppArgs),$)]
end;
decode_type(T) -> %Just raw names.
decode_name(T).
decode_name(En) ->
binary_to_list(En).
decode_name(En) when is_atom(En) -> erlang:atom_to_list(En);
decode_name(En) when is_binary(En) -> binary_to_list(En).
decode_fields(Efs) ->
[ decode_field(Ef) || Ef <- Efs ].
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
decode_field(#{name := En, type := Et}) ->
Name = decode_name(En),
Type = decode_type(Et),
[Name," : ",Type].
@@ -295,39 +309,41 @@ decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
%% Here we are only interested in the type definitions and ignore the
%% aliases. We find them as they always have variants.
decode_tdefs(Ts) -> [ decode_tdef(T) ||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
].
decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
" = ",decode_type(T),$\n].
decode_tdef(#{name := Name, vars := Vs, typedef := T}) ->
TypeDef = decode_type(T),
DefType = decode_deftype(T),
[" ", DefType, " ", decode_name(Name), decode_tvars(Vs), " = ", TypeDef, $\n].
decode_tvars([]) -> []; %No tvars, no parentheses
decode_deftype(#{record := _Efs}) -> "record";
decode_deftype(#{variant := _}) -> "datatype";
decode_deftype(_T) -> "type".
decode_tvars([]) -> []; %No tvars, no parentheses
decode_tvars(Vs) ->
Dvs = [ decode_tvar(V) || V <- Vs ],
[$(,lists:join(", ", Dvs),$)].
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
decode_tvar(#{name := N}) -> io_lib:format("~s", [N]).
payable(true) -> "payable ";
payable(false) -> "".
%% #contract{Ann, Con, [Declarations]}.
contract_name(#contract{con=#con{name=N}}) -> N.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_fun(D)].
contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, letfun) ].
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
[ D || D <- Decls, is_type(D) ].
contract_types(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, type_def) ].
is_fun({letfun, _, _, _, _, _}) -> true;
is_fun({fun_decl, _, _, _}) -> true;
is_fun(_) -> false.
%% To keep dialyzer happy and quiet.
%% namespace_name(#namespace{con=#con{name=N}}) -> N.
%%
%% namespace_funcs(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, letfun) ].
%%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
is_type({type_def, _, _, _, _}) -> true;
is_type(_) -> false.
sort_decls(Ds) ->
Sort = fun (D1, D2) ->
@@ -336,58 +352,12 @@ sort_decls(Ds) ->
end,
lists:sort(Sort, Ds).
%% #letfun{Ann, Id, [Arg], Type, Typedef}.
is_entrypoint(Node) -> aeso_syntax:get_ann(entrypoint, Node, false).
is_stateful(Node) -> aeso_syntax:get_ann(stateful, Node, false).
is_payable(Node) -> aeso_syntax:get_ann(payable, Node, false).
function_name(#letfun{id=#id{name=N}}) -> N.
typedef_name({type_def, _, {id, _, Name}, _, _}) -> Name.
function_args(#letfun{args=Args}) -> Args.
typedef_vars({type_def, _, _, Vars, _}) -> Vars.
function_type(#letfun{type=Type}) -> Type.
is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
%% parse(ContractString, Options) -> {ok,AST}.
%% Signal errors, the sophia compiler way. Sigh!
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error(Pos, ErrorString) ->
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
typedef_def({type_def, _, _, _, Def}) -> Def.
+1162 -407
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+525 -324
View File
File diff suppressed because it is too large Load Diff
+203 -19
View File
@@ -10,6 +10,7 @@
-module(aeso_builtins).
-export([ builtin_function/1
, bytes_to_raw_string/2
, check_event_type/1
, used_builtins/1 ]).
@@ -44,7 +45,9 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> [].
dep_closure(Deps) ->
@@ -60,12 +63,14 @@ v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}).
-define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, v(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
@@ -85,18 +90,18 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(BSL(X, B), op('bsl', ?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(I) when is_integer(I) -> {integer, I};
operand(T) -> T.
str_to_icode(String) when is_list(String) ->
str_to_icode(list_to_binary(String));
str_to_icode(BinStr) ->
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
check_event_type(Icode) ->
case maps:get(event_type, Icode) of
{variant_t, Cons} ->
@@ -117,11 +122,12 @@ check_event_type(EvtName, Ix, Type, Icode) ->
catch _:_ ->
error({EvtName, could_not_resolve_type, Type})
end,
case {Ix, VMType} of
{indexed, word} -> ok;
{notindexed, string} -> ok;
{indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
case {Ix, VMType, Type} of
{indexed, word, _} -> ok;
{notindexed, string, _} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end.
bfun(B, {IArgs, IExpr, IRet}) ->
@@ -131,6 +137,8 @@ builtin_function(BF) ->
case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete());
@@ -158,6 +166,12 @@ builtin_function(BF) ->
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_())
end.
@@ -171,16 +185,23 @@ builtin_event(EventT) ->
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) ->
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause =
fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
@@ -201,6 +222,17 @@ builtin_abort() ->
A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives
builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type),
@@ -437,6 +469,10 @@ builtin_baseX_int_pad(X = 10) ->
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
@@ -471,6 +507,77 @@ builtin_baseX_digits(X) ->
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
%% Two versions of this helper function, worker for sections not even 16 bytes long
%% and worker_x for the full sized chunks.
builtin_bytes_to_str_worker_x() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
},
word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
},
word}.
builtin_bytes_to_str_body(Var, N) when N < 16 ->
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
builtin_bytes_to_str_body(Var, 16) ->
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N < 32 ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
builtin_bytes_to_str_body(Var, 32) ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N > 32 ->
WholeWords = ((N + 31) div 32) - 1,
lists:append(
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|| I <- lists:seq(0, WholeWords - 1) ]) ++
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(w, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(p, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string}.
builtin_string_reverse() ->
{[{"s", string}],
?DEREF(n, s,
@@ -498,3 +605,80 @@ builtin_string_reverse_() ->
builtin_addr_to_str() ->
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
%% At most one word
%% | ..... | ========= | ........ |
%% Offs ^ ^- Len -^ TotalLen ^
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
%% Bytes are packed into a single word
Masked =
case Offs of
0 -> Bytes;
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
end,
Unpadded =
case 32 - (Offs + Len) of
0 -> Masked;
N -> ?BSR(Masked, N)
end,
case Len of
32 -> Unpadded;
_ -> ?BSL(Unpadded, 32 - Len)
end;
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
%% Might read one word more than necessary.
Word = op('!', Offs, Bytes),
case Len == 32 of
true -> Word;
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
end.
builtin_bytes_concat(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Words = fun(N) -> (N + 31) div 32 end,
WordsRes = Words(A + B),
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
(I) when 32 * I < A ->
Len = A rem 32,
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
?ADD(Hi, ?BSR(Lo, Len));
(I) ->
Offs = 32 * I - A,
Len = min(32, B - Offs),
bytes_slice(Offs, Len, B, ?V(b))
end,
Body =
case {A, B} of
{0, _} -> ?V(b);
{_, 0} -> ?V(a);
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
end,
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
builtin_bytes_split(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Word = fun(I, Max) ->
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
end,
Body =
case {A, B} of
{0, _} -> [?I(0), ?V(c)];
{_, 0} -> [?V(c), ?I(0)];
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
end,
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
bytes_to_raw_string(N, Term) when N =< 32 ->
{tuple, [?I(N), Term]};
bytes_to_raw_string(N, Term) when N > 32 ->
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
end,
Words = (N + 31) div 32,
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
+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)).
+428 -256
View File
@@ -2,7 +2,7 @@
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Compiler from Aeterinty Sophia language to the Aeternity VM, aevm.
%%% Compiler from Aeterinty Sophia language to both AEVM and FATE VM.
%%% @end
%%% Created : 12 Dec 2017
%%%-------------------------------------------------------------------
@@ -12,12 +12,18 @@
, file/2
, from_string/2
, check_call/4
, create_calldata/3
, create_calldata/3 %% deprecated
, create_calldata/4
, version/0
, numeric_version/0
, sophia_type_to_typerep/1
, to_sophia_value/4
, to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/5
, decode_calldata/3
, decode_calldata/3 %% deprecated
, decode_calldata/4
, parse/2
, add_include_path/2
, validate_byte_code/3
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -31,9 +37,14 @@
| pp_icode
| pp_assembler
| pp_bytecode
| no_code
| keep_included
| debug_mode
| {backend, aevm | fate}
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}.
| {src_file, string()}
| {aci, aeso_aci:aci_type()}.
-type options() :: [option()].
-export_type([ option/0
@@ -59,68 +70,121 @@ version() ->
{ok, list_to_binary(VsnString)}
end.
-spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) ->
Dir = filename:dirname(Filename),
{ok, Cwd} = file:get_cwd(),
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
-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(), options()) -> {ok, map()} | {error, binary()}.
file(File, Options) ->
-spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(Filename) ->
file(Filename, []).
-spec file(string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(File, Options0) ->
Options = add_include_path(File, Options0),
case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} ->
ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
Msg = lists:flatten([File,": ",file:format_error(Error)]),
{error, [aeso_errors:new(file_error, Msg)]}
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
add_include_path(File, Options) ->
case lists:keymember(include, 1, Options) of
true -> Options;
false ->
Dir = filename:dirname(File),
{ok, Cwd} = file:get_cwd(),
[{include, {file_system, [Cwd, Dir]}} | Options]
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
try
#{icode := Icode} = string_to_icode(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
{ok, #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo
}}
from_string1(Backend, ContractString, Options)
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
throw:{error, Errors} -> {error, Errors}
end.
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
string_to_icode(ContractString, Options0) ->
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
from_string1(aevm, ContractString, Options) ->
#{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode)
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) ->
#{ fcode := FCode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
undefined ->
Result;
Type ->
{ok, Aci} = aeso_aci:from_typed_ast(Type, FoldedTypedAst),
maps:put(aci, Aci, Result)
end.
-spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
pp_typed_ast(TypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
#{ typed_ast => TypedAst,
type_env => TypeEnv,
icode => Icode }.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(UnfoldedTypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(UnfoldedTypedAst, Options),
pp_icode(Icode, Options),
#{ icode => Icode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
#{ fcode => Fcode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
end.
-define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
@@ -131,14 +195,15 @@ join_errors(Prefix, Errors, Pfun) ->
%% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()}
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]}
| {error, [aeso_errors:error()]}
when Type :: term().
check_call(Source, "init" = FunName, Args, Options) ->
PatchFun = fun(T) -> {tuple, [typerep, T]} end,
case check_call(Source, FunName, Args, Options, PatchFun) of
case check_call1(Source, FunName, Args, Options) of
Err = {error, _} when Args == [] ->
%% Try with default init-function
case check_call(insert_init_function(Source, Options), FunName, Args, Options, PatchFun) of
case check_call1(insert_init_function(Source, Options), FunName, Args, Options) of
{error, _} -> Err; %% The first error is most likely better...
Res -> Res
end;
@@ -146,50 +211,76 @@ check_call(Source, "init" = FunName, Args, Options) ->
Res
end;
check_call(Source, FunName, Args, Options) ->
PatchFun = fun(T) -> T end,
check_call(Source, FunName, Args, Options, PatchFun).
check_call1(Source, FunName, Args, Options).
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
check_call1(ContractString0, FunName, Args, Options) ->
try
%% First check the contract without the __call function and no permissive literals
#{} = string_to_icode(ContractString0, Options),
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
{ok, FunName, {ArgVMTypes, PatchFun(RetVMType)}, ArgTerms}
case proplists:get_value(backend, Options, aevm) of
aevm ->
%% First check the contract without the __call function
#{ast := Ast} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
#{unfolded_typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 =
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{ fcode := OrgFcode
, ast := Ast } = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_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)}
throw:{error, Errors} -> {error, Errors}
end.
arguments_of_body(CallName, _FunName, Fcode) ->
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
{def, _FName, Args} = Body,
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
first_none_match(_CallName, _Hashes, []) ->
error(unable_to_find_unique_call_name);
first_none_match(CallName, Hashes, [Char|Chars]) ->
case not lists:member(aeb_fate_code:symbol_identifier(list_to_binary(CallName)), Hashes) of
true ->
CallName;
false ->
first_none_match(?CALL_NAME++[Char], Hashes, Chars)
end.
%% Add the __call function to a contract.
-spec insert_call_function(string(), string(), [string()], options()) -> string().
insert_call_function(Code, FunName, Args, Options) ->
Ast = parse(Code, Options),
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
insert_call_function(Ast, Code, Call, FunName, Args) ->
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "),
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
@@ -199,7 +290,7 @@ insert_init_function(Code, Options) ->
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "), "function init() = ()\n"
lists:duplicate(Ind, " "), "entrypoint init() = ()\n"
]).
last_contract_indent(Decls) ->
@@ -209,168 +300,174 @@ last_contract_indent(Decls) ->
end.
-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}]).
-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) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, _Options) ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err
to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of
aevm ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} ->
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
try aeb_fate_encoding:deserialize(Data) of
Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end
end;
to_sophia_value(ContractString, FunName, ok, Data, Options) ->
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
try
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, Options),
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, translate_vm_value(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))],
fun (E) -> E end)}
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
fate ->
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
throw:{error, Errors} -> {error, Errors}
end.
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
%% TODO: somewhere else
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
{hash, [], <<Hi:256, Lo:256>>};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
none -> {con, [], "None"};
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [translate_vm_value(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
translate_vm_value(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
translate_vm_value(VmTypes, ConType, Args);
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}.
| {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) ->
case check_call(Code, Fun, Args, []) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
create_calldata(Code, Fun, Args, [{backend, aevm}]).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end
end.
-spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}.
| {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
#{ typed_ast := TypedAst,
type_env := TypeEnv,
icode := Icode } = string_to_icode(ContractString, []),
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, Args, _} = get_decode_type(FunName, TypedAst),
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(DropArg, Args),
GetType = fun({typed, _, _, T}) -> T; (T) -> T end,
ArgTypes = lists:map(GetType, Args),
Type0 = {tuple_t, [], ArgTypes},
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]))],
fun (E) -> E end)}
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
%% Values are Sophia expressions in AST format
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
[FateArgs, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _} ->
Msg = io_lib:format("Failed to decode calldata binary\n", []),
{error, [aeso_errors:new(data_error, Msg)]}
end
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
throw:{error, Errors} -> {error, Errors}
end.
get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
[] -> error({missing_call_function, Funs})
[] -> error_missing_call_function()
end.
-dialyzer({nowarn_function, error_missing_call_function/0}).
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
@@ -378,19 +475,27 @@ get_call_type([{contract, _, _, Defs}]) ->
{app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call};
[] -> {error, missing_call_function}
[] -> error_missing_call_function()
end;
get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract
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}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
case lists:flatmap(GetType, Defs) of
[{Args, Ret}] -> {ok, Args, Ret};
[] -> {error, missing_function}
[] ->
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end
end;
get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
@@ -399,6 +504,7 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str;
@@ -422,6 +528,14 @@ icode_to_term(T = {map, KT, VT}, M) ->
#{};
_ -> throw({todo, M})
end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) ->
throw({todo, typerep});
icode_to_term(T, V) ->
@@ -445,8 +559,9 @@ to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep)
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end,
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name),
not lists:member(private, Attrs)
@@ -459,9 +574,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_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_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_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp(Code, Options, Option, PPFun) ->
case proplists:lookup(Option, Options) of
{Option, true} ->
@@ -470,49 +587,104 @@ pp(Code, Options, Option, PPFun) ->
ok
end.
%% -- Byte code validation ---------------------------------------------------
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
case proplists:get_value(backend, Options, aevm) of
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
fate ->
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(fate, Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end
end.
compare_fate_code(FCode1, FCode2) ->
Funs1 = aeb_fate_code:functions(FCode1),
Funs2 = aeb_fate_code:functions(FCode2),
Syms1 = aeb_fate_code:symbols(FCode1),
Syms2 = aeb_fate_code:symbols(FCode2),
FunHashes1 = maps:keys(Funs1),
FunHashes2 = maps:keys(Funs2),
case FunHashes1 == FunHashes2 of
false ->
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
{error, Msg};
true ->
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
maps:to_list(Funs2)) ]) of
[] -> ok;
Errs -> {error, Errs}
end
end.
compare_fate_fun(_Name, Fun, Fun) -> [];
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
[io_lib:format("- The attributes of the function ~s differ:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
[io_lib:format("- The type of the function ~s differs:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
pp_fate_sig({[Arg], Res}) ->
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
pp_fate_sig({Args, Res}) ->
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
pp_fate_type(T) -> io_lib:format("~w", [T]).
%% -------------------------------------------------------------------
%% TODO: Tempoary parser hook below...
-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}.
sophia_type_to_typerep(String) ->
{ok, Ast} = aeso_parser:type(String),
Ast = aeso_parser:run_parser(aeso_parser:type(), String),
try aeso_ast_to_icode:ast_typerep(Ast) of
Type -> {ok, Type}
catch _:_ -> {error, bad_type}
end.
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse(Text, sets:new(), Options).
parse_error(Pos, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Included, Options) ->
aeso_parser:string(Text, Included, Options).
read_contract(Name) ->
file:read_file(Name).
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.
File diff suppressed because it is too large Load Diff
+15 -1
View File
@@ -13,8 +13,10 @@
pp/1,
set_name/2,
set_namespace/2,
set_payable/2,
enter_namespace/2,
get_namespace/1,
in_main_contract/1,
qualify/2,
set_functions/2,
map_typerep/2,
@@ -48,6 +50,7 @@
, type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag
, options => [any()]
, payable => boolean()
}.
pp(Icode) ->
@@ -65,16 +68,19 @@ new(Options) ->
, types => builtin_types()
, type_vars => #{}
, constructors => builtin_constructors()
, options => Options}.
, options => Options
, payable => false }.
builtin_types() ->
Word = fun([]) -> word end,
#{ "bool" => Word
, "int" => Word
, "char" => Word
, "bits" => Word
, "string" => fun([]) -> string end
, "address" => Word
, "hash" => Word
, "unit" => fun([]) -> {tuple, []} end
, "signature" => fun([]) -> {tuple, [word, word]} end
, "oracle" => fun([_, _]) -> word end
, "oracle_query" => fun([_, _]) -> word end
@@ -103,6 +109,10 @@ new_env() ->
set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode).
-spec set_payable(boolean(), icode()) -> icode().
set_payable(Payable, Icode) ->
maps:put(payable, Payable, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
@@ -112,6 +122,10 @@ enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
+5 -3
View File
@@ -27,7 +27,7 @@ convert(#{ contract_name := _ContractName
},
_Options) ->
%% Create a function dispatcher
DispatchFun = {"_main", [], [{"arg", "_"}],
DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]},
@@ -44,7 +44,7 @@ convert(#{ contract_name := _ContractName
%% taken from the stack
StopLabel = make_ref(),
StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "_main"),
MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top
@@ -343,6 +343,8 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
%% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
-299
View File
@@ -1,299 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Fate backend for Sophia compiler
%%% @end
%%% Created : 11 Jan 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_fate).
-include("aeso_icode.hrl").
-export([compile/2]).
%% -- Preamble ---------------------------------------------------------------
-define(TODO(What), error({todo, ?FILE, ?LINE, ?FUNCTION_NAME, What})).
-define(i(__X__), {immediate, __X__}).
-define(a, {stack, 0}).
-record(env, { args = [], stack = [], tailpos = true }).
%% -- Debugging --------------------------------------------------------------
%% debug(Options, Fmt) -> debug(Options, Fmt, []).
debug(Options, Fmt, Args) ->
case proplists:get_value(debug, Options, true) of
true -> io:format(Fmt, Args);
false -> ok
end.
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(ICode, Options) ->
#{ contract_name := _ContractName,
state_type := _StateType,
functions := Functions } = ICode,
SFuns = functions_to_scode(Functions, Options),
SFuns1 = optimize_scode(SFuns, Options),
to_basic_blocks(SFuns1, Options).
functions_to_scode(Functions, Options) ->
maps:from_list(
[ {list_to_binary(Name), function_to_scode(Name, Args, Body, Type, Options)}
|| {Name, _Ann, Args, Body, Type} <- Functions, Name /= "init" ]). %% TODO: skip init for now
function_to_scode(Name, Args, Body, Type, Options) ->
debug(Options, "Compiling ~p ~p : ~p ->\n ~p\n", [Name, Args, Type, Body]),
ArgTypes = [ icode_type_to_fate(T) || {_, T} <- Args ],
ResType = icode_type_to_fate(Type),
SCode = to_scode(init_env(Args), Body),
debug(Options, " scode: ~p\n", [SCode]),
{{ArgTypes, ResType}, SCode}.
%% -- Types ------------------------------------------------------------------
%% TODO: the Fate types don't seem to be specified anywhere...
icode_type_to_fate(word) -> integer;
icode_type_to_fate(string) -> string;
icode_type_to_fate({tuple, Types}) ->
{tuple, lists:map(fun icode_type_to_fate/1, Types)};
icode_type_to_fate({list, Type}) ->
{list, icode_type_to_fate(Type)};
icode_type_to_fate(typerep) -> typerep;
icode_type_to_fate(Type) -> ?TODO(Type).
%% -- Phase I ----------------------------------------------------------------
%% Icode to structured assembly
%% -- Environment functions --
init_env(Args) ->
#env{ args = Args, stack = [], tailpos = true }.
push_env(Type, Env) ->
Env#env{ stack = [{"_", Type} | Env#env.stack] }.
notail(Env) -> Env#env{ tailpos = false }.
lookup_var(#env{ args = Args, stack = S }, X) ->
case {keyfind_index(X, 1, S), keyfind_index(X, 1, Args)} of
{false, false} -> false;
{false, Arg} -> {arg, Arg};
{Local, _} -> {stack, Local}
end.
%% -- The compiler --
to_scode(_Env, #integer{ value = N }) ->
[aeb_fate_code:push(?i(N))]; %% Doesn't exist (yet), translated by desugaring
to_scode(Env, #var_ref{name = X}) ->
case lookup_var(Env, X) of
false -> error({unbound_variable, X, Env});
{stack, N} -> [aeb_fate_code:dup(?i(N))];
{arg, N} -> [aeb_fate_code:push({arg, N})]
end;
to_scode(Env, #binop{ op = Op, left = A, right = B }) ->
[ to_scode(notail(Env), B)
, to_scode(push_env(binop_type_r(Op), Env), A)
, binop_to_scode(Op) ];
to_scode(Env, #ifte{decision = Dec, then = Then, else = Else}) ->
[ to_scode(notail(Env), Dec)
, {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
to_scode(_Env, Icode) -> ?TODO(Icode).
%% -- Operators --
binop_types('+') -> {word, word};
binop_types('-') -> {word, word};
binop_types('==') -> {word, word};
binop_types(Op) -> ?TODO(Op).
%% binop_type_l(Op) -> element(1, binop_types(Op)).
binop_type_r(Op) -> element(2, binop_types(Op)).
binop_to_scode('+') -> add_a_a_a(); %% Optimization introduces other variants
binop_to_scode('-') -> sub_a_a_a();
binop_to_scode('==') -> eq_a_a_a().
% binop_to_scode(Op) -> ?TODO(Op).
add_a_a_a() -> aeb_fate_code:add(?a, ?a, ?a).
sub_a_a_a() -> aeb_fate_code:sub(?a, ?a, ?a).
eq_a_a_a() -> aeb_fate_code:eq(?a, ?a, ?a).
%% -- Phase II ---------------------------------------------------------------
%% Optimize
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
flatten_s({ifte, Then, Else}) -> {ifte, flatten(Then), flatten(Else)};
flatten_s(I) -> I.
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
Code0 = flatten(Code),
debug(Options, "Optimizing ~s\n", [Name]),
debug(Options, " original : ~p\n", [Code0]),
Code1 = simplify(Code0),
debug(Options, " simplified: ~p\n", [Code1]),
Code2 = desugar(Code1),
debug(Options, " desugared : ~p\n", [Code2]),
{{Args, Res}, Code2}.
simplify([]) -> [];
simplify([I | Code]) ->
simpl_top(simpl_s(I), simplify(Code)).
simpl_s({ifte, Then, Else}) ->
{ifte, simplify(Then), simplify(Else)};
simpl_s(I) -> I.
%% add_i 0 --> nop
simpl_top({'ADD', _, ?i(0), _}, Code) -> Code;
%% push n, add_a --> add_i n
simpl_top({'PUSH', ?a, ?i(N)},
[{'ADD', ?a, ?a, ?a} | Code]) ->
simpl_top( aeb_fate_code:add(?a, ?i(N), ?a), Code);
%% push n, add_i m --> add_i (n + m)
simpl_top({'PUSH', ?a, ?i(N)}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top(aeb_fate_code:push(?i(N + M)), Code);
%% add_i n, add_i m --> add_i (n + m)
simpl_top({'ADD', ?a, ?i(N), ?a}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
simpl_top({'ADD', ?a, ?i(N + M), ?a}, Code);
simpl_top(I, Code) -> [I | Code].
%% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_code:inc()];
desugar({ifte, Then, Else}) -> [{ifte, desugar(Then), desugar(Else)}];
desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I].
%% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks
to_basic_blocks(Funs, Options) ->
maps:from_list([ {Name, {{Args, Res},
bb(Name, Code ++ [aeb_fate_code:return()], Options)}}
|| {Name, {{Args, Res}, Code}} <- maps:to_list(Funs) ]).
bb(Name, Code, Options) ->
Blocks0 = blocks(Code),
Blocks = optimize_blocks(Blocks0),
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
BBs = [ set_labels(Labels, B) || B <- Blocks ],
debug(Options, "Final code for ~s:\n ~p\n", [Name, BBs]),
maps:from_list(BBs).
%% -- Break up scode into basic blocks --
blocks(Code) ->
Top = make_ref(),
blocks([{Top, Code}], []).
blocks([], Acc) ->
lists:reverse(Acc);
blocks([{Ref, Code} | Blocks], Acc) ->
block(Ref, Code, [], Blocks, Acc).
block(Ref, [], CodeAcc, Blocks, BlockAcc) ->
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
block(Ref, [{ifte, Then, Else} | Code], Acc, Blocks, BlockAcc) ->
ThenLbl = make_ref(),
RestLbl = make_ref(),
block(Ref, Else ++ [{jump, RestLbl}],
[{jumpif, ThenLbl} | Acc],
[{ThenLbl, Then ++ [{jump, RestLbl}]},
{RestLbl, Code} | Blocks],
BlockAcc);
block(Ref, [I | Code], Acc, Blocks, BlockAcc) ->
block(Ref, Code, [I | Acc], Blocks, BlockAcc).
%% -- Reorder, inline, and remove dead blocks --
optimize_blocks(Blocks) ->
%% We need to look at the last instruction a lot, so reverse all blocks.
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
RBlocks = Rev(Blocks),
RBlockMap = maps:from_list(RBlocks),
RBlocks1 = reorder_blocks(RBlocks, []),
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
RBlocks3 = remove_dead_blocks(RBlocks2),
Rev(RBlocks3).
%% Choose the next block based on the final jump.
reorder_blocks([], Acc) ->
lists:reverse(Acc);
reorder_blocks([{Ref, Code} | Blocks], Acc) ->
reorder_blocks(Ref, Code, Blocks, Acc).
reorder_blocks(Ref, Code, Blocks, Acc) ->
Acc1 = [{Ref, Code} | Acc],
case Code of
['RETURN'|_] -> reorder_blocks(Blocks, Acc1);
[{'RETURNR', _}|_] -> reorder_blocks(Blocks, Acc1);
[{jump, L}|_] ->
NotL = fun({L1, _}) -> L1 /= L end,
case lists:splitwith(NotL, Blocks) of
{Blocks1, [{L, Code1} | Blocks2]} ->
reorder_blocks(L, Code1, Blocks1 ++ Blocks2, Acc1);
{_, []} -> reorder_blocks(Blocks, Acc1)
end
end.
%% Inline short blocks ( 2 instructions)
inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
case maps:get(L, BlockMap, nocode) of
Dest when length(Dest) < 3 ->
%% Remove Ref to avoid infinite loops
inline_block(maps:remove(Ref, BlockMap), L, Dest) ++ Code;
_ -> Code0
end;
inline_block(_, _, Code) -> Code.
%% Remove unused blocks
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
BlockMap = maps:from_list(Blocks),
LiveBlocks = chase_labels([Top], BlockMap, #{}),
[ B || B = {L, _} <- Blocks, maps:is_key(L, LiveBlocks) ].
chase_labels([], _, Live) -> Live;
chase_labels([L | Ls], Map, Live) ->
Code = maps:get(L, Map),
Jump = fun({jump, A}) -> [A || not maps:is_key(A, Live)];
({jumpif, A}) -> [A || not maps:is_key(A, Live)];
(_) -> [] end,
New = lists:flatmap(Jump, Code),
chase_labels(New ++ Ls, Map, Live#{ L => true }).
%% -- Translate label refs to indices --
set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
set_labels(Labels, {jump, Ref}) -> aeb_fate_code:jump(maps:get(Ref, Labels));
set_labels(Labels, {jumpif, Ref}) -> aeb_fate_code:jumpif(?a, maps:get(Ref, Labels));
set_labels(_, I) -> I.
%% -- Helpers ----------------------------------------------------------------
with_ixs(Xs) ->
lists:zip(lists:seq(0, length(Xs) - 1), Xs).
keyfind_index(X, J, Xs) ->
case [ I || {I, E} <- with_ixs(Xs), X == element(J, E) ] of
[I | _] -> I;
[] -> false
end.
+75 -11
View File
@@ -9,12 +9,14 @@
-module(aeso_parse_lib).
-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,
left/2, right/2, between/3, optional/1,
many/1, many1/1, sep/2, sep1/2,
infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
%% -- Types ------------------------------------------------------------------
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
@@ -72,25 +74,31 @@
%% first argument. I.e. no backtracking to the second argument if the first
%% fails.
trampoline({bounce, Cont}) when is_function(Cont, 0) ->
trampoline(Cont());
trampoline(Res) ->
Res.
-define(BOUNCE(X), {bounce, fun() -> X end}).
%% Apply a parser to its continuation. This compiles a parser to its low-level representation.
-spec apply_p(parser(A), fun((A) -> parser1(B))) -> parser1(B).
apply_p(?lazy(F), K) -> apply_p(F(), K);
apply_p(?fail(Err), _) -> {fail, Err};
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(apply_p(Q, K), R) end,
apply_p(P, K), Ps);
apply_p(?choice([P | Ps]), K) -> lists:foldl(fun(Q, R) -> choice1(trampoline(apply_p(Q, K)), R) end,
trampoline(apply_p(P, K)), Ps);
apply_p(?bind(P, F), K) -> apply_p(P, fun(X) -> apply_p(F(X), K) end);
apply_p(?right(P, Q), K) -> apply_p(P, fun(_) -> apply_p(Q, K) end);
apply_p(?left(P, Q), K) -> apply_p(P, fun(X) -> apply_p(Q, fun(_) -> K(X) end) end);
apply_p(?map(F, P), K) -> apply_p(P, fun(X) -> K(F(X)) end);
apply_p(?layout, K) -> {layout, K, {fail, {expected, layout_block}}};
apply_p(?tok(Atom), K) -> {tok_bind, #{Atom => K}};
apply_p(?return(X), K) -> K(X);
apply_p(?return(X), K) -> ?BOUNCE(K(X));
apply_p([P | Q], K) -> apply_p(P, fun(H) -> apply_p(Q, fun(T) -> K([H | T]) end) end);
apply_p(T, K) when is_tuple(T) -> apply_p(tuple_to_list(T), fun(Xs) -> K(list_to_tuple(Xs)) end);
apply_p(M, K) when is_map(M) ->
{Keys, Ps} = lists:unzip(maps:to_list(M)),
apply_p(Ps, fun(Vals) -> K(maps:from_list(lists:zip(Keys, Vals))) end);
apply_p(X, K) -> K(X).
apply_p(X, K) -> ?BOUNCE(K(X)).
%% -- Primitive combinators --------------------------------------------------
@@ -98,6 +106,10 @@ apply_p(X, K) -> K(X).
-spec lazy(fun(() -> parser(A))) -> parser(A).
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.
-spec fail(term()) -> parser(none()).
fail(Err) -> ?fail(Err).
@@ -154,8 +166,8 @@ layout() -> ?layout.
%% @doc Parse a sequence of tokens using a parser. Fails if the parse is ambiguous.
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
parse(P, S) ->
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
{[], {Pos, Err}} -> {error, {Pos, parse_error, flatten_error(Err)}};
case parse1(trampoline(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end)), S) of
{[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
{[A], _} -> {ok, A};
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
end.
@@ -235,7 +247,7 @@ col(T) when is_tuple(T) -> element(2, pos(T)).
%% If both parsers want the next token we grab it and merge the continuations.
choice1({tok_bind, Map1}, {tok_bind, Map2}) ->
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(F(T), G(T)) end end, Map1, Map2)};
{tok_bind, merge_with(fun(F, G) -> fun(T) -> choice1(trampoline(F(T)), trampoline(G(T))) end end, Map1, Map2)};
%% If both parsers fail we combine the error messages. If only one fails we discard it.
choice1({fail, E1}, {fail, E2}) -> {fail, add_error(E1, E2)};
@@ -249,7 +261,7 @@ choice1(P, {return_plus, X, Q}) -> {return_plus, X, choice1(P, Q)};
%% If both sides want a layout block we combine them. If only one side wants a layout block we
%% will commit to a layout block is there is one.
choice1({layout, F, P}, {layout, G, Q}) ->
{layout, fun(N) -> choice1(F(N), G(N)) end, choice1(P, Q)};
{layout, fun(N) -> choice1(trampoline(F(N)), trampoline(G(N))) end, choice1(P, Q)};
choice1({layout, F, P}, Q) -> {layout, F, choice1(P, Q)};
choice1(P, {layout, G, Q}) -> {layout, G, choice1(P, Q)}.
@@ -272,6 +284,8 @@ parse1(P, S) ->
%% The main work horse. Returns a list of possible parses and an error message in case parsing
%% fails.
-spec parse1(parser1(A), #ts{}, [A], term()) -> {[A], error()}.
parse1({bounce, F}, Ts, Acc, Err) ->
parse1(F(), Ts, Acc, Err);
parse1({tok_bind, Map}, Ts, Acc, Err) ->
case next_token(Ts) of
{T, Ts1} ->
@@ -285,7 +299,7 @@ parse1({tok_bind, Map}, Ts, Acc, Err) ->
%% y + y)(4)
case maps:get(vclose, Map, '$not_found') of
'$not_found' ->
{Acc, unexpected_token_error(Ts, T)};
{Acc, unexpected_token_error(Ts, maps:keys(Map), T)};
F ->
VClose = {vclose, pos(T)},
Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }),
@@ -322,12 +336,52 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T);
current_pos(#ts{ last = T }) -> end_pos(pos(T)).
-spec mk_error(#ts{}, term()) -> error().
mk_error(_Ts, {Pos, Err}) ->
{Pos, Err};
mk_error(Ts, Err) ->
{current_pos(Ts), Err}.
-spec unexpected_token_error(#ts{}, token()) -> error().
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.
-spec next_token(#ts{}) -> false | {token(), #ts{}}.
@@ -411,3 +465,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1))
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,
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
fail/0, fail/1, 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]).
+277 -94
View File
@@ -3,40 +3,88 @@
%%% Description :
%%% Created : 1 Mar 2018 by Ulf Norell
-module(aeso_parser).
-compile({no_auto_import,[map_get/2]}).
-export([string/1,
string/2,
type/1]).
string/3,
auto_imports/1,
hash_include/2,
decl/0,
type/0,
body/0,
maybe_block/1,
run_parser/2,
run_parser/3]).
-include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-type parse_result() :: {ok, aeso_syntax:ast()}
| {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}.
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
-type include_hash() :: {string(), binary()}.
escape_errors({ok, Ok}) ->
Ok;
escape_errors({error, Err}) ->
parse_error(Err).
-spec string(string()) -> parse_result().
string(String) ->
string(String, []).
string(String, sets:new(), []).
-spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) ->
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
expand_includes(AST, Opts);
Err = {error, _} ->
Err
case lists:keyfind(src_file, 1, Opts) of
{src_file, File} -> string(String, sets:add_element(File, sets:new()), Opts);
false -> string(String, sets:new(), Opts)
end.
type(String) ->
parse_and_scan(type(), String, []).
-spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result().
string(String, Included, Opts) ->
AST = run_parser(file(), String, Opts),
case expand_includes(AST, Included, Opts) of
{ok, AST1} -> AST1;
{error, Err} -> parse_error(Err)
end.
run_parser(P, Inp) ->
escape_errors(parse_and_scan(P, Inp, [])).
run_parser(P, Inp, Opts) ->
escape_errors(parse_and_scan(P, Inp, Opts)).
parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error
{error, {{Input, Pos}, _}} ->
{error, {Pos, scan_error, Input}}
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 ----------------------------------------------------------
file() -> choice([], block(decl())).
@@ -46,8 +94,10 @@ decl() ->
choice(
%% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, _2})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, pragma()
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -60,13 +110,40 @@ decl() ->
, ?RULE(keyword(datatype), id(), type_vars(), tok('='), typedef(variant), {type_def, _1, _2, _3, _5})
%% Function declarations
, ?RULE(modifiers(), keyword(function), id(), tok(':'), type(), add_modifiers(_1, {fun_decl, _2, _3, _5}))
, ?RULE(modifiers(), keyword(function), fundef(), add_modifiers(_1, set_pos(get_pos(_2), _3)))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
, ?RULE(modifiers(), fun_or_entry(), maybe_block(fundef_or_decl()), fun_block(_1, _2, _3))
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])).
fun_block(Mods, Kind, [Decl]) ->
add_modifiers(Mods, Kind, set_pos(get_pos(Kind), Decl));
fun_block(Mods, Kind, Decls) ->
{block, get_ann(Kind), [ add_modifiers(Mods, Kind, Decl) || Decl <- Decls ]}.
fundef_or_decl() ->
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
fundef()]).
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() ->
choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]).
modifiers() ->
many(choice([token(stateful), token(public), token(private), token(internal)])).
many(choice([token(stateful), token(payable), token(private), token(public)])).
add_modifiers(Mods, Entry = {entrypoint, _}, Node) ->
add_modifiers(Mods ++ [Entry], Node);
add_modifiers(Mods, {function, _}, Node) ->
add_modifiers(Mods, Node).
add_modifiers([], Node) -> Node;
add_modifiers(Mods = [Tok | _], Node) ->
@@ -87,7 +164,7 @@ constructors() ->
sep1(constructor(), tok('|')).
constructor() -> %% TODO: format for Con() vs Con
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
con_args() -> paren_list(con_arg()).
@@ -99,27 +176,24 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
%% -- Let declarations -------------------------------------------------------
letdecl() ->
choice(
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
letdef() -> choice(valdef(), fundef()).
valdef() ->
choice(
?RULE(id(), tok('='), body(), {letval, [], _1, type_wildcard(), _3}),
?RULE(id(), tok(':'), type(), tok('='), body(), {letval, [], _1, _3, _5})).
?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}).
fundef() ->
choice(
[ ?RULE(id(), args(), tok('='), body(), {letfun, [], _1, _2, type_wildcard(), _4})
, ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, [], _1, _2, _4, _6})
[ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4})
, ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6})
]).
args() -> paren_list(arg()).
args() -> paren_list(pattern()).
lam_args() -> paren_list(arg()).
arg() -> choice(
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard()}),
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
%% -- Types ------------------------------------------------------------------
@@ -131,24 +205,35 @@ type() -> ?LAZY_P(type100()).
type100() -> type200().
type200() ->
?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)).
?RULE(many({type300(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type400().
type300() ->
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
type400() ->
?RULE(typeAtom(), optional(type_args()),
choice(
[?RULE(typeAtom(), optional(type_args()),
case _2 of
none -> _1;
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
end).
end),
?RULE(id("bytes"), parens(token(int)),
{bytes_t, get_ann(_1), element(3, _2)})
]).
typeAtom() ->
?LAZY_P(choice(
[ id(), token(con), token(qcon), token(qid), tvar()
, ?RULE(keyword('('), comma_sep(type()), tok(')'), tuple_t(_1, _2))
[ parens(type())
, args_t()
, id(), token(con), token(qcon), token(qid), tvar()
])).
fun_domain() -> ?RULE(?LAZY_P(type300()), fun_domain(_1)).
args_t() ->
?LAZY_P(choice(
[ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
%% Singleton case handled separately
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
])).
%% -- Statements -------------------------------------------------------------
@@ -169,7 +254,7 @@ branch() ->
?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}).
pattern() ->
?LET_P(E, expr500(), parse_pattern(E)).
?LET_P(E, expr(), parse_pattern(E)).
%% -- Expressions ------------------------------------------------------------
@@ -179,7 +264,7 @@ expr100() ->
Expr100 = ?LAZY_P(expr100()),
Expr200 = ?LAZY_P(expr200()),
choice(
[ ?RULE(args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
, ?RULE(Expr200, optional(right(tok(':'), type())),
case _2 of
@@ -203,19 +288,33 @@ exprAtom() ->
?LAZY_P(begin
Expr = ?LAZY_P(expr()),
choice(
[ id(), con(), token(qid), token(qcon)
, token(hash), token(string), token(char)
[ id_or_addr(), con(), token(qid), token(qcon)
, token(bytes), token(string), token(char)
, token(int)
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
, {bool, keyword(true), true}
, {bool, keyword(false), false}
, ?RULE(brace_list(?LAZY_P(field_assignment())), record(_1))
, ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs))
, {list, [], bracket_list(Expr)}
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
])
end).
comprehension_exp() ->
?LAZY_P(choice(
[ comprehension_bind()
, letdecl()
, comprehension_if()
])).
comprehension_if() ->
?RULE(keyword('if'), parens(expr()), {comprehension_if, _1, _2}).
comprehension_bind() ->
?RULE(pattern(), tok('<-'), expr(), {comprehension_bind, _1, _3}).
arg_expr() ->
?LAZY_P(
choice([ ?RULE(id(), tok('='), expr(), {named_arg, [], _1, _3})
@@ -241,7 +340,7 @@ map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
elim(E, []) -> E;
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
elim(E, [{app, Ann, Args} | Es]) -> elim({app, 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, [{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).
@@ -252,15 +351,23 @@ record_update(Ann, E, Flds) ->
record([]) -> {map, [], []};
record(Fs) ->
case record_or_map(Fs) of
record -> {record, get_ann(hd(Fs)), Fs};
record ->
Fld = fun({field, _, [_], _} = F) -> F;
({field, Ann, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in record construction", infix({lvalue, Ann, LV}, {'@', Ann}, Id));
({field, Ann, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in record construction", {lvalue, Ann, LV}) end,
{record, get_ann(hd(Fs)), lists:map(Fld, Fs)};
map ->
Ann = get_ann(hd(Fs ++ [{empty, []}])), %% TODO: source location for empty maps
KV = fun({field, _, [{map_get, _, Key}], Val}) -> {Key, Val};
({field, _, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix(LV, {op, Ann, '@'}, Id));
({field, _, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", LV) end,
{map, Ann, lists:map(KV, Fs)}
({field, FAnn, LV, Id, _}) ->
bad_expr_err("Cannot use '@' in map construction", infix({lvalue, FAnn, LV}, {'@', Ann}, Id));
({field, FAnn, LV, _}) ->
bad_expr_err("Cannot use nested fields or keys in map construction", {lvalue, FAnn, LV}) end,
{map, Ann, lists:map(KV, Fs)};
record_or_map_error ->
{record_or_map_error, get_ann(hd(Fs)), Fs}
end.
record_or_map(Fields) ->
@@ -272,9 +379,7 @@ record_or_map(Fields) ->
case lists:usort(lists:map(Kind, Fields)) of
[proj] -> record;
[map_get] -> map;
_ ->
[{field, Ann, _, _} | _] = Fields,
bad_expr_err("Mixed record fields and map keys in", {record, Ann, Fields})
_ -> record_or_map_error %% Defer error until type checking
end.
field_assignment() ->
@@ -326,6 +431,26 @@ token(Tag) ->
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
end).
id(Id) ->
?LET_P({id, A, X} = Y, id(),
if X == Id -> Y;
true -> fail({A, "expected '" ++ Id ++ "'"})
end).
id_or_addr() ->
?RULE(id(), parse_addr_literal(_1)).
parse_addr_literal(Id = {id, Ann, Name}) ->
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
false -> Id;
true ->
try aeser_api_encoder:decode(list_to_binary(Name)) of
{Type, Bin} -> {Type, Ann, Bin}
catch _:_ ->
Id
end
end.
%% -- Helpers ----------------------------------------------------------------
keyword(K) -> ann(tok(K)).
@@ -355,12 +480,6 @@ bracket_list(P) -> brackets(comma_sep(P)).
-spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) ->
{proplists:get_value(file, Ann),
proplists:get_value(line, Ann),
@@ -391,8 +510,8 @@ infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
prefixes(Ops, E) -> lists:foldr(fun prefix/2, E, Ops).
prefix(Op, E) -> set_ann(format, prefix, {app, get_ann(Op), Op, [E]}).
type_wildcard() ->
{id, [{origin, system}], "_"}.
type_wildcard(Ann) ->
{id, [{origin, system} | Ann], "_"}.
block_e(Stmts) ->
group_ifs(Stmts, []).
@@ -417,7 +536,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
{'if', Ann, Cond, Then, Else};
build_if(Ann, Cond, Then, []) ->
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
else_branches(Stmts, [Elif | Acc]);
@@ -430,21 +549,21 @@ tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
fun_t(Domains, Type) ->
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
lists:foldr(fun({{args_t, _, Dom}, Ann}, T) -> {fun_t, Ann, [], Dom, T};
({Dom, Ann}, T) -> {fun_t, Ann, [], [Dom], T} end,
Type, Domains).
tuple_e(Ann, []) -> {unit, Ann};
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
%% TODO: not nice
fun_domain({tuple_t, _, Args}) -> Args;
fun_domain(T) -> [T].
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
parse_pattern({app, Ann, Con = {con, _, _}, Es}) ->
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
{int, Ann, -N};
parse_pattern({app, Ann, Con = {Tag, _, _}, Es}) when Tag == con; Tag == qcon ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
parse_pattern({tuple, Ann, Es}) ->
{tuple, Ann, lists:map(fun parse_pattern/1, Es)};
@@ -452,12 +571,14 @@ parse_pattern({list, Ann, Es}) ->
{list, Ann, lists:map(fun parse_pattern/1, Es)};
parse_pattern({record, Ann, Fs}) ->
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
parse_pattern({typed, Ann, E, Type}) ->
{typed, Ann, parse_pattern(E), Type};
parse_pattern(E = {con, _, _}) -> E;
parse_pattern(E = {qcon, _, _}) -> E;
parse_pattern(E = {id, _, _}) -> E;
parse_pattern(E = {unit, _}) -> E;
parse_pattern(E = {int, _, _}) -> E;
parse_pattern(E = {bool, _, _}) -> E;
parse_pattern(E = {hash, _, _}) -> E;
parse_pattern(E = {bytes, _, _}) -> E;
parse_pattern(E = {string, _, _}) -> E;
parse_pattern(E = {char, _, _}) -> E;
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
@@ -466,42 +587,53 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}.
return_error({no_file, L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> no_return().
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
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()) -> no_return().
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
bad_expr_err(Reason, E) ->
ret_doc_err(get_ann(E),
prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
case read_file(File, Opts) of
{ok, Bin} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case string(binary_to_list(Bin), Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Acc, Opts);
Err = {error, _} ->
Err
end;
{error, _} ->
{error, {get_pos(S), include_error, File}}
expand_includes(AST, Included, Opts) ->
Ann = [{origin, system}],
AST1 = [ {include, Ann, {string, Ann, File}}
|| File <- lists:usort(auto_imports(AST)) ] ++ AST,
expand_includes(AST1, Included, [], Opts).
expand_includes([], Included, Acc, Opts) ->
case lists:member(keep_included, Opts) of
false ->
{ok, lists:reverse(Acc)};
true ->
{ok, {lists:reverse(Acc), Included}}
end;
expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts).
expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
case get_include_code(File, Ann, Opts) of
{ok, Code} ->
Hashed = hash_include(File, Code),
case sets:is_element(Hashed, Included) of
false ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
Included1 = sets:add_element(Hashed, Included),
case parse_and_scan(file(), Code, Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} ->
Err
end;
true ->
expand_includes(AST, Included, Acc, Opts)
end;
Err = {error, _} ->
Err
end;
expand_includes([E | AST], Included, Acc, Opts) ->
expand_includes(AST, Included, [E | Acc], Opts).
read_file(File, Opts) ->
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
@@ -513,6 +645,57 @@ read_file(File, Opts) ->
case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found};
Src -> {ok, Src}
end;
escript ->
try
Escript = escript:script_name(),
{ok, Sections} = escript:extract(Escript, []),
Archive = proplists:get_value(archive, Sections),
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
{ok, [{_, Src}]} -> {ok, Src};
_ -> {error, not_found}
end
catch _:_ ->
{error, not_found}
end
end.
stdlib_options() ->
StdLibDir = aeso_stdlib:stdlib_include_path(),
case filelib:is_dir(StdLibDir) of
true -> [{include, {file_system, [StdLibDir]}}];
false -> [{include, escript}]
end.
get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, Bin}, {ok, _}} ->
case filename:basename(File) == File of
true -> { error
, fail( ann_pos(Ann)
, "Illegal redefinition of standard library " ++ binary_to_list(File))};
%% If a path is provided then the stdlib takes lower priority
false -> {ok, binary_to_list(Bin)}
end;
{_, {ok, Bin}} ->
{ok, binary_to_list(Bin)};
{{ok, Bin}, _} ->
{ok, binary_to_list(Bin)};
{_, _} ->
{error, {ann_pos(Ann), include_error, File}}
end.
-spec hash_include(string() | binary(), string()) -> include_hash().
hash_include(File, Code) when is_binary(File) ->
hash_include(binary_to_list(File), Code);
hash_include(File, Code) when is_list(File) ->
{filename:basename(File), crypto:hash(sha256, Code)}.
auto_imports({comprehension_bind, _, _}) -> [<<"ListInternal.aes">>];
auto_imports({'..', _}) -> [<<"ListInternal.aes">>];
auto_imports(L) when is_list(L) ->
lists:flatmap(fun auto_imports/1, L);
auto_imports(T) when is_tuple(T) ->
auto_imports(tuple_to_list(T));
auto_imports(_) -> [].
+80 -26
View File
@@ -145,23 +145,46 @@ decl(D, Options) ->
with_options(Options, fun() -> decl(D) end).
-spec decl(aeso_syntax:decl()) -> doc().
decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({contract, Attrs, C, Ds}) ->
Mod = fun({Mod, true}) when Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")])
, hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, 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_def, _, T, Vars, Def}) ->
Kind = element(1, Def),
equals(typedecl(Kind, T, Vars), typedef(Def));
decl({fun_decl, _, F, T}) ->
hsep(text("function"), typed(name(F), T));
decl({fun_decl, Ann, F, T}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of
true -> text("entrypoint");
false -> text("function")
end,
hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]);
decl(D = {letfun, Attrs, _, _, _, _}) ->
Mod = fun({Mod, true}) when Mod == private; Mod == internal; Mod == public; Mod == stateful ->
Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
decl(D = {letrec, _, _}) -> letdecl("let", D).
Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of
true -> "entrypoint";
false -> "function"
end,
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl({fun_clauses, Ann, Name, Type, Clauses}) ->
above([ decl(D) || D <- [{fun_decl, Ann, Name, Type} | Clauses] ]);
decl(D = {letval, _, _, _}) -> letdecl("let", D);
decl({block, _, Ds}) ->
above([ decl(D) || D <- Ds ]).
-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().
expr(E, Options) ->
@@ -181,12 +204,10 @@ name({tvar, _, Name}) -> text(Name);
name({typed, _, Name, _}) -> name(Name).
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
letdecl(Let, {letval, _, F, T, E}) ->
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
letdecl(Let, {letval, _, P, E}) ->
block_expr(0, hsep([text(Let), expr(P), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, E}) ->
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
letdecl(Let, {letrec, _, [D | Ds]}) ->
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E).
-spec args([aeso_syntax:arg()]) -> doc().
args(Args) ->
@@ -217,7 +238,7 @@ typedef({variant_t, Constructors}) ->
-spec constructor_t(aeso_syntax:constructor_t()) -> doc().
constructor_t({constr_t, _, C, []}) -> name(C);
constructor_t({constr_t, _, C, Args}) -> beside(name(C), tuple_type(Args)).
constructor_t({constr_t, _, C, Args}) -> beside(name(C), args_type(Args)).
-spec field_t(aeso_syntax:field_t()) -> doc().
field_t({field_t, _, Name, Type}) ->
@@ -229,13 +250,20 @@ type(Type, Options) ->
-spec type(aeso_syntax:type()) -> doc().
type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, Ret}) ->
follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret));
type({app_t, _, Type, []}) ->
type(Type);
type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args));
beside(type(Type), args_type(Args));
type({tuple_t, _, Args}) ->
tuple_type(Args);
type({args_t, _, Args}) ->
args_type(Args);
type({bytes_t, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"]));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
@@ -248,10 +276,20 @@ type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T).
-spec tuple_type([aeso_syntax:type()]) -> doc().
tuple_type(Args) ->
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
tuple(lists:map(fun type/1, Args)).
-spec tuple_type([aeso_syntax:type()]) -> doc().
tuple_type([]) ->
text("unit");
tuple_type(Factors) ->
beside(
[ text("(")
, par(punctuate(text(" *"), lists:map(fun type/1, Factors)), 0)
, text(")")
]).
-spec arg_expr(aeso_syntax:arg_expr()) -> doc().
arg_expr({named_arg, _, Name, E}) ->
follow(hsep(expr(Name), text("=")), expr(E));
@@ -278,6 +316,8 @@ expr_p(_, {tuple, _, Es}) ->
tuple(lists:map(fun expr/1, Es));
expr_p(_, {list, _, Es}) ->
list(lists:map(fun expr/1, Es));
expr_p(_, {list_comp, _, E, Binds}) ->
list([follow(expr(E), hsep(text("|"), par(punctuate(text(","), lists:map(fun lc_bind/1, Binds)), 0)), 0)]);
expr_p(_, {record, _, Fs}) ->
record(lists:map(fun field/1, Fs));
expr_p(_, {map, Ann, KVs}) ->
@@ -319,10 +359,20 @@ expr_p(_, E = {int, _, N}) ->
end,
text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {bytes, _, Bin}) ->
Digits = byte_size(Bin),
<<N:Digits/unit:8>> = Bin,
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {Type, _, Bin})
when Type == account_pubkey;
Type == contract_pubkey;
Type == oracle_pubkey;
Type == oracle_query_id ->
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
expr_p(_, {string, _, <<>>}) -> text("\"\"");
expr_p(_, {string, _, S}) ->
text(io_lib:format("\"~s\"", [binary_to_list(S)]));
expr_p(_, {char, _, C}) ->
case C of
$' -> text("'\\''");
@@ -351,9 +401,17 @@ stmt_p({else, Else}) ->
_ -> block_expr(200, text("else"), Else)
end.
lc_bind({comprehension_bind, P, E}) ->
follow(hsep(expr(P), text("<-")), expr(E));
lc_bind({comprehension_if, _, E}) ->
beside([text("if("), expr(E), text(")")]);
lc_bind(Let) ->
letdecl("let", Let).
-spec bin_prec(aeso_syntax:bin_op()) -> {integer(), integer(), integer()}.
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500};
@@ -413,7 +471,7 @@ elim1(Get={map_get, _, _}) -> elim(Get);
elim1(Get={map_get, _, _, _}) -> elim(Get).
alt({'case', _, Pat, Body}) ->
block_expr(0, hsep(expr_p(500, Pat), text("=>")), Body).
block_expr(0, hsep(expr(Pat), text("=>")), Body).
block_expr(_, Header, {block, _, Ss}) ->
block(Header, statements(Ss));
@@ -423,9 +481,8 @@ block_expr(P, Header, E) ->
statements(Stmts) ->
above([ statement(S) || S <- Stmts ]).
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
statement(S = {letval, _, _, _}) -> letdecl("let", S);
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
statement(S = {letrec, _, _}) -> letdecl("let", S);
statement(E) -> expr(E).
get_elifs(Expr) -> get_elifs(Expr, []).
@@ -437,6 +494,3 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) ->
end;
get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}.
fmt(Fmt, Args) -> text(lists:flatten(io_lib:format(Fmt, Args))).
term(X) -> fmt("~p", [X]).
+24 -15
View File
@@ -13,14 +13,15 @@
override/2, push/2, pop/1]).
lexer() ->
Number = fun(Digit) -> [Digit, "+(_", Digit, "+)*"] end,
DIGIT = "[0-9]",
HEXDIGIT = "[0-9a-fA-F]",
LOWER = "[a-z_]",
UPPER = "[A-Z]",
CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"],
HEX = ["0x", HEXDIGIT, "+"],
HASH = ["#", HEXDIGIT, "+"],
INT = Number(DIGIT),
HEX = ["0x", Number(HEXDIGIT)],
BYTES = ["#", Number(HEXDIGIT)],
WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID],
@@ -36,8 +37,8 @@ lexer() ->
, {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"],
KW = string:join(Keywords, "|"),
Rules =
@@ -53,8 +54,8 @@ lexer() ->
, {CHAR, token(char, fun parse_char/1)}
, {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)}
, {HASH, token(hash, fun parse_hash/1)}
, {INT, token(int, fun parse_int/1)}
, {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!)
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
@@ -95,9 +96,11 @@ parse_char([$', C, $']) -> C.
unescape(Str) -> unescape(Str, []).
%% TODO: numeric escapes
unescape([$"], 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) ->
Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
case Code of
@@ -115,12 +118,18 @@ unescape([$\\, Code | Chars], Acc) ->
unescape([C | Chars], 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_hash("#" ++ Chars) ->
N = list_to_integer(Chars, 16),
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
true -> <<N:64/unit:8>>; %% signature
false -> <<N:32/unit:8>> %% address
end.
parse_hex("0x" ++ S) ->
list_to_integer(strip_underscores(S), 16).
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>>.
+17
View File
@@ -0,0 +1,17 @@
%%%-------------------------------------------------------------------
%%% @author Radosław Rowicki
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Standard library for Sophia
%%% @end
%%% Created : 6 July 2019
%%%
%%%-------------------------------------------------------------------
-module(aeso_stdlib).
-export([stdlib_include_path/0]).
stdlib_include_path() ->
filename:join([code:priv_dir(aesophia), "stdlib"]).
+37 -14
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]).
@@ -25,7 +25,7 @@
-type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -36,15 +36,27 @@
-type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {type_decl, ann(), id(), [tvar()]}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()}
| letbind().
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
| {block, ann(), [decl()]}
| fundecl()
| letfun()
| letval(). % Only for error msgs
-type compiler_version() :: [non_neg_integer()].
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind()
:: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}
| {letrec, ann(), [letbind()]}.
:: letfun()
| letval().
-type arg() :: {arg, ann(), id(), type()}.
@@ -60,6 +72,8 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]}
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
| {bytes_t, ann(), integer() | any}
| id() | qid()
| con() | qcon() %% contracts
| tvar().
@@ -69,8 +83,11 @@
-type constant()
:: {int, ann(), integer()}
| {bool, ann(), true | false}
| {hash, ann(), binary()}
| {unit, ann()}
| {bytes, ann(), binary()}
| {account_pubkey, ann(), binary()}
| {contract_pubkey, ann(), binary()}
| {oracle_pubkey, ann(), binary()}
| {oracle_query_id, ann(), binary()}
| {string, ann(), binary()}
| {char, ann(), integer()}.
@@ -89,10 +106,10 @@
| {proj, ann(), expr(), id()}
| {tuple, ann(), [expr()]}
| {list, ann(), [expr()]}
| {list_comp, ann(), expr(), [comprehension_exp()]}
| {typed, ann(), expr(), type()}
| {record, ann(), [field(expr())]}
| {record, ann(), expr(), [field(expr())]} %% record update
| {map, ann(), expr(), [field(expr())]} %% map update
| {record_or_map(), ann(), [field(expr())]}
| {record_or_map(), ann(), expr(), [field(expr())]} %% record/map update
| {map, ann(), [{expr(), expr()}]}
| {map_get, ann(), expr(), expr()}
| {map_get, ann(), expr(), expr(), expr()}
@@ -101,6 +118,12 @@
| id() | qid() | con() | qcon()
| constant().
-type record_or_map() :: record | map | record_or_map_error.
-type comprehension_exp() :: [ {comprehension_bind, pat(), expr()}
| {comprehension_if, ann(), expr()}
| letbind() ].
-type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}.
%% When lvalue is a projection this is sugar for accessing fields in nested
@@ -125,6 +148,7 @@
-type pat() :: {app, ann(), con() | op(), [pat()]}
| {tuple, ann(), [pat()]}
| {list, ann(), [pat()]}
| {typed, ann(), pat(), type()}
| {record, ann(), [field(pat())]}
| constant()
| con()
@@ -145,4 +169,3 @@ get_ann(Key, Node, Default) ->
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+37 -28
View File
@@ -6,7 +6,7 @@
%%%-------------------------------------------------------------------
-module(aeso_syntax_utils).
-export([used_ids/1, used_types/1, used/1]).
-export([used_ids/1, used_types/2, used/1]).
-record(alg, {zero, plus, scoped}).
@@ -39,23 +39,17 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_decl, _, I, _} -> BindType(I);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
@@ -77,6 +71,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{proj, _, E, _} -> Expr(E);
{tuple, _, As} -> Expr(As);
{list, _, As} -> Expr(As);
{list_comp, _, Y, []} -> Expr(Y);
{list_comp, A, Y, [{comprehension_bind, I, E}|R]} ->
Plus(Expr(E), Scoped(BindExpr(I), Expr({list_comp, A, Y, R})));
{list_comp, A, Y, [{comprehension_if, _, E}|R]} ->
Plus(Expr(E), Expr({list_comp, A, Y, R}));
{list_comp, A, Y, [D = {letval, _, Pat, _} | R]} ->
Plus(Decl(D), Scoped(BindExpr(Pat), Expr({list_comp, A, Y, R})));
{list_comp, A, Y, [D = {letfun, _, F, _, _, _} | R]} ->
Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R})));
{typed, _, E, T} -> Plus(Expr(E), Type(T));
{record, _, Fs} -> Expr(Fs);
{record, _, E, Fs} -> Expr([E | Fs]);
@@ -89,7 +92,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]);
%% arg()
{arg, _, X, T} -> Plus(Expr(X), Type(T));
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
%% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim()
@@ -104,29 +107,35 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
%% Name dependencies
used_ids(E) ->
[ X || {term, [X]} <- used(E) ].
[ X || {{term, [X]}, _} <- used(E) ].
used_types(T) ->
[ X || {type, [X]} <- used(T) ].
used_types([Top] = _CurrentNS, T) ->
F = fun({{type, [X]}, _}) -> [X];
({{type, [Top1, X]}, _}) when Top1 == Top -> [X];
(_) -> []
end,
lists:flatmap(F, used(T)).
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg([entity()]).
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> maps:without(Keys, Map) end,
Scoped = fun(Xs, Ys) ->
{Bound, Others} = lists:partition(IsBound, Ys),
Bound = [E || E <- maps:keys(Xs), IsBound(E)],
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
lists:umerge(Xs -- Bound1, Others)
Others = Remove(Bound1, Ys),
maps:merge(Remove(Bound, Xs), Others)
end,
#alg{ zero = []
, plus = fun lists:umerge/2
#alg{ zero = #{}
, plus = fun maps:merge/2
, scoped = Scoped }.
-spec used(_) -> [entity()].
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
@@ -134,14 +143,14 @@ used(D) ->
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
fold(entity_alg(),
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> []
end, decl, D),
maps:to_list(fold(entity_alg(),
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
+137
View File
@@ -0,0 +1,137 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
from_fate({id, _, "int"}, N) when is_integer(N) ->
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
{list, [], [from_fate(Type, X) || X <- List]};
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, [0, 1], 0, {}} -> {con, [], "None"};
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
end;
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
{tuple, [], []};
from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
when length(Types) == tuple_size(Val) ->
{tuple, [], [from_fate(Type, X)
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
from_fate({record_t, [{field_t, _, FName, FType}]}, Val) ->
{record, [], [{field, [], [{proj, [], FName}], from_fate(FType, Val)}]};
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
when length(Fields) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_fate(KeyType, Key),
from_fate(ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
when length(Cons) > Tag ->
ConType = lists:nth(Tag + 1, Cons),
Arity = lists:nth(Tag + 1, Ar),
case tuple_to_list(Args) of
ArgList when length(ArgList) == Arity ->
from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia)
end;
from_fate({constr_t, _, Con, []}, []) -> Con;
from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia).
make_bits(N) ->
Id = fun(F) -> {qid, [], ["Bits", F]} end,
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
true -> make_bits(Id("set"), Id("none"), 0, N) end.
make_bits(_Set, Zero, _I, 0) -> Zero;
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
make_bits(Set, Zero, I + 1, N div 2);
make_bits(Set, Zero, I, N) ->
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
+3 -2
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Contract Language for aeternity"},
{vsn, "2.1.0"},
{vsn, "4.3.0"},
{registered, []},
{applications,
[kernel,
@@ -8,7 +8,8 @@
jsx,
syntax_tools,
getopt,
aebytecode
aebytecode,
eblake2
]},
{env,[]},
{modules, []},
-78
View File
@@ -1,78 +0,0 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
case Opts of
[version] ->
print_vsn();
[help] ->
usage();
_ ->
compile(Opts)
end;
{ok, {_, NonOpts}} ->
io:format("Can't understand ~p\n\n", [NonOpts]),
usage();
{error, {Reason, Data}} ->
io:format("Error: ~s ~p\n\n", [Reason, Data]),
usage()
end.
compile(Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined ->
io:format("Error: no input source file\n\n"),
usage();
File ->
compile(File, Opts)
end.
compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined),
try
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
write_outfile(OutFile, Res),
io:format("\nCompiled successfully!\n")
catch
%% The compiler errors.
error:{type_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
error:{parse_errors, Errors} ->
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
error:{code_errors, Errors} ->
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
%% General programming errors in the compiler.
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
io:format("\n~s\n", [ErrorString])
end.
write_outfile(undefined, _) -> ok;
write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+95 -25
View File
@@ -1,9 +1,12 @@
-module(aeso_abi_tests).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile([export_all, nowarn_export_all]).
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123).
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
sandbox(Code) ->
Parent = self(),
@@ -59,28 +62,81 @@ encode_decode_sophia_test() ->
Other -> Other
end end,
ok = Check("int", "42"),
ok = Check("int", "- 42"),
ok = Check("bool", "true"),
ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""),
ok = Check("(string, list(int), option(bool))",
ok = Check("string * list(int) * option(bool)",
"(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
"when checking the application at line 5, column 34 of\n"
" x : (int) => string\nto arguments\n true : bool\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
ok.
decode_calldata_neg_test() ->
Code1 = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
ok.
encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n"
, " type arg_type = ", SophiaType, "\n"
, " type an_alias('a) = (string, 'a)\n"
, " type an_alias('a) = string * 'a\n"
, " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n"
, " function foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
, " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]),
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
{ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} ->
@@ -97,7 +153,15 @@ calldata_test() ->
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
@@ -108,16 +172,14 @@ calldata_init_test() ->
calldata_indent_test() ->
Test = fun(Extra) ->
encode_decode_calldata_(
parameterized_contract(Extra, "foo", ["int"]),
"foo", ["42"], word)
Code = parameterized_contract(Extra, "foo", ["int"]),
encode_decode_calldata_(Code, "foo", ["42"], word)
end,
Test(" stateful function bla() = ()"),
Test(" stateful entrypoint bla() = ()"),
Test(" type x = int"),
Test(" private function bla : int => int"),
Test(" public stateful function bla(x : int) =\n"
Test(" stateful entrypoint bla(x : int) =\n"
" x + 1"),
Test(" stateful private function bla(x : int) : int =\n"
Test(" stateful entrypoint bla(x : int) : int =\n"
" x + 1"),
ok.
@@ -126,29 +188,35 @@ parameterized_contract(FunName, Types) ->
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Dummy =\n",
["contract Remote =\n"
" entrypoint bla : () => unit\n\n"
"contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n"
" type an_alias('a) = string * 'a\n"
" record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n"
" function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() ->
Contract =
"contract OracleTest =\n"
" function question(o, q : oracle_query(list(string), option(int))) =\n"
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
ok.
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" function haxx(o : oracle(list(string), option(int))) =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
{error, [Err]} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)),
?assertEqual(type_error, aeso_errors:type(Err)),
ok.
encode_decode_calldata(FunName, Types, Args) ->
@@ -159,14 +227,16 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
?assertEqual(RetVMType1, RetVMType),
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
case FunName of
"init" ->
ok;
_ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values})
end,
+110 -54
View File
@@ -2,75 +2,131 @@
-include_lib("eunit/include/eunit.hrl").
do_test() ->
test_contract(1),
test_contract(2),
test_contract(3).
simple_aci_test_() ->
[{"Test contract " ++ integer_to_list(N),
fun() -> test_contract(N) end}
|| N <- [1, 2, 3]].
test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:encode(Contract),
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
?assertEqual(DecACI, aeso_aci:decode(JSON)).
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)),
%% Check if the compiler provides correct aci
{ok,#{aci := JSON2}} = aeso_compiler:from_string(Contract, [{aci, json}]),
?assertEqual(JSON, JSON2).
test_cases(1) ->
Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> => [],
<<"functions">> =>
[#{<<"name">> => <<"a">>,
<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
Contract = <<"payable contract C =\n"
" payable stateful entrypoint a(i : int) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>,
type_defs => [],
payable => true,
functions =>
[#{name => <<"a">>,
arguments =>
[#{name => <<"i">>,
type => <<"int">>}],
returns => <<"int">>,
stateful => true,
payable => true}]}},
DecACI = <<"payable contract C =\n"
" payable entrypoint a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" function a(i : allan) = i+1\n">>,
MapACI = #{<<"contract">> =>
#{<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"allan">>,
<<"typedef">> => <<"int">>,
<<"vars">> => []}],
<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> => [<<"int">>]}],
<<"name">> => <<"a">>,
<<"returns">> => <<"int">>,
<<"stateful">> => false}]}},
" entrypoint a(i : allan) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>, payable => false,
type_defs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
functions =>
[#{arguments =>
[#{name => <<"i">>,
type => <<"C.allan">>}],
name => <<"a">>,
returns => <<"int">>,
stateful => false,
payable => false}]}},
DecACI = <<"contract C =\n"
" function a : (int) => int\n">>,
" type allan = int\n"
" entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" function a(i : bert(string)) = 1\n">>,
MapACI = #{<<"contract">> =>
#{<<"functions">> =>
[#{<<"arguments">> =>
[#{<<"name">> => <<"i">>,
<<"type">> =>
[#{<<"C.bert">> => [<<"string">>]}]}],
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
<<"stateful">> => false}],
<<"name">> => <<"C">>,
<<"type_defs">> =>
[#{<<"name">> => <<"bert">>,
<<"typedef">> =>
#{<<"variant">> =>
" entrypoint a(i : bert(string)) = 1\n">>,
MapACI = #{contract =>
#{functions =>
[#{arguments =>
[#{name => <<"i">>,
type =>
#{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>,
stateful => false, payable => false}],
name => <<"C">>, payable => false,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>,
type_defs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]},
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
" datatype bert('a) = Bin('a)\n"
" function a : (C.bert(string)) => int\n">>,
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" entrypoint a : (C.bert(string)) => int\n">>,
{Contract,MapACI,DecACI}.
%% Roundtrip
aci_test_() ->
[{"Testing ACI generation for " ++ ContractName,
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
Opts = case lists:member(Name, aeso_compiler_tests:debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
check_stub(Stub, Options) ->
try aeso_parser:string(binary_to_list(Stub), Options) of
Ast ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
_:R ->
io:format("Error: ~p\n", [R]),
error(R)
end
catch throw:{error, Errs} ->
_ = [ io:format("~s\n", [aeso_errors:pp(E)]) || E <- Errs ],
error({parse_errors, Errs})
end.
+146
View File
@@ -0,0 +1,146 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc Test Sophia language compiler.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_calldata_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
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) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)),
strip_ann(Exprs).
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
%% compilable_contracts() -> [ContractName].
%% The currently compilable contracts.
compilable_contracts() ->
[
{"identity", "init", []},
{"maps", "init", []},
{"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
{"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", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
"414243444546474849505152535455565758596061626364656667"]},
{"funargs", "trettiotva", ["#0102030405060708091011121314151617181920212223242526272829303132"]},
{"funargs", "find_oracle", ["ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5"]},
{"funargs", "find_query", ["oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY"]},
{"funargs", "traffic_light", ["Green"]},
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
{"bytes_equality", "init", []},
{"address_chain", "init", []},
{"counter", "init",
["-3334353637383940202122232425262728293031323334353637"]},
{"dutch_auction", "init",
["ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt", "200000", "1000"]},
{"maps", "fromlist_i",
["[(1, {x = 1, y = 2}), (2, {x = 3, y = 4}), (3, {x = 4, y = 4})]"]},
{"maps", "get_i", ["1", "{}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
{"strings", "str_concat", ["\"test\"","\"me\""]},
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
{"__call" "init", []},
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f20212223242526272829303132333435363738"]},
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]},
{"stub", "foo", ["42"]},
{"stub", "foo", ["-42"]},
{"payable", "foo", ["42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
[].
+776 -118
View File
@@ -8,67 +8,120 @@
-module(aeso_compiler_tests).
-compile([export_all, nowarn_export_all]).
-include_lib("eunit/include/eunit.hrl").
run_test(Test) ->
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
[ begin
io:format("~s\n", [Label]),
Fun()
end || {Label, Fun} <- ?MODULE:TestFun() ],
ok.
%% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates
%% that the compilation worked.
simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract",
fun() ->
#{byte_code := ByteCode,
contract_source := _,
type_info := _} = compile(ContractName),
?assertMatch(Code when is_binary(Code), ByteCode)
end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
case compile(ContractName) of
<<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
[ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination",
fun() ->
#{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
ok
end} ].
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
fun() ->
case compile(Backend, ContractName) of
#{byte_code := ByteCode,
contract_source := _,
type_info := _} when Backend == aevm ->
?assertMatch(Code when is_binary(Code), ByteCode);
#{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code});
ErrBin ->
io:format("\n~s", [ErrBin]),
error(ErrBin)
end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_compilable_on(Backend))] ++
[ {"Test file not found error",
fun() ->
{error, Errors} = aeso_compiler:file("does_not_exist.aes"),
ExpErr = <<"File error:\ndoes_not_exist.aes: no such file or directory">>,
check_errors([ExpErr], Errors)
end} ] ++
[ {"Testing error messages of " ++ ContractName,
fun() ->
Errors = compile(aevm, ContractName),
check_errors(ExpectedErrors, Errors)
end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] ++
[ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName,
fun() ->
Errors = compile(Backend, ContractName),
Expect =
case is_binary(ExpectedError) of
true -> [ExpectedError];
false ->
case proplists:get_value(Backend, ExpectedError, no_error) of
no_error -> no_error;
Err -> [Err]
end
end,
check_errors(Expect, Errors)
end} ||
{ContractName, ExpectedError} <- failing_code_gen_contracts(),
Backend <- [aevm, fate] ] ++
[ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
[ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend),
fun() ->
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"),
#{ byte_code := DeadCode } = compile(Backend, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
Delta = if Backend == aevm -> 40;
Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok
end} || Backend <- [aevm, fate] ] ++
[].
check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
check_errors(Expect, #{}) ->
?assertEqual({error, Expect}, ok);
check_errors(Expect0, Actual0) ->
Expect = lists:sort(Expect0),
Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ],
case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
{Missing, Extra} -> ?assertEqual(Missing, Extra)
end.
compile(Name) ->
compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name) ->
compile(Backend, Name,
[{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Name, Options) ->
compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
{ok, Map} -> Map;
{error, ErrorString} -> ErrorString
Options1 =
case lists:member(Name, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[ {src_file, Name ++ ".aes"}, {backend, Backend}
, {include, {file_system, [aeso_test_utils:contract_path()]}}
] ++ Options,
case aeso_compiler:from_string(String, Options1) of
{ok, Map} -> Map;
{error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors
end.
%% compilable_contracts() -> [ContractName].
@@ -80,6 +133,7 @@ compilable_contracts() ->
"dutch_auction",
"environment",
"factorial",
"functions",
"fundme",
"identity",
"maps",
@@ -92,130 +146,734 @@ compilable_contracts() ->
"test",
"builtin_bug",
"builtin_map_get_bug",
"lc_record_bug",
"nodeadcode",
"deadcode",
"variant_types",
"state_handling",
"events",
"include",
"basic_auth"
"basic_auth",
"bitcoin_auth",
"address_literals",
"bytes_equality",
"address_chain",
"namespace_bug",
"bytes_to_x",
"bytes_concat",
"aens",
"tuple_match",
"cyclic_include",
"stdlib_include",
"double_include",
"manual_stdlib_include",
"list_comp",
"payable",
"unapplied_builtins",
"underscore_number_literals",
"qualified_constructor",
"let_patterns",
"lhs_matching",
"hermetization_turnoff"
].
not_compilable_on(fate) -> [];
not_compilable_on(aevm) ->
["stdlib_include",
"manual_stdlib_include",
"hermetization_turnoff"
].
debug_mode_contracts() ->
["hermetization_turnoff"].
%% Contracts that should produce type errors
-define(Pos(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " error in '",
(list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n").
-define(Pos(Line, Col), ?Pos(__Kind, __File, Line, Col)).
-define(ERROR(Kind, Name, Errs),
(fun() ->
__Kind = Kind,
__File = ??Name,
{__File, Errs}
end)()).
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
failing_contracts() ->
[ {"name_clash",
[<<"Duplicate definitions of abort at\n"
{ok, V} = aeso_compiler:numeric_version(),
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
%% Parse errors
[ ?PARSE_ERROR(field_parse_error,
[<<?Pos(5, 26)
"Cannot use nested fields or keys in record construction: p.x">>])
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
%% Type errors
, ?TYPE_ERROR(name_clash,
[<<?Pos(14, 3)
"Duplicate definitions of abort at\n"
" - (builtin location)\n"
" - line 14, column 3">>,
<<"Duplicate definitions of double_def at\n"
" - line 10, column 3\n"
" - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<"Duplicate definitions of put at\n"
<<?Pos(15, 3)
"Duplicate definitions of require at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of state at\n"
<<?Pos(11, 3)
"Duplicate definitions of double_def at\n"
" - line 10, column 3\n"
" - line 11, column 3">>,
<<?Pos(5, 3)
"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<?Pos(8, 3)
"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<?Pos(16, 3)
"Duplicate definitions of put at\n"
" - (builtin location)\n"
" - line 16, column 3">>]}
, {"type_errors",
[<<"Unbound variable zz at line 17, column 21">>,
<<"Cannot unify int\n"
" - line 16, column 3">>,
<<?Pos(17, 3)
"Duplicate definitions of state at\n"
" - (builtin location)\n"
" - line 17, column 3">>])
, ?TYPE_ERROR(type_errors,
[<<?Pos(17, 23)
"Unbound variable zz at line 17, column 23">>,
<<?Pos(26, 9)
"Cannot unify int\n"
" and list(int)\n"
"when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n"
"to arguments\n"
" x : int\n"
" x : int">>,
<<"Cannot unify string\n"
<<?Pos(9, 48)
"Cannot unify string\n"
" and int\n"
"when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 46)\n"
" x : map(string, string) (at line 9, column 48)\n"
"to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n"
<<?Pos(34, 47)
"Cannot unify int\n"
" and string\n"
"when checking the type of the expression at line 34, column 45\n"
"when checking the type of the expression at line 34, column 47\n"
" 1 : int\n"
"against the expected type\n"
" string">>,
<<"Cannot unify string\n"
<<?Pos(34, 52)
"Cannot unify string\n"
" and int\n"
"when checking the type of the expression at line 34, column 50\n"
"when checking the type of the expression at line 34, column 52\n"
" \"bla\" : string\n"
"against the expected type\n"
" int">>,
<<"Cannot unify string\n"
<<?Pos(32, 18)
"Cannot unify string\n"
" and int\n"
"when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n"
"against the expected type\n"
" int">>,
<<"Cannot unify string\n"
<<?Pos(11, 58)
"Cannot unify string\n"
" and int\n"
"when checking the type of the expression at line 11, column 56\n"
"when checking the type of the expression at line 11, column 58\n"
" \"foo\" : string\n"
"against the expected type\n"
" int">>,
<<"Cannot unify int\n"
<<?Pos(38, 13)
"Cannot unify int\n"
" and string\n"
"when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 38)">>,
<<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 42)">>,
<<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 38)">>,
<<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 35)">>,
<<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
<<?Pos(22, 40)
"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>,
<<?Pos(21, 44)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>,
<<?Pos(20, 40)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>,
<<?Pos(19, 37)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>,
<<?Pos(13, 27)
"Ambiguous record type with field y (at line 13, column 27) could be one of\n"
" - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n"
<<?Pos(26, 7)
"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>,
<<"No record type with fields y, z (at line 14, column 22)">>,
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
, {"init_type_error",
[<<"Cannot unify string\n"
<<?Pos(44, 14)
"Repeated names x, y in pattern\n"
" (x : int, y, x : string, y : bool) (at line 44, column 14)">>,
<<?Pos(44, 39)
"Cannot unify int\n"
" and string\n"
"when checking the type of the expression at line 44, column 39\n"
" x : int\n"
"against the expected type\n"
" string">>,
<<?Pos(44, 72)
"Cannot unify int\n"
" and string\n"
"when checking the type of the expression at line 44, column 72\n"
" x : int\n"
"against the expected type\n"
" string">>,
<<?Pos(14, 24)
"No record type with fields y, z (at line 14, column 24)">>,
<<?Pos(15, 26)
"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<?Pos(15, 24)
"Record type r2 does not have field y (at line 15, column 24)">>,
<<?Pos(47, 5)
"Let binding at line 47, column 5 must be followed by an expression">>,
<<?Pos(50, 5)
"Let binding at line 50, column 5 must be followed by an expression">>,
<<?Pos(54, 5)
"Let binding at line 54, column 5 must be followed by an expression">>,
<<?Pos(58, 5)
"Let binding at line 58, column 5 must be followed by an expression">>,
<<?Pos(63, 5)
"Cannot unify int\n"
" and bool\n"
"when checking the type of the expression at line 63, column 5\n"
" id(n) : int\n"
"against the expected type\n"
" bool">>])
, ?TYPE_ERROR(init_type_error,
[<<?Pos(7, 3)
"Cannot unify string\n"
" and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type",
[<<"Cannot unify string\n"
" and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression",
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
<<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events",
[<<"The payload type int (at line 10, column 30) should be string">>,
<<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
, {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash",
[<<"Cannot unify int\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>])
, ?TYPE_ERROR(missing_state_type,
[<<?Pos(5, 3)
"Cannot unify string\n"
" and unit\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>])
, ?TYPE_ERROR(missing_fields_in_record_expression,
[<<?Pos(7, 42)
"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>,
<<?Pos(8, 42)
"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>,
<<?Pos(6, 42)
"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>])
, ?TYPE_ERROR(namespace_clash,
[<<?Pos(4, 10)
"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>])
, ?TYPE_ERROR(bad_events,
[<<?Pos(9, 25)
"The indexed type string (at line 9, column 25) is not a word type">>,
<<?Pos(10, 25)
"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>])
, ?TYPE_ERROR(bad_events2,
[<<?Pos(9, 7)
"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<?Pos(10, 7)
"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>])
, ?TYPE_ERROR(type_clash,
[<<?Pos(12, 42)
"Cannot unify int\n"
" and string\n"
"when checking the record projection at line 12, column 40\n"
"when checking the record projection at line 12, column 42\n"
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]}
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
" (gas : int, value : int) => map(string, int)">>])
, ?TYPE_ERROR(not_toplevel_include,
[<<?Pos(2, 11)
"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>])
, ?TYPE_ERROR(not_toplevel_namespace,
[<<?Pos(2, 13)
"Nested namespaces are not allowed\nNamespace 'Foo' at line 2, column 13 not defined at top level.">>])
, ?TYPE_ERROR(not_toplevel_contract,
[<<?Pos(2, 12)
"Nested contracts are not allowed\nContract 'Con' at line 2, column 12 not defined at top level.">>])
, ?TYPE_ERROR(bad_address_literals,
[<<?Pos(11, 5)
"Cannot unify address\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<?Pos(9, 5)
"Cannot unify address\n"
" and Remote\n"
"when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" Remote">>,
<<?Pos(7, 5)
"Cannot unify address\n"
" and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n"
" bytes(32)">>,
<<?Pos(14, 5)
"Cannot unify oracle('a, 'b)\n"
" and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('a, 'b)\n"
"against the expected type\n"
" oracle_query(int, bool)">>,
<<?Pos(16, 5)
"Cannot unify oracle('c, 'd)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('c, 'd)\n"
"against the expected type\n"
" bytes(32)">>,
<<?Pos(18, 5)
"Cannot unify oracle('e, 'f)\n"
" and Remote\n"
"when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('e, 'f)\n"
"against the expected type\n"
" Remote">>,
<<?Pos(21, 5)
"Cannot unify oracle_query('g, 'h)\n"
" and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('g, 'h)\n"
"against the expected type\n"
" oracle(int, bool)">>,
<<?Pos(23, 5)
"Cannot unify oracle_query('i, 'j)\n"
" and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('i, 'j)\n"
"against the expected type\n"
" bytes(32)">>,
<<?Pos(25, 5)
"Cannot unify oracle_query('k, 'l)\n"
" and Remote\n"
"when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('k, 'l)\n"
"against the expected type\n"
" Remote">>,
<<?Pos(28, 5)
"The type address is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" address">>,
<<?Pos(30, 5)
"The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" oracle(int, bool)">>,
<<?Pos(32, 5)
"The type bytes(32) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n"
" bytes(32)">>,
<<?Pos(34, 5),
"The type address is not a contract type\n"
"when checking that the call to\n"
" Address.to_contract\n"
"has the type\n"
" address">>])
, ?TYPE_ERROR(stateful,
[<<?Pos(13, 35)
"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
<<?Pos(14, 35)
"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
<<?Pos(16, 15)
"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<?Pos(20, 31)
"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<?Pos(35, 47)
"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
<<?Pos(48, 57)
"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<?Pos(49, 56)
"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
<<?Pos(52, 17)
"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>])
, ?TYPE_ERROR(bad_init_state_access,
[<<?Pos(11, 5)
"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n"
" - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 38), which calls\n"
" - put (at line 7, column 39)">>,
<<?Pos(12, 5)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 29)">>,
<<?Pos(13, 13)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n"
" - state (at line 13, column 13)">>])
, ?TYPE_ERROR(modifier_checks,
[<<?Pos(11, 3)
"The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
<<?Pos(3, 3)
"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<?Pos(5, 10)
"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<?Pos(12, 3)
"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<?Pos(6, 3)
"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
<<?Pos(10, 3)
"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
<<?Pos(6, 3)
"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>])
, ?TYPE_ERROR(list_comp_not_a_list,
[<<?Pos(2, 36)
"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
])
, ?TYPE_ERROR(list_comp_if_not_bool,
[<<?Pos(2, 44)
"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
])
, ?TYPE_ERROR(list_comp_bad_shadow,
[<<?Pos(2, 53)
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
])
, ?TYPE_ERROR(map_as_map_key,
[<<?Pos(5, 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 12, column 25)\n"
" - bytes(20) (at line 12, column 29)">>,
<<?Pos(16, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - bytes(15) (at line 15, column 24)\n"
"and result types\n"
" - 'c (at line 16, column 5)\n"
" - 'd (at line 16, column 5)">>,
<<?Pos(19, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - 'b (at line 18, column 20)\n"
"and result types\n"
" - bytes(20) (at line 18, column 25)\n"
" - 'a (at line 18, column 37)">>])
, ?TYPE_ERROR(wrong_compiler_version,
[<<?Pos(1, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " < 1.0">>,
<<?Pos(2, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
, ?TYPE_ERROR(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.">>])
, ?TYPE_ERROR(toplevel_let,
[<<?Pos(2, 7)
"Toplevel \"let\" definitions are not supported\n"
"Value this_is_illegal at line 2, column 7 could be replaced by 0-argument function">>])
, ?TYPE_ERROR(empty_typedecl,
[<<?Pos(2, 8)
"Empty type declarations are not supported\n"
"Type t at line 2, column 8 lacks a definition">>])
, ?TYPE_ERROR(higher_kinded_type,
[<<?Pos(2, 35)
"Type 'm is a higher kinded type variable\n"
"(takes another type as an argument)">>])
, ?TYPE_ERROR(bad_arity,
[<<?Pos(3, 20)
"Arity for id doesn't match. Expected 1, got 0">>,
<<?Pos(3, 25)
"Cannot unify int\n"
" and id\n"
"when checking the type of the expression at line 3, column 25\n"
" 123 : int\n"
"against the expected type\n"
" id">>,
<<?Pos(4, 20)
"Arity for id doesn't match. Expected 1, got 2">>,
<<?Pos(4, 35)
"Cannot unify int\n"
" and id(int, int)\n"
"when checking the type of the expression at line 4, column 35\n"
" 123 : int\n"
"against the expected type\n"
" id(int, int)">>])
, ?TYPE_ERROR(bad_unnamed_map_update_default,
[<<?Pos(4, 17)
"Invalid map update with default">>])
, ?TYPE_ERROR(non_functional_entrypoint,
[<<?Pos(2, 14)
"f at line 2, column 14 was declared with an invalid type int.\n"
"Entrypoints and functions must have functional types">>])
, ?TYPE_ERROR(bad_records,
[<<?Pos(3, 16)
"Mixed record fields and map keys in\n"
" {x = 0, [0] = 1}">>,
<<?Pos(4, 6)
"Mixed record fields and map keys in\n"
" r {x = 0, [0] = 1}">>,
<<?Pos(5, 6)
"Empty record/map update\n"
" r {}">>
])
, ?TYPE_ERROR(bad_function_block,
[<<?Pos(4, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>,
<<?Pos(5, 5)
"Mismatch in the function block. Expected implementation/type declaration of g function">>
])
, ?TYPE_ERROR(just_an_empty_file,
[<<?Pos(0, 0)
"Empty contract">>
])
, ?TYPE_ERROR(bad_number_of_args,
[<<?Pos(3, 39)
"Cannot unify () => unit\n"
" and (int) => 'a\n",
"when checking the application at line 3, column 39 of\n"
" f : () => unit\n"
"to arguments\n"
" 1 : int">>,
<<?Pos(4, 20)
"Cannot unify (int, string) => 'e\n"
" and (int) => 'd\n"
"when checking the application at line 4, column 20 of\n"
" g : (int, string) => 'e\n"
"to arguments\n"
" 1 : int">>,
<<?Pos(5, 20)
"Cannot unify (int, string) => 'c\n"
" and (string) => 'b\n"
"when checking the application at line 5, column 20 of\n"
" g : (int, string) => 'c\nto arguments\n"
" \"Litwo, ojczyzno moja\" : string">>
])
, ?TYPE_ERROR(bad_state,
[<<?Pos(4, 16)
"Conflicting updates for field 'foo'">>])
].
-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,
case lists:member(Contract2, debug_mode_contracts()) of
true -> [debug_mode];
false -> []
end ++
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
+16 -12
View File
@@ -4,6 +4,8 @@
-include_lib("eunit/include/eunit.hrl").
id(X) -> X.
simple_contracts_test_() ->
{foreach,
fun() -> ok end,
@@ -14,7 +16,7 @@ simple_contracts_test_() ->
" function id(x) = x\n",
?assertMatch(
[{contract, _, {con, _, "Identity"},
[{letfun, _, {id, _, "id"}, [{arg, _, {id, _, "x"}, {id, _, "_"}}], {id, _, "_"},
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
{id, _, "x"}}]}], parse_string(Text)),
ok
end},
@@ -30,7 +32,7 @@ simple_contracts_test_() ->
end,
Parse = fun(S) ->
try remove_line_numbers(parse_expr(S))
catch _:_ -> ?assertMatch(ok, {parse_fail, S}) end
catch _:_ -> ?assertMatch(ok, id({parse_fail, S})) end
end,
CheckParens = fun(Expr) ->
?assertEqual(Parse(NoPar(Expr)), Parse(Par(Expr)))
@@ -38,8 +40,7 @@ simple_contracts_test_() ->
LeftAssoc = fun(Op) -> CheckParens({{a, Op, b}, Op, c}) end,
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
NonAssoc = fun(Op) ->
OpAtom = list_to_atom(Op),
?assertError({error, {_, parse_error, _}},
?assertThrow({error, [_]},
parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
Stronger = fun(Op1, Op2) ->
CheckParens({{a, Op1, b}, Op2, c}),
@@ -62,7 +63,7 @@ simple_contracts_test_() ->
%% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, withdrawal, fundme, dutch_auction] ]
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ]
}.
parse_contract(Name) ->
@@ -71,25 +72,28 @@ parse_contract(Name) ->
roundtrip_contract(Name) ->
round_trip(aeso_test_utils:read_contract(Name)).
parse_string(Text) ->
case aeso_parser:string(Text) of
{ok, Contract} -> Contract;
Err -> error(Err)
end.
parse_string(Text) -> parse_string(Text, []).
parse_string(Text, Opts) ->
aeso_parser:string(Text, Opts).
parse_expr(Text) ->
[{letval, _, _, _, Expr}] =
[{letval, _, _, Expr}] =
parse_string("let _ = " ++ Text),
Expr.
round_trip(Text) ->
Contract = parse_string(Text),
Text1 = prettypr:format(aeso_pretty:decls(Contract)),
Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))),
Contract1 = parse_string(Text1),
NoSrcLoc = remove_line_numbers(Contract),
NoSrcLoc1 = remove_line_numbers(Contract1),
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
strip_stdlib([{namespace, _, {con, _, "ListInternal"}, _} | Decls]) ->
strip_stdlib(Decls);
strip_stdlib(Decls) -> Decls.
remove_line_numbers({line, _L}) -> {line, 0};
remove_line_numbers({col, _C}) -> {col, 0};
remove_line_numbers([H|T]) ->
+3 -3
View File
@@ -41,14 +41,14 @@ all_tokens() ->
%% Operators
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
%% Keywords
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
lists:map(Lit, [contract, type, 'let', switch]) ++
%% Comment token (not an actual token), just for tests
[{comment, 0, "// *Comment!\"\n"},
{comment, 0, "/* bla /* bla bla */*/"}] ++
%% Literals
[ Lit(true), Lit(false)
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
, Tok(hash, Hash)
, Tok(bytes, Hash)
, Tok(int, 1234567890), Tok(hex, 9876543210)
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
].
@@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P;
show_token({string, _, S}) -> fmt(binary_to_list(S));
show_token({int, _, N}) -> fmt(N);
show_token({hex, _, N}) -> fmt("0x~.16b", N);
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
show_token({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
show_token({comment, _, S}) -> S;
show_token({_, _, _}) -> "TODO".
+1 -2
View File
@@ -58,8 +58,7 @@ contract Greeter =
let state = { greeting = "Hello" }
let setGreeting =
(greeting: string) =>
function setGreeting(greeting: string) =
state{ greeting = greeting }
+5
View File
@@ -0,0 +1,5 @@
contract Identity =
function main (x:int) = x
function __call() = 12
+1 -1
View File
@@ -8,7 +8,7 @@ contract AbortTest =
{ value = v }
// Aborting
public function do_abort(v : int, s : string) : () =
public function do_abort(v : int, s : string) : unit =
put_value(v)
revert_abort(s)
+3 -3
View File
@@ -1,9 +1,9 @@
contract Interface =
function do_abort : (int, string) => ()
function do_abort : (int, string) => unit
function get_value : () => int
function put_value : (int) => ()
function put_value : (int) => unit
function get_values : () => list(int)
function put_values : (int) => ()
function put_values : (int) => unit
contract AbortTestInt =
+36
View File
@@ -0,0 +1,36 @@
contract Remote =
entrypoint main : (int) => unit
contract AddrChain =
type o_type = oracle(string, map(string, int))
type oq_type = oracle_query(string, map(string, int))
entrypoint is_o(a : address) =
Address.is_oracle(a)
entrypoint is_c(a : address) =
Address.is_contract(a)
// entrypoint get_o(a : address) : option(o_type) =
// Address.get_oracle(a)
// entrypoint get_c(a : address) : option(Remote) =
// Address.get_contract(a)
entrypoint check_o(o : o_type) =
Oracle.check(o)
entrypoint check_oq(o : o_type, oq : oq_type) =
Oracle.check_query(o, oq)
// entrypoint h_to_i(h : hash) : int =
// Hash.to_int(h)
// entrypoint a_to_i(a : address) : int =
// Address.to_int(a) mod 10 ^ 16
entrypoint c_creator() : address =
Contract.creator
entrypoint is_payable(a : address) : bool =
Address.is_payable(a)
+16
View File
@@ -0,0 +1,16 @@
contract Remote =
entrypoint foo : () => unit
contract AddressLiterals =
entrypoint addr() : address =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle() : oracle(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query() : oracle_query(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr_addr() : Remote =
Address.to_contract(addr())
+34 -33
View File
@@ -3,53 +3,54 @@ contract AENSTest =
// Name resolution
function resolve_word(name : string, key : string) : option(address) =
stateful entrypoint resolve_word(name : string, key : string) : option(address) =
AENS.resolve(name, key)
function resolve_string(name : string, key : string) : option(string) =
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key)
// Transactions
function preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : () = // Commitment hash
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash
AENS.preclaim(addr, chash)
function signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash, // Commitment hash
sign : signature) : () = // Signed by addr (if not Contract.address)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash, // Commitment hash
sign : signature) : unit = // Signed by addr (if not Contract.address)
AENS.preclaim(addr, chash, signature = sign)
function claim(addr : address,
name : string,
salt : int) : () =
AENS.claim(addr, name, salt)
stateful entrypoint claim(addr : address,
name : string,
salt : int,
name_fee : int) : unit =
AENS.claim(addr, name, salt, name_fee)
function signedClaim(addr : address,
name : string,
salt : int,
sign : signature) : () =
AENS.claim(addr, name, salt, signature = sign)
stateful entrypoint signedClaim(addr : address,
name : string,
salt : int,
name_fee : int,
sign : signature) : unit =
AENS.claim(addr, name, salt, name_fee, signature = sign)
// TODO: update() -- how to handle pointers?
function transfer(owner : address,
new_owner : address,
name_hash : hash) : () =
AENS.transfer(owner, new_owner, name_hash)
stateful entrypoint transfer(owner : address,
new_owner : address,
name : string) : unit =
AENS.transfer(owner, new_owner, name)
function signedTransfer(owner : address,
new_owner : address,
name_hash : hash,
sign : signature) : () =
AENS.transfer(owner, new_owner, name_hash, signature = sign)
stateful entrypoint signedTransfer(owner : address,
new_owner : address,
name : string,
sign : signature) : unit =
AENS.transfer(owner, new_owner, name, signature = sign)
function revoke(owner : address,
name_hash : hash) : () =
AENS.revoke(owner, name_hash)
function signedRevoke(owner : address,
name_hash : hash,
sign : signature) : () =
AENS.revoke(owner, name_hash, signature = sign)
stateful entrypoint revoke(owner : address,
name : string) : unit =
AENS.revoke(owner, name)
stateful entrypoint signedRevoke(owner : address,
name : string,
sign : signature) : unit =
AENS.revoke(owner, name, signature = sign)
+4 -6
View File
@@ -104,10 +104,10 @@ contract AEProof =
proofsByOwner : map(address, array(uint)) }
function notarize(document:string, comment:string, ipfsHash:hash) =
let _ = require(aetoken.balanceOf(caller()) > 0)
let _ = require(aetoken.balanceOf(caller()) > 0, "false")
let proofHash: uint = calculateHash(document)
let proof : proof = Map.get_(proofHash, state().proofs)
let _ = require(proof.owner == #0)
let _ = require(proof.owner == #0, "false")
let proof' : proof = proof { owner = caller()
, timestamp = block().timestamp
, proofBlock = block().height
@@ -124,12 +124,12 @@ contract AEProof =
function getProof(document) : proof =
let calcHash = calculateHash(document)
let proof = Map.get_(calcHash, state().proofs)
let _ = require(proof.owner != #0)
let _ = require(proof.owner != #0, "false")
proof
function getProofByHash(hash: uint) : proof =
let proof = Map.get_(hash, state().proofs)
let _ = require(proof.owner != #0)
let _ = require(proof.owner != #0, "false")
proof
@@ -141,5 +141,3 @@ contract AEProof =
function getProofsByOwner(owner: address): array(uint) =
Map.get(owner, state())
function require(x : bool) : unit = if(x) () else abort("false")
+63 -34
View File
@@ -1,49 +1,78 @@
// Try to cover all syntactic constructs.
@compiler > 0
@compiler =< 10.1.1.1.1.1.2.3.4
contract AllSyntaxType =
type typeDecl /* bla */
type paramTypeDecl('a, 'b)
namespace Ns =
datatype d('a) = D | S(int) | M('a, list('a), int)
private function fff() = 123
stateful entrypoint
f (1, x) = (_) => x
payable contract AllSyntaxType =
/** Multi-
* line
* comment
*/
function foo : _
stateful function foo : _
entrypoint bar : int => (int * 'a)
contract AllSyntax =
type typeDecl = int
type paramTypeDecl('a, 'b) = (('a, 'b) => 'b) => list('a) => 'b => 'b
datatype mickiewicz = Adam | Mickiewicz
record goethe('a, 'b) = {
johann : int,
wolfgang : 'a,
von : 'a * 'b * int,
goethe : unit
}
type dante = Ns.d(int)
type shakespeare('a) = goethe('a, 'a)
record nestedRecord = { x : int }
record recordType = { z : nestedRecord, y : int }
datatype variantType('a) = None | Some('a)
type state = shakespeare(int)
let valWithType : map(int, int) => option(int) = (m) => Map.get(m, 42)
let valNoType =
if(valWithType(Map.empty) == None)
print(42 mod 10 * 5 / 3)
entrypoint init() = {
johann = 1000,
wolfgang = -10,
von = (2 + 2, 0, List.sum([x | k <- [1,2,3]
, let l = k + 1
, if(l < 10)
, let f(x) = x + 100
, Adam <- [Adam, Mickiewicz]
, let x = f(l)
])),
goethe = () }
function funWithType(x : int, y) : (int, list(int)) = (x, 0 :: [y] ++ [])
function funNoType() =
let foo = (x, y : bool) =>
if (! (y && x =< 0x0b || true)) [x]
else [11..20]
let setY(r : recordType) : unit = r{ y = 5 }
let setX(r : recordType, x : int) : recordType = r { z.x = x } // nested record update
let getY(r) = switch(r) {y = y} => y
switch (funWithType(1, -2))
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => ()
function f() =
let kp = "nietzsche"
let p = "Пушкин"
let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210)
function mutual() =
let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
recFun(0)
let hash : address = #01ab0fff11
let b = false
let qcon = Mod.Con
let str = "blabla\nfoo"
let chr = '"'
let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
if(Bits.test(Bits.all, 10))
abort("ohno")
if(true && false)
require(true, "ohyes")
elif(false || 2 == 2)
()
else
()
if(true) f(1,2)((1,2))
else switch(1::[1,2,3])
[] => 1
a::b => 123
1::2::3 => 123123
[2,3,4] => 1
_ => 13
1::[2] => 2138
put(state{johann = 1})
let m = {["foo"] = 19, /*hey wanna talk about inlined comments?*/ ["bar"] = 42}
let n = {}
m{ ["x" = 0] @ z = z + state.johann }
let sh : shakespeare(shakespeare(int)) =
{wolfgang = state}
sh{wolfgang.wolfgang = sh.wolfgang} // comment
+35
View File
@@ -0,0 +1,35 @@
contract Remote =
entrypoint foo : () => unit
contract AddressLiterals =
entrypoint addr1() : bytes(32) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr2() : Remote =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint addr3() : oracle(int, bool) =
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
entrypoint oracle1() : oracle_query(int, bool) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle2() : bytes(32) =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint oracle3() : Remote =
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
entrypoint query1() : oracle(int, bool) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query2() : bytes(32) =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint query3() : Remote =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr1() : address =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr2() : oracle(int, bool) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr4() : address =
Address.to_contract(Contract.address)
+4
View File
@@ -0,0 +1,4 @@
contract C =
type id('a) = 'a
entrypoint f() : id = 123
entrypoint g() : id(int, int) = 123
+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)
+7 -9
View File
@@ -6,20 +6,18 @@ contract Events =
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string, string)
| BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
| BadEvent1(indexed string)
| BadEvent2(indexed alias_string)
function f1(x : int, y : string) =
entrypoint f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
entrypoint f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
entrypoint f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
entrypoint i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a)
+5 -6
View File
@@ -8,17 +8,16 @@ contract Events =
| Event2(alias_string, indexed alias_address)
| BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
function f1(x : int, y : string) =
entrypoint f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
entrypoint f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
entrypoint f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
entrypoint i2s(i : int) = Int.to_str(i)
entrypoint a2s(a : address) = Address.to_str(a)
+5
View File
@@ -0,0 +1,5 @@
contract C =
function
g(1) = 2
f(2) = 3
h(1) = 123
-6
View File
@@ -1,6 +0,0 @@
contract Bad =
include "included.aes"
namespace Foo =
function foo() = 42
function foo() = 43
+13
View File
@@ -0,0 +1,13 @@
contract BadInit =
type state = int
entrypoint new_state(n) = state + n
stateful entrypoint roundabout(n) = put(n)
stateful entrypoint set_state(n) = roundabout(n)
stateful entrypoint init() =
set_state(4)
new_state(0)
state + state
+6
View File
@@ -0,0 +1,6 @@
contract Test =
entrypoint f() = ()
entrypoint g(x : int, y : string) = f(1)
entrypoint h() = g(1)
entrypoint i() = g("Litwo, ojczyzno moja")
+5
View File
@@ -0,0 +1,5 @@
contract BadRecord =
entrypoint foo() =
let r = {x = 0, [0] = 1}
r{x = 0, [0] = 1}
r{}
+5
View File
@@ -0,0 +1,5 @@
contract C =
record state = { foo : int }
entrypoint init(i : int) =
state{ foo = i,
foo = 42 }
+3
View File
@@ -0,0 +1,3 @@
function square(x) = x ^ 2
contract Main =
entrypoint main() = square(10)
@@ -0,0 +1,5 @@
contract C =
entrypoint f() =
let z = 123
{}{ [1 = 0] = z + 1 }
2
+6 -6
View File
@@ -2,18 +2,18 @@
contract BasicAuth =
record state = { nonce : int, owner : address }
function init() = { nonce = 1, owner = Call.caller }
entrypoint init() = { nonce = 1, owner = Call.caller }
function authorize(n : int, s : signature) : bool =
stateful entrypoint authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify(to_sign(tx_hash, n), state.owner, s)
Some(tx_hash) => Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s)
function to_sign(h : hash, n : int) =
entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n))
private function require(b : bool, err : string) =
if(!b) abort(err)
entrypoint weird_string() : string =
"\x19Weird String\x42\nMore\n"
+16
View File
@@ -0,0 +1,16 @@
contract BitcoinAuth =
record state = { nonce : int, owner : bytes(20) }
entrypoint init(owner' : bytes(20)) = { nonce = 1, owner = owner' }
stateful entrypoint authorize(n : int, s : bytes(65)) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) : hash =
Crypto.blake2b((h, n))
+2 -2
View File
@@ -5,8 +5,8 @@ contract BuiltinBug =
record state = {proofs : map(address, list(string))}
public function init() = {proofs = {}}
entrypoint init() = {proofs = {}}
public stateful function createProof(hash : string) =
stateful entrypoint createProof(hash : string) =
put( state{ proofs[Call.caller] = hash :: state.proofs[Call.caller] } )
+3 -7
View File
@@ -1,12 +1,8 @@
contract TestContract =
record state = {
_allowed : map(address, map(address, int))}
record state = {_allowed : map(address, map(address, int))}
public stateful function init() = {
_allowed = {}}
public stateful function approve(spender: address, value: int) : bool =
entrypoint init() = {_allowed = {}}
stateful entrypoint approve(spender: address, value: int) : bool =
put(state{_allowed[Call.caller][spender] = value})
true
+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)
+18
View File
@@ -0,0 +1,18 @@
contract BytesEquality =
entrypoint eq16(a : bytes(16), b) = a == b
entrypoint ne16(a : bytes(16), b) = a != b
entrypoint eq32(a : bytes(32), b) = a == b
entrypoint ne32(a : bytes(32), b) = a != b
entrypoint eq47(a : bytes(47), b) = a == b
entrypoint ne47(a : bytes(47), b) = a != b
entrypoint eq64(a : bytes(64), b) = a == b
entrypoint ne64(a : bytes(64), b) = a != b
entrypoint eq65(a : bytes(65), b) = a == b
entrypoint ne65(a : bytes(65), b) = a != b
+8
View File
@@ -0,0 +1,8 @@
contract BytesToX =
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
entrypoint to_str(b : bytes(12)) : string =
String.concat(Bytes.to_str(b), Bytes.to_str(#ffff))
entrypoint to_str_big(b : bytes(65)) : string =
Bytes.to_str(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)

Some files were not shown because too many files have changed in this diff Show More