Compare commits

..

175 Commits

Author SHA1 Message Date
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
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
90 changed files with 3820 additions and 1958 deletions
+36 -2
View File
@@ -9,8 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### Removed
## [4.0.0-rc1] - 2019-08-22 ## [4.1.0] - 2019-11-26
### Added ### Added
- Support encoding and decoding bit fields in call arguments and results.
### Changed
- Various improvements to FATE code generator.
### Removed
## [4.0.0] - 2019-10-11
### Added
- `Address.to_contract` - casts an address to a (any) contract type.
- Pragma to check compiler version, e.g. `@compiler >= 4.0`.
- Handle numeric escapes, i.e. `"\x19Ethereum Signed Message:\n"`, and similar strings.
- `Bytes.concat` and `Bytes.split` are added to be able to
(de-)construct byte arrays.
- `[a..b]` language construct, returning the list of numbers between
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
- Checks that `init` is not called from other functions.
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many - FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
for. for.
@@ -27,6 +43,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
that shall be able to receive funds should be marked as payable. `Address.is_payable(a)` that shall be able to receive funds should be marked as payable. `Address.is_payable(a)`
can be used to check if an (contract) address is payable or not. can be used to check if an (contract) address is payable or not.
### Changed ### Changed
- Nice type error if contract function is called as from a namespace.
- Fail on function definitions in contracts other than the main contract.
- Bug fix in variable optimization - don't discard writes to the store/state.
- Bug fixes in error reporting.
- Bug fix in variable liveness analysis for FATE.
- Error messages are changed into a uniform format, and more helpful
messages have been added.
- `Crypto.<hash_fun>` and `String.<hash_fun>` for byte arrays now only
hash the actual byte array - not the internal ABI format.
- More strict checks for polymorphic oracles and higher order oracles
and entrypoints.
- `AENS.claim` is updated with a `NameFee` field - to be able to do
name auctions within contracts.
- Fixed a bug in `Bytes.to_str` for AEVM.
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and - New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and
regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`. regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`.
Parens are not necessary. Note it only affects the types, values remain as their were before, Parens are not necessary. Note it only affects the types, values remain as their were before,
@@ -134,7 +164,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...HEAD [Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0...HEAD
[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0
[4.0.0-rc5]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc4...v4.0.0-rc5
[4.0.0-rc4]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc3...v4.0.0-rc4
[4.0.0-rc3]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...v4.0.0-rc3
[4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1 [4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0 [3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0 [3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0
+46
View File
@@ -0,0 +1,46 @@
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)
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))
function curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) =
(x) => (y) => f(x, y)
function curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) =
(x) => (y) => (z) => f(x, y, z)
function uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c =
(x, y) => f(x)(y)
function uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd =
(x, y, z) => f(x)(y)(z)
function tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c =
(t) => switch(t)
(x, y) => f(x, y)
function tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd =
(t) => switch(t)
(x, y, z) => f(x, y, z)
function untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c =
(x, y) => f((x, y))
function untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd =
(x, y, z) => f((x, y, z))
+214
View File
@@ -0,0 +1,214 @@
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 find(p : 'a => bool, l : list('a)) : option('a) = switch(l)
[] => None
h::t => if(p(h)) Some(h) else find(p, t)
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)
function from_to(a : int, b : int) : list(int) = [a..b]
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)
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)
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)
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)
// TODO: Improve?
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => switch (partition((x) => lesser_cmp(x, h), t))
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
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)
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)
+76
View File
@@ -0,0 +1,76 @@
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
function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o)
None => n
Some(x) => s(x)
function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o)
function force(o : option('a)) : 'a = default(abort("Forced None value"), o)
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
function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o))
(Some(ff), Some(xx)) => Some(ff(xx))
_ => None
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]
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)
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)
function choose(o1 : option('a), o2 : option('a)) : option('a) =
if(is_some(o1)) o1 else o2
function choose_first(l : list(option('a))) : option('a) = switch(l)
[] => None
None::t => choose_first(t)
Some(x)::_ => Some(x)
+20
View File
@@ -0,0 +1,20 @@
namespace Pair =
function fst(t : ('a * 'b)) : 'a = switch(t)
(x, _) => x
function snd(t : ('a * 'b)) : 'b = switch(t)
(_, y) => y
function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t)
(x, y) => (f(x), y)
function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t)
(x, y) => (x, f(y))
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)
+37
View File
@@ -0,0 +1,37 @@
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
function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t)
(x, y, z) => (f(x), y, z)
function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t)
(x, y, z) => (x, f(y), z)
function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t)
(x, y, z) => (x, y, f(z))
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)
function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t)
(x, y, z) => (z, x, y)
function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t)
(x, y, z) => (y, z, x)
+2 -2
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"10cc127"}}} {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
@@ -15,7 +15,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}. ]}.
{relx, [{release, {aesophia, "4.0.0-rc1"}, {relx, [{release, {aesophia, "4.1.0"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+6 -2
View File
@@ -1,17 +1,21 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"10cc1278831ad7e90138533466ceef4bcafd74a9"}}, {ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}}, {ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
1}, 1},
{<<"base58">>, {<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git", {git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}}, {ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
2}, 2},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0}, {<<"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}, {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>, {<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git", {git,"https://github.com/talentdeficit/jsx.git",
+1 -13
View File
@@ -74,21 +74,9 @@ do_contract_interface(Type, ContractString, Options) ->
string -> do_render_aci_json(JArray) string -> do_render_aci_json(JArray)
end end
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, Errors}
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end. end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
encode_contract(Contract = {contract, _, {con, _, Name}, _}) -> encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
C0 = #{name => encode_name(Name)}, C0 = #{name => encode_name(Name)},
File diff suppressed because it is too large Load Diff
+153 -61
View File
@@ -20,7 +20,7 @@
-type fun_name() :: {entrypoint, binary()} -type fun_name() :: {entrypoint, binary()}
| {local_fun, [string()]} | {local_fun, [string()]}
| init | event. | event.
-type var_name() :: string(). -type var_name() :: string().
-type sophia_name() :: [string()]. -type sophia_name() :: [string()].
@@ -32,7 +32,7 @@
map_delete | map_member | map_size | string_length | map_delete | map_member | map_size | string_length |
string_concat | bits_set | bits_clear | bits_test | bits_sum | string_concat | bits_set | bits_clear | bits_test | bits_sum |
bits_intersection | bits_union | bits_difference | bits_intersection | bits_union | bits_difference |
contract_to_address | crypto_verify_sig | crypto_verify_sig_secp256k1 | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 |
crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_sha3 | crypto_sha256 | crypto_blake2b |
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1. crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
@@ -67,6 +67,7 @@
| {def_u, fun_name(), arity()} | {def_u, fun_name(), arity()}
| {remote_u, [ftype()], ftype(), fexpr(), fun_name()} | {remote_u, [ftype()], ftype(), fexpr(), fun_name()}
| {builtin_u, builtin(), arity()} | {builtin_u, builtin(), arity()}
| {builtin_u, builtin(), arity(), [fexpr()]} %% Typerep arguments to be added after normal args.
| {lam, [var_name()], fexpr()}. | {lam, [var_name()], fexpr()}.
-type fsplit() :: {split, ftype(), var_name(), [fcase()]} -type fsplit() :: {split, ftype(), var_name(), [fcase()]}
@@ -140,6 +141,7 @@
functions := #{ fun_name() => fun_def() } }. functions := #{ fun_name() => fun_def() } }.
-define(HASH_BYTES, 32). -define(HASH_BYTES, 32).
%% -- Entrypoint ------------------------------------------------------------- %% -- Entrypoint -------------------------------------------------------------
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2 %% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
@@ -185,7 +187,7 @@ builtins() ->
{["Oracle"], [{"register", 4}, {"query_fee", 1}, {"query", 5}, {"get_question", 2}, {["Oracle"], [{"register", 4}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
{"respond", 4}, {"extend", 3}, {"get_answer", 2}, {"respond", 4}, {"extend", 3}, {"get_answer", 2},
{"check", 1}, {"check_query", 2}]}, {"check", 1}, {"check_query", 2}]},
{["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 4}, {"transfer", 4}, {["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4},
{"revoke", 3}]}, {"revoke", 3}]},
{["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2}, {["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2},
{"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]},
@@ -196,9 +198,9 @@ builtins() ->
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
{["Bytes"], [{"to_int", 1}, {"to_str", 1}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
{["Int"], [{"to_str", 1}]}, {["Int"], [{"to_str", 1}]},
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} {["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
], ],
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes, || {NS, Funs} <- Scopes,
@@ -232,7 +234,7 @@ is_no_code(Env) ->
%% -- Compilation ------------------------------------------------------------ %% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode(). -spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) -> to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
#{ builtins := Builtins } = Env, #{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main}, MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none}, builtins => Builtins#{[Main, "state"] => {get_state, none},
@@ -247,8 +249,10 @@ to_fcode(Env, [{contract, Attrs, {con, _, Main}, Decls}]) ->
state_type => StateType, state_type => StateType,
event_type => EventType, event_type => EventType,
payable => Payable, payable => Payable,
functions => add_init_function(Env1, StateType, functions => add_init_function(Env1, MainCon, StateType,
add_event_function(Env1, EventType, Funs)) }; add_event_function(Env1, EventType, Funs)) };
to_fcode(_Env, [NotContract]) ->
fcode_error({last_declaration_must_be_contract, NotContract});
to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) -> to_fcode(Env, [{contract, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls), Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
to_fcode(Env1, Code); to_fcode(Env1, Code);
@@ -270,30 +274,34 @@ decls_to_fcode(Env, Decls) ->
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env, {type_decl, _, _, _}) -> Env;
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, Ann, {id, _, Name}, _}) -> decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of case is_no_code(Env) of
false -> fcode_error({missing_definition, Name, lists:keydelete(entrypoint, 1, Ann)}); false -> fcode_error({missing_definition, Id});
true -> Env true -> Env
end; end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env; decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) -> decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def); typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, Body}) -> decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
Attrs = get_attributes(Ann), Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)), FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args), FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body), FBody = expr_to_fcode(Env#{ vars => [X || {X, _} <- FArgs] }, Body),
[ ensure_first_order_entrypoint(Ann, Id, Args, Ret, FArgs, FRet)
|| aeso_syntax:get_ann(entrypoint, Ann, false) ],
Def = #{ attrs => Attrs, Def = #{ attrs => Attrs,
args => FArgs, args => FArgs,
return => type_to_fcode(Env, Ret), return => FRet,
body => FBody }, body => FBody },
NewFuns = Funs#{ FName => Def }, NewFuns = Funs#{ FName => Def },
Env#{ functions := NewFuns }. Env#{ functions := NewFuns }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env(). -spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) -> typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
Q = qname(Env, Name), Q = qname(Env, Name),
FDef = fun(Args) -> FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)), Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
case Def of case Def of
{record_t, Fields} -> {todo, Xs, Args, record_t, Fields}; {record_t, Fields} -> {todo, Xs, Args, record_t, Fields};
@@ -304,7 +312,9 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end || Con <- Cons ], end || Con <- Cons ],
{variant, FCons}; {variant, FCons};
{alias_t, Type} -> {todo, Xs, Args, alias_t, Type} {alias_t, Type} -> {todo, Xs, Args, alias_t, Type}
end end, end;
(Args) -> internal_error({type_arity_mismatch, Name, length(Args), length(Xs)})
end,
Constructors = Constructors =
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
@@ -325,6 +335,14 @@ typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
end, end,
bind_type(Env2, Q, FDef). bind_type(Env2, Q, FDef).
check_state_and_event_types(#{ context := {main_contract, _} }, Id, [_ | _]) ->
case Id of
{id, _, "state"} -> fcode_error({parameterized_state, Id});
{id, _, "event"} -> fcode_error({parameterized_event, Id});
_ -> ok
end;
check_state_and_event_types(_, _, _) -> ok.
-spec type_to_fcode(env(), aeso_syntax:type()) -> ftype(). -spec type_to_fcode(env(), aeso_syntax:type()) -> ftype().
type_to_fcode(Env, Type) -> type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type). type_to_fcode(Env, #{}, Type).
@@ -389,7 +407,31 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables %% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]); expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, _Type, {qid, _, X}) -> resolve_var(Env, X); expr_to_fcode(Env, Type, {qid, Ann, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
B =:= oracle_get_answer;
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
OType = get_oracle_type(B, Type),
{oracle, QType, RType} = type_to_fcode(Env, OType),
validate_oracle_type(Ann, OType, QType, RType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = aens_resolve, Ar} ->
{fun_t, _, _, _, ResType} = Type,
AensType = type_to_fcode(Env, ResType),
validate_aens_resolve_type(Ann, ResType, AensType),
TypeArgs = [{lit, {typerep, AensType}}],
{builtin_u, B, Ar, TypeArgs};
{builtin_u, B = bytes_split, Ar} ->
{fun_t, _, _, _, {tuple_t, _, [{bytes_t, _, N}, _]}} = Type,
{builtin_u, B, Ar, [{lit, {int, N}}]};
Other -> Other
end;
%% Constructors %% Constructors
expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon -> expr_to_fcode(Env, Type, {C, _, _} = Con) when C == con; C == qcon ->
@@ -399,7 +441,7 @@ expr_to_fcode(Env, _Type, {app, _, {typed, _, {C, _, _} = Con, _}, Args}) when C
Arity = lists:nth(I + 1, Arities), Arity = lists:nth(I + 1, Arities),
case length(Args) == Arity of case length(Args) == Arity of
true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]}; true -> {con, Arities, I, [expr_to_fcode(Env, Arg) || Arg <- Args]};
false -> fcode_error({constructor_arity_mismatch, Con, length(Args), Arity}) false -> internal_error({constructor_arity_mismatch, Con, length(Args), Arity})
end; end;
%% Tuples %% Tuples
@@ -453,12 +495,16 @@ expr_to_fcode(Env, _Type, {list, _, Es}) ->
lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end, lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end,
nil, Es); nil, Es);
expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) ->
{def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]),
{def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) -> expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) ->
{op, '::', [expr_to_fcode(Env, Yield), nil]}; {op, '::', [expr_to_fcode(Env, Yield), nil]};
expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, {typed, {id, _, Arg}, _}, BindExpr}|Rest]}) -> expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, {typed, {id, _, Arg}, _}, BindExpr}|Rest]}) ->
Env1 = bind_var(Env, Arg), Env1 = bind_var(Env, Arg),
Bind = {lam, [Arg], expr_to_fcode(Env1, {list_comp, As, Yield, Rest})}, Bind = {lam, [Arg], expr_to_fcode(Env1, {list_comp, As, Yield, Rest})},
{def_u, FlatMap, _} = resolve_fun(Env, ["List", "flat_map"]), {def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]),
{def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]}; {def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]};
expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) -> expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) ->
make_if(expr_to_fcode(Env, Cond), make_if(expr_to_fcode(Env, Cond),
@@ -506,29 +552,13 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end; end;
%% Function calls %% Function calls
expr_to_fcode(Env, Type, {app, _Ann, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) -> expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args), Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1], FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of case expr_to_fcode(Env, Fun) of
{builtin_u, B, _} when B =:= oracle_query; {builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(B, FArgs ++ TypeArgs);
B =:= oracle_get_question; {builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
B =:= oracle_get_answer; {def_u, F, _Ar} -> {def, F, FArgs};
B =:= oracle_respond;
B =:= oracle_register;
B =:= oracle_check;
B =:= oracle_check_query ->
%% Get the type of the oracle from the args or the expression itself
OType = get_oracle_type(B, Type, Args1),
{oracle, QType, RType} = type_to_fcode(Env, OType),
TypeArgs = [{lit, {typerep, QType}}, {lit, {typerep, RType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _} when B =:= aens_resolve ->
%% Get the type we are assuming the name resolves to
AensType = type_to_fcode(Env, Type),
TypeArgs = [{lit, {typerep, AensType}}],
builtin_to_fcode(B, FArgs ++ TypeArgs);
{builtin_u, B, _Ar} -> builtin_to_fcode(B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs}; {remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
FFun -> FFun ->
%% FFun is a closure, with first component the function name and %% FFun is a closure, with first component the function name and
@@ -592,13 +622,60 @@ make_if(Cond, Then, Else) ->
{'let', X, Cond, make_if({var, X}, Then, Else)}. {'let', X, Cond, make_if({var, X}, Then, Else)}.
get_oracle_type(oracle_register, OType, _Args) -> OType; get_oracle_type(oracle_register, {fun_t, _, _, _, OType}) -> OType;
get_oracle_type(oracle_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_question, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_question, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_get_answer, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_get_answer, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check, _Type, [{typed, _, _Expr, OType}]) -> OType; get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, _Type, [{typed, _, _Expr, OType} | _]) -> OType; get_oracle_type(oracle_check_query, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_respond, _Type, [_, {typed, _,_Expr, OType} | _]) -> OType. get_oracle_type(oracle_respond, {fun_t, _, _, [OType | _], _}) -> OType.
validate_oracle_type(Ann, Type, QType, RType) ->
ensure_monomorphic(QType, {invalid_oracle_type, polymorphic, query, Ann, Type}),
ensure_monomorphic(RType, {invalid_oracle_type, polymorphic, response, Ann, Type}),
ensure_first_order(QType, {invalid_oracle_type, higher_order, query, Ann, Type}),
ensure_first_order(RType, {invalid_oracle_type, higher_order, response, Ann, Type}),
ok.
validate_aens_resolve_type(Ann, {app_t, _, _, [Type]}, {variant, [[], [FType]]}) ->
case FType of
string -> ok;
address -> ok;
contract -> ok;
{oracle, _, _} -> ok;
oracle_query -> ok;
_ -> fcode_error({invalid_aens_resolve_type, Ann, Type})
end.
ensure_first_order_entrypoint(Ann, Id = {id, _, Name}, Args, Ret, FArgs, FRet) ->
[ ensure_first_order(FT, {invalid_entrypoint, higher_order, Ann1, Id, {argument, X, T}})
|| {{arg, Ann1, X, T}, {_, FT}} <- lists:zip(Args, FArgs) ],
[ ensure_first_order(FRet, {invalid_entrypoint, higher_order, Ann, Id, {result, Ret}})
|| Name /= "init" ], %% init can return higher-order values, since they're written to the store
%% rather than being returned.
ok.
ensure_monomorphic(Type, Err) ->
case is_monomorphic(Type) of
true -> ok;
false -> fcode_error(Err)
end.
ensure_first_order(Type, Err) ->
case is_first_order(Type) of
true -> ok;
false -> fcode_error(Err)
end.
is_monomorphic({tvar, _}) -> false;
is_monomorphic(Ts) when is_list(Ts) -> lists:all(fun is_monomorphic/1, Ts);
is_monomorphic(Tup) when is_tuple(Tup) -> is_monomorphic(tuple_to_list(Tup));
is_monomorphic(_) -> true.
is_first_order({function, _, _}) -> false;
is_first_order(Ts) when is_list(Ts) -> lists:all(fun is_first_order/1, Ts);
is_first_order(Tup) when is_tuple(Tup) -> is_first_order(tuple_to_list(Tup));
is_first_order(_) -> true.
%% -- Pattern matching -- %% -- Pattern matching --
@@ -827,6 +904,7 @@ op_builtins() ->
string_length, string_concat, string_sha3, string_sha256, string_blake2b, string_length, string_concat, string_sha3, string_sha256, string_blake2b,
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
bits_difference, int_to_str, address_to_str, crypto_verify_sig, bits_difference, int_to_str, address_to_str, crypto_verify_sig,
address_to_contract,
crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1 crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
]. ].
@@ -855,19 +933,18 @@ builtin_to_fcode(Builtin, Args) ->
%% -- Init function -- %% -- Init function --
add_init_function(Env, StateType, Funs0) -> add_init_function(Env, Main, StateType, Funs0) ->
case is_no_code(Env) of case is_no_code(Env) of
true -> Funs0; true -> Funs0;
false -> false ->
Funs = add_default_init_function(Env, StateType, Funs0), Funs = add_default_init_function(Env, Main, StateType, Funs0),
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
InitFun = #{ args := InitArgs } = maps:get(InitName, Funs, none), InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Vars = [ {var, X} || {X, _} <- InitArgs ], Funs#{ InitName => InitFun#{ return => {tuple, []},
Funs#{ init => InitFun#{ return => {tuple, []}, body => {builtin, set_state, [InitBody]} } }
body => {builtin, set_state, [{def, InitName, Vars}]} } }
end. end.
add_default_init_function(_Env, StateType, Funs) -> add_default_init_function(_Env, Main, StateType, Funs) ->
InitName = {entrypoint, <<"init">>}, InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of case maps:get(InitName, Funs, none) of
%% Only add default init function if state is unit. %% Only add default init function if state is unit.
@@ -876,7 +953,7 @@ add_default_init_function(_Env, StateType, Funs) ->
args => [], args => [],
return => {tuple, []}, return => {tuple, []},
body => {tuple, []}} }; body => {tuple, []}} };
none -> fcode_error(missing_init_function); none -> fcode_error({missing_init_function, Main});
_ -> Funs _ -> Funs
end. end.
@@ -958,9 +1035,14 @@ make_closure(FVs, Xs, Body) ->
lambda_lift_expr({lam, Xs, Body}) -> lambda_lift_expr({lam, Xs, Body}) ->
FVs = free_vars({lam, Xs, Body}), FVs = free_vars({lam, Xs, Body}),
make_closure(FVs, Xs, lambda_lift_expr(Body)); make_closure(FVs, Xs, lambda_lift_expr(Body));
lambda_lift_expr({Tag, F, Ar}) when Tag == def_u; Tag == builtin_u -> lambda_lift_expr(UExpr) when element(1, UExpr) == def_u; element(1, UExpr) == builtin_u ->
[Tag, F, Ar | _] = tuple_to_list(UExpr),
ExtraArgs = case UExpr of
{builtin_u, _, _, TypeArgs} -> TypeArgs;
_ -> []
end,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ], Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, Ar) ],
Args = [{var, X} || X <- Xs], Args = [{var, X} || X <- Xs] ++ ExtraArgs,
Body = case Tag of Body = case Tag of
builtin_u -> builtin_to_fcode(F, Args); builtin_u -> builtin_to_fcode(F, Args);
def_u -> {def, F, Args} def_u -> {def, F, Args}
@@ -1067,7 +1149,7 @@ lookup_type(Env, {qid, _, Name}, Args) ->
lookup_type(Env, Name, Args); lookup_type(Env, Name, Args);
lookup_type(Env, Name, Args) -> lookup_type(Env, Name, Args) ->
case lookup_type(Env, Name, Args, not_found) of case lookup_type(Env, Name, Args, not_found) of
not_found -> error({unknown_type, Name}); not_found -> internal_error({unknown_type, Name});
Type -> Type Type -> Type
end. end.
@@ -1152,7 +1234,7 @@ resolve_var(Env, Q) -> resolve_fun(Env, Q).
resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) -> resolve_fun(#{ fun_env := Funs, builtins := Builtin }, Q) ->
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
{not_found, not_found} -> fcode_error({unbound_variable, Q}); {not_found, not_found} -> internal_error({unbound_variable, Q});
{_, {B, none}} -> {builtin, B, []}; {_, {B, none}} -> {builtin, B, []};
{_, {B, Ar}} -> {builtin_u, B, Ar}; {_, {B, Ar}} -> {builtin_u, B, Ar};
{{Fun, Ar}, _} -> {def_u, Fun, Ar} {{Fun, Ar}, _} -> {def_u, Fun, Ar}
@@ -1210,6 +1292,7 @@ free_vars(Expr) ->
{remote_u, _, _, Ct, _} -> free_vars(Ct); {remote_u, _, _, Ct, _} -> free_vars(Ct);
{builtin, _, As} -> free_vars(As); {builtin, _, As} -> free_vars(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> []; %% Typereps are always literals
{con, _, _, As} -> free_vars(As); {con, _, _, As} -> free_vars(As);
{tuple, As} -> free_vars(As); {tuple, As} -> free_vars(As);
{proj, A, _} -> free_vars(A); {proj, A, _} -> free_vars(A);
@@ -1238,6 +1321,7 @@ used_defs(Expr) ->
{remote_u, _, _, Ct, _} -> used_defs(Ct); {remote_u, _, _, Ct, _} -> used_defs(Ct);
{builtin, _, As} -> used_defs(As); {builtin, _, As} -> used_defs(As);
{builtin_u, _, _} -> []; {builtin_u, _, _} -> [];
{builtin_u, _, _, _} -> [];
{con, _, _, As} -> used_defs(As); {con, _, _, As} -> used_defs(As);
{tuple, As} -> used_defs(As); {tuple, As} -> used_defs(As);
{proj, A, _} -> used_defs(A); {proj, A, _} -> used_defs(A);
@@ -1278,6 +1362,7 @@ rename(Ren, Expr) ->
{def_u, _, _} -> Expr; {def_u, _, _} -> Expr;
{builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]}; {builtin, B, Es} -> {builtin, B, [rename(Ren, E) || E <- Es]};
{builtin_u, _, _} -> Expr; {builtin_u, _, _} -> Expr;
{builtin_u, _, _, _} -> Expr;
{remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]}; {remote, ArgsT, RetT, Ct, F, Es} -> {remote, ArgsT, RetT, rename(Ren, Ct), F, [rename(Ren, E) || E <- Es]};
{remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F}; {remote_u, ArgsT, RetT, Ct, F} -> {remote_u, ArgsT, RetT, rename(Ren, Ct), F};
{con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]}; {con, Ar, I, Es} -> {con, Ar, I, [rename(Ren, E) || E <- Es]};
@@ -1392,8 +1477,14 @@ get_attributes(Ann) ->
indexed(Xs) -> indexed(Xs) ->
lists:zip(lists:seq(1, length(Xs)), Xs). lists:zip(lists:seq(1, length(Xs)), Xs).
fcode_error(Err) -> -dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
error(Err).
fcode_error(Error) ->
aeso_errors:throw(aeso_code_errors:format(Error)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
aeso_errors:throw(aeso_errors:new(internal_error, aeso_errors:pos(0, 0), Msg)).
%% -- Pretty printing -------------------------------------------------------- %% -- Pretty printing --------------------------------------------------------
@@ -1411,7 +1502,6 @@ pp_fun(Name, #{ args := Args, return := Return, body := Body }) ->
pp_text(" : "), pp_ftype(Return), pp_text(" =")]), pp_text(" : "), pp_ftype(Return), pp_text(" =")]),
prettypr:nest(2, pp_fexpr(Body))). prettypr:nest(2, pp_fexpr(Body))).
pp_fun_name(init) -> pp_text('INIT');
pp_fun_name(event) -> pp_text(event); pp_fun_name(event) -> pp_text(event);
pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E)); pp_fun_name({entrypoint, E}) -> pp_text(binary_to_list(E));
pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")). pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
@@ -1419,7 +1509,8 @@ pp_fun_name({local_fun, Q}) -> pp_text(string:join(Q, ".")).
pp_text(<<>>) -> prettypr:text("\"\""); pp_text(<<>>) -> prettypr:text("\"\"");
pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)]))); pp_text(Bin) when is_binary(Bin) -> prettypr:text(lists:flatten(io_lib:format("~p", [binary_to_list(Bin)])));
pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S])); pp_text(S) when is_list(S) -> prettypr:text(lists:concat([S]));
pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A)). pp_text(A) when is_atom(A) -> prettypr:text(atom_to_list(A));
pp_text(N) when is_integer(N) -> prettypr:text(integer_to_list(N)).
pp_int(I) -> prettypr:text(integer_to_list(I)). pp_int(I) -> prettypr:text(integer_to_list(I)).
@@ -1493,6 +1584,8 @@ pp_fexpr({'let', X, A, B}) ->
pp_fexpr(B)]); pp_fexpr(B)]);
pp_fexpr({builtin_u, B, N}) -> pp_fexpr({builtin_u, B, N}) ->
pp_beside([pp_text(B), pp_text("/"), pp_text(N)]); pp_beside([pp_text(B), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin_u, B, N, TypeArgs}) ->
pp_beside([pp_text(B), pp_text("@"), pp_fexpr({tuple, TypeArgs}), pp_text("/"), pp_text(N)]);
pp_fexpr({builtin, B, As}) -> pp_fexpr({builtin, B, As}) ->
pp_call(pp_text(B), As); pp_call(pp_text(B), As);
pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) -> pp_fexpr({remote_u, ArgsT, RetT, Ct, Fun}) ->
@@ -1551,4 +1644,3 @@ pp_pat(Pat) -> pp_fexpr(Pat).
is_infix(Op) -> is_infix(Op) ->
C = hd(atom_to_list(Op)), C = hd(atom_to_list(Op)),
C < $a orelse C > $z. C < $a orelse C > $z.
+447 -330
View File
File diff suppressed because it is too large Load Diff
+131 -24
View File
@@ -10,6 +10,7 @@
-module(aeso_builtins). -module(aeso_builtins).
-export([ builtin_function/1 -export([ builtin_function/1
, bytes_to_raw_string/2
, check_event_type/1 , check_event_type/1
, used_builtins/1 ]). , used_builtins/1 ]).
@@ -44,7 +45,7 @@ builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}]; builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}]; builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker]; builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
builtin_deps1(string_reverse) -> [string_reverse_]; builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort]; builtin_deps1(require) -> [abort];
builtin_deps1(_) -> []. builtin_deps1(_) -> [].
@@ -89,7 +90,13 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)). -define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)). -define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}. op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
%% We generate a lot of B * 8 for integer B from BSL and BSR.
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
{integer, A * B};
simpl(Op) -> Op.
operand(A) when is_atom(A) -> v(A); operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I}; operand(I) when is_integer(I) -> {integer, I};
@@ -161,7 +168,10 @@ builtin_function(BF) ->
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N)); {bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N)); {bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker()); bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_()) string_reverse_ -> bfun(BF, builtin_string_reverse_())
end. end.
@@ -512,40 +522,60 @@ builtin_bytes_to_int(N) when N > 32 ->
end, end,
{[{"b", pointer}], Body, word}. {[{"b", pointer}], Body, word}.
builtin_bytes_to_str_worker() -> %% Two versions of this helper function, worker for sections not even 16 bytes long
%% and worker_x for the full sized chunks.
builtin_bytes_to_str_worker_x() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>, <<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}], {[{"w", word}, {"offs", word}, {"acc", word}],
{seq, [{ifte, ?AND(?GT(offs, 0), ?EQ(0, ?MOD(offs, 16))), {ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
{seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}]}, ?LET(b, ?BYTE(offs, w),
{inline_asm, []}}, ?LET(lo, ?BYTE(?MOD(b, 16), Tab),
{ifte, ?EQ(offs, 32), {inline_asm, [?A(?MSIZE)]}, ?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?LET(b, ?BYTE(offs, w), ?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
?LET(lo, ?BYTE(?MOD(b, 16), Tab), },
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker,
[?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
}
]},
word}. word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
},
word}.
builtin_bytes_to_str_body(Var, N) when N < 16 ->
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
builtin_bytes_to_str_body(Var, 16) ->
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N < 32 ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
builtin_bytes_to_str_body(Var, 32) ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N > 32 ->
WholeWords = ((N + 31) div 32) - 1,
lists:append(
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|| I <- lists:seq(0, WholeWords - 1) ]) ++
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
builtin_bytes_to_str(N) when N =< 32 -> builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}], {[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)]), builtin_bytes_to_str_body(w, N) ++
{inline_asm, [?A(?POP)]}, [{inline_asm, [?A(?POP)]}, ?V(ret)]}),
?V(ret)]}),
string}; string};
builtin_bytes_to_str(N) when N > 32 -> builtin_bytes_to_str(N) when N > 32 ->
Work = fun(I) ->
[?DEREF(w, ?ADD(p, 32 * I), ?call(bytes_to_str_worker, [?V(w), ?I(0), ?I(0)])),
{inline_asm, [?A(?POP)]}]
end,
{[{"p", pointer}], {[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++ {seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
lists:append([ Work(I) || I <- lists:seq(0, (N + 31) div 32 - 1) ]) ++ builtin_bytes_to_str_body(p, N) ++
[?V(ret)]}), [{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string}. string}.
builtin_string_reverse() -> builtin_string_reverse() ->
@@ -575,3 +605,80 @@ builtin_string_reverse_() ->
builtin_addr_to_str() -> builtin_addr_to_str() ->
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}. {[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
%% At most one word
%% | ..... | ========= | ........ |
%% Offs ^ ^- Len -^ TotalLen ^
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
%% Bytes are packed into a single word
Masked =
case Offs of
0 -> Bytes;
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
end,
Unpadded =
case 32 - (Offs + Len) of
0 -> Masked;
N -> ?BSR(Masked, N)
end,
case Len of
32 -> Unpadded;
_ -> ?BSL(Unpadded, 32 - Len)
end;
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
%% Might read one word more than necessary.
Word = op('!', Offs, Bytes),
case Len == 32 of
true -> Word;
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
end.
builtin_bytes_concat(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Words = fun(N) -> (N + 31) div 32 end,
WordsRes = Words(A + B),
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
(I) when 32 * I < A ->
Len = A rem 32,
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
?ADD(Hi, ?BSR(Lo, Len));
(I) ->
Offs = 32 * I - A,
Len = min(32, B - Offs),
bytes_slice(Offs, Len, B, ?V(b))
end,
Body =
case {A, B} of
{0, _} -> ?V(b);
{_, 0} -> ?V(a);
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
end,
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
builtin_bytes_split(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Word = fun(I, Max) ->
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
end,
Body =
case {A, B} of
{0, _} -> [?I(0), ?V(c)];
{_, 0} -> [?V(c), ?I(0)];
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
end,
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
bytes_to_raw_string(N, Term) when N =< 32 ->
{tuple, [?I(N), Term]};
bytes_to_raw_string(N, Term) when N > 32 ->
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
end,
Words = (N + 31) div 32,
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
+120
View File
@@ -0,0 +1,120 @@
%%%-------------------------------------------------------------------
%%% @author Ulf Norell
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc
%%% Formatting of code generation errors.
%%% @end
%%%
%%%-------------------------------------------------------------------
-module(aeso_code_errors).
-export([format/1, pos/1]).
format({last_declaration_must_be_contract, Decl = {namespace, _, {con, _, C}, _}}) ->
Msg = io_lib:format("Expected a contract as the last declaration instead of the namespace '~s'\n",
[C]),
mk_err(pos(Decl), Msg);
format({missing_init_function, Con}) ->
Msg = io_lib:format("Missing init function for the contract '~s'.\n", [pp_expr(Con)]),
Cxt = "The 'init' function can only be omitted if the state type is 'unit'.\n",
mk_err(pos(Con), Msg, Cxt);
format({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function '~s'.\n", [pp_expr(Id)]),
mk_err(pos(Id), Msg);
format({parameterized_state, Decl}) ->
Msg = "The state type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({parameterized_event, Decl}) ->
Msg = "The event type cannot be parameterized.\n",
mk_err(pos(Decl), Msg);
format({invalid_entrypoint, Why, Ann, {id, _, Name}, Thing}) ->
What = case Why of higher_order -> "higher-order (contains function types)";
polymorphic -> "polymorphic (contains type variables)" end,
ThingS = case Thing of
{argument, X, T} -> io_lib:format("argument\n~s\n", [pp_typed(X, T)]);
{result, T} -> io_lib:format("return type\n~s\n", [pp_type(2, T)])
end,
Bad = case Thing of
{argument, _, _} -> io_lib:format("has a ~s type", [What]);
{result, _} -> io_lib:format("is ~s", [What])
end,
Msg = io_lib:format("The ~sof entrypoint '~s' ~s.\n",
[ThingS, Name, Bad]),
case Why of
polymorphic -> mk_err(pos(Ann), Msg, "Use the FATE backend if you want polymorphic entrypoints.\n");
higher_order -> mk_err(pos(Ann), Msg)
end;
format({cant_compare_type_aevm, Ann, Op, Type}) ->
StringAndTuple = [ "- type string\n"
"- tuple or record of word type\n" || lists:member(Op, ['==', '!=']) ],
Msg = io_lib:format("Cannot compare values of type\n"
"~s\n"
"The AEVM only supports '~s' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"~s",
[pp_type(2, Type), Op, StringAndTuple]),
Cxt = "Use FATE if you need to compare arbitrary types.\n",
mk_err(pos(Ann), Msg, Cxt);
format({invalid_aens_resolve_type, Ann, T}) ->
Msg = io_lib:format("Invalid return type of AENS.resolve:\n"
"~s\n"
"It must be a string or a pubkey type (address, oracle, etc).\n",
[pp_type(2, T)]),
mk_err(pos(Ann), Msg);
format({unapplied_contract_call, Contract}) ->
Msg = io_lib:format("The AEVM does not support unapplied contract call to\n"
"~s\n", [pp_expr(2, Contract)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Contract), Msg, Cxt);
format({unapplied_builtin, Id}) ->
Msg = io_lib:format("The AEVM does not support unapplied use of ~s.\n", [pp_expr(0, Id)]),
Cxt = "Use FATE if you need this.\n",
mk_err(pos(Id), Msg, Cxt);
format({invalid_map_key_type, Why, Ann, Type}) ->
Msg = io_lib:format("Invalid map key type\n~s\n", [pp_type(2, Type)]),
Cxt = case Why of
polymorphic -> "Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.\n";
function -> "Map keys cannot be higher-order.\n"
end,
mk_err(pos(Ann), Msg, Cxt);
format({invalid_oracle_type, Why, What, Ann, Type}) ->
WhyS = case Why of higher_order -> "higher-order (contain function types)";
polymorphic -> "polymorphic (contain type variables)" end,
Msg = io_lib:format("Invalid oracle type\n~s\n", [pp_type(2, Type)]),
Cxt = io_lib:format("The ~s type must not be ~s.\n", [What, WhyS]),
mk_err(pos(Ann), Msg, Cxt);
format({higher_order_state, {type_def, Ann, _, _, State}}) ->
Msg = io_lib:format("Invalid state type\n~s\n", [pp_type(2, State)]),
Cxt = "The state cannot contain functions in the AEVM. Use FATE if you need this.\n",
mk_err(pos(Ann), Msg, Cxt);
format(Err) ->
mk_err(aeso_errors:pos(0, 0), io_lib:format("Unknown error: ~p\n", [Err])).
pos(Ann) ->
File = aeso_syntax:get_ann(file, Ann, no_file),
Line = aeso_syntax:get_ann(line, Ann, 0),
Col = aeso_syntax:get_ann(col, Ann, 0),
aeso_errors:pos(File, Line, Col).
pp_typed(E, T) ->
prettypr:format(prettypr:nest(2,
lists:foldr(fun prettypr:beside/2, prettypr:empty(),
[aeso_pretty:expr(E), prettypr:text(" : "),
aeso_pretty:type(T)]))).
pp_expr(E) ->
pp_expr(0, E).
pp_expr(N, E) ->
prettypr:format(prettypr:nest(N, aeso_pretty:expr(E))).
pp_type(N, T) ->
prettypr:format(prettypr:nest(N, aeso_pretty:type(T))).
mk_err(Pos, Msg) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg)).
mk_err(Pos, Msg, Cxt) ->
aeso_errors:new(code_error, Pos, lists:flatten(Msg), lists:flatten(Cxt)).
+179 -147
View File
@@ -15,6 +15,7 @@
, create_calldata/3 %% deprecated , create_calldata/3 %% deprecated
, create_calldata/4 , create_calldata/4
, version/0 , version/0
, numeric_version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/5 , to_sophia_value/5
@@ -22,6 +23,7 @@
, decode_calldata/4 , decode_calldata/4
, parse/2 , parse/2
, add_include_path/2 , add_include_path/2
, validate_byte_code/3
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
@@ -36,7 +38,6 @@
| pp_assembler | pp_assembler
| pp_bytecode | pp_bytecode
| no_code | no_code
| no_implicit_stdlib
| {backend, aevm | fate} | {backend, aevm | fate}
| {include, {file_system, [string()]} | | {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}} {explicit_files, #{string() => binary()}}}
@@ -66,18 +67,29 @@ version() ->
{ok, list_to_binary(VsnString)} {ok, list_to_binary(VsnString)}
end. end.
-spec file(string()) -> {ok, map()} | {error, binary()}. -spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}.
numeric_version() ->
case version() of
{ok, Bin} ->
[NoSuf | _] = binary:split(Bin, <<"-">>),
Numbers = binary:split(NoSuf, <<".">>, [global]),
{ok, [binary_to_integer(Num) || Num <- Numbers]};
{error, _} = Err ->
Err
end.
-spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
-spec file(string(), options()) -> {ok, map()} | {error, binary()}. -spec file(string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(File, Options0) -> file(File, Options0) ->
Options = add_include_path(File, Options0), Options = add_include_path(File, Options0),
case read_contract(File) of case read_contract(File) of
{ok, Bin} -> from_string(Bin, [{src_file, File} | Options]); {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} -> {error, Error} ->
ErrorString = [File,": ",file:format_error(Error)], Msg = lists:flatten([File,": ",file:format_error(Error)]),
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)} {error, [aeso_errors:new(file_error, Msg)]}
end. end.
add_include_path(File, Options) -> add_include_path(File, Options) ->
@@ -89,7 +101,7 @@ add_include_path(File, Options) ->
[{include, {file_system, [Cwd, Dir]}} | Options] [{include, {file_system, [Cwd, Dir]}} | Options]
end. end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
from_string(Contract, Options) -> from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options). from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
@@ -99,22 +111,14 @@ from_string(Backend, ContractString, Options) ->
try try
from_string1(Backend, ContractString, Options) from_string1(Backend, ContractString, Options)
catch catch
%% The compiler errors. throw:{error, Errors} -> {error, Errors}
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end. end.
from_string1(aevm, ContractString, Options) -> from_string1(aevm, ContractString, Options) ->
#{icode := Icode} = string_to_code(ContractString, Options), #{icode := Icode} = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode), TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options), Assembler = assemble(Icode, Options),
pp_assembler(Assembler, Options), pp_assembler(aevm, Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options), ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options), pp_bytecode(ByteCode, Options),
@@ -129,6 +133,7 @@ from_string1(aevm, ContractString, Options) ->
from_string1(fate, ContractString, Options) -> from_string1(fate, ContractString, Options) ->
#{fcode := FCode} = string_to_code(ContractString, Options), #{fcode := FCode} = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options), FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []), ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(), {ok, Version} = version(),
{ok, #{byte_code => ByteCode, {ok, #{byte_code => ByteCode,
@@ -142,19 +147,10 @@ from_string1(fate, ContractString, Options) ->
-spec string_to_code(string(), options()) -> map(). -spec string_to_code(string(), options()) -> map().
string_to_code(ContractString, Options) -> string_to_code(ContractString, Options) ->
Ast = case lists:member(no_implicit_stdlib, Options) of Ast = parse(ContractString, Options),
true -> parse(ContractString, Options);
false ->
IncludedSTD = sets:from_list(
[aeso_parser:hash_include(F, C)
|| {F, C} <- aeso_stdlib:stdlib_list()]),
InitAst = parse(ContractString, IncludedSTD, Options),
STD = parse_stdlib(),
STD ++ InitAst
end,
pp_sophia_code(Ast, Options), pp_sophia_code(Ast, Options),
pp_ast(Ast, Options), pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(TypedAst, Options), pp_typed_ast(TypedAst, Options),
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
@@ -170,10 +166,6 @@ string_to_code(ContractString, Options) ->
type_env => TypeEnv} type_env => TypeEnv}
end. end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode"). -define(DECODE_NAME, "__decode").
@@ -185,7 +177,7 @@ join_errors(Prefix, Errors, Pfun) ->
%% a special return type (typerep, T) %% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} -spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]} | {ok, string(), [term()]}
| {error, term()} | {error, [aeso_errors:error()]}
when Type :: term(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of case check_call1(Source, FunName, Args, Options) of
@@ -240,16 +232,7 @@ check_call1(ContractString0, FunName, Args, Options) ->
{ok, FunName, CallArgs} {ok, FunName, CallArgs}
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, Errors}
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
arguments_of_body(CallName, _FunName, Fcode) -> arguments_of_body(CallName, _FunName, Fcode) ->
@@ -297,27 +280,34 @@ last_contract_indent(Decls) ->
end. end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) -> -spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(ContractString, Fun, ResType, Data) -> to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]). to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) -> -spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}. {ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(_, _, error, Err, _Options) -> to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}}; {ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) -> to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
case aeb_heap:from_binary(string, Data) of case aeb_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}; {ok, Err} ->
{error, _} = Err -> Err {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
Err = aeb_fate_encoding:deserialize(Data), try aeb_fate_encoding:deserialize(Data) of
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}} Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end
end; end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) -> to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_implicit_stdlib, no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
@@ -334,51 +324,43 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)} {ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]))], [Data, VmType, Type0Str]),
fun (E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
try try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
{error, join_errors("Translation error", Type1 = prettypr:format(aeso_pretty:type(Type)),
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type]))], [aeb_fate_encoding:deserialize(Data), Type1]),
fun (E) -> E end)}; {error, [aeso_errors:new(data_error, Msg)]};
_:R -> _:_ ->
{error, iolist_to_binary(io_lib:format("Decode error ~p: ~p\n", [R, erlang:get_stacktrace()]))} Type1 = prettypr:format(aeso_pretty:type(Type)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, Errors}
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
-spec create_calldata(string(), string(), [string()]) -> -spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()} {ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, term()}. | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) -> create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]). create_calldata(Code, Fun, Args, [{backend, aevm}]).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) -> -spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()} {ok, binary()} | {error, [aeso_errors:error()]}.
| {error, term()}.
create_calldata(Code, Fun, Args, Options0) -> create_calldata(Code, Fun, Args, Options0) ->
Options = [no_implicit_stdlib, no_code | Options0], Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of case proplists:get_value(backend, Options, aevm) of
aevm -> aevm ->
case check_call(Code, Fun, Args, Options) of case check_call(Code, Fun, Args, Options) of
@@ -396,12 +378,12 @@ create_calldata(Code, Fun, Args, Options0) ->
-spec decode_calldata(string(), string(), binary()) -> -spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]} {ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, term()}. | {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata) -> decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]). decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, Options0) -> decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_implicit_stdlib, no_code | Options0], Options = [no_code | Options0],
try try
Code = string_to_code(ContractString, Options), Code = string_to_code(ContractString, Options),
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code, #{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
@@ -423,15 +405,14 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
%% Values are Sophia expressions in AST format %% Values are Sophia expressions in AST format
{ok, ArgTypes, Values} {ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n", [VmValue, VmType, Type0Str]),
[VmValue, VmType, Type0Str]))], {error, [aeso_errors:new(data_error, Msg)]}
fun (E) -> E end)}
end; end;
{error, _Err} -> {error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))], Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end; end;
fate -> fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of case aeb_fate_abi:decode_calldata(FunName, Calldata) of
@@ -443,35 +424,30 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
{ok, ArgTypes, AstArgs} {ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia -> catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)), Type0Str = prettypr:format(aeso_pretty:type(Type0)),
{error, join_errors("Translation error", Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n", [FateArgs, Type0Str]),
[FateArgs, Type0Str]))], {error, [aeso_errors:new(data_error, Msg)]}
fun (E) -> E end)}
end; end;
{error, _} -> {error, _} ->
{error, join_errors("Decode errors", ["Failed to decode binary"], Msg = io_lib:format("Failed to decode calldata binary\n", []),
fun(E) -> E end)} {error, [aeso_errors:new(data_error, Msg)]}
end end
end end
catch catch
error:{parse_errors, Errors} -> throw:{error, Errors} -> {error, Errors}
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args; [Args] -> Args;
[] -> error({missing_call_function, Funs}) [] -> error_missing_call_function()
end. end.
-dialyzer({nowarn_function, error_missing_call_function/0}).
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
@@ -479,13 +455,14 @@ get_call_type([{contract, _, _, Defs}]) ->
{app, _, {app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> error_missing_call_function()
end; end;
get_call_type([_ | Contracts]) -> get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract %% The __call should be in the final contract
get_call_type(Contracts). get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) -> -dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}]; GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}]; ({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end, (_) -> [] end,
@@ -494,7 +471,10 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
[] -> [] ->
case FunName of case FunName of
"init" -> {ok, [], {tuple_t, [], []}}; "init" -> {ok, [], {tuple_t, [], []}};
_ -> {error, missing_function} _ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Pos = aeso_code_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end end
end; end;
get_decode_type(FunName, [_ | Contracts]) -> get_decode_type(FunName, [_ | Contracts]) ->
@@ -528,6 +508,14 @@ icode_to_term(T = {map, KT, VT}, M) ->
#{}; #{};
_ -> throw({todo, M}) _ -> throw({todo, M})
end; end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) -> icode_to_term(typerep, _) ->
throw({todo, typerep}); throw({todo, typerep});
icode_to_term(T, V) -> icode_to_term(T, V) ->
@@ -566,9 +554,11 @@ pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1). pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1). pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1). pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
pp_assembler(C, Opts)-> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1).
pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1). pp_bytecode(C, Opts) -> pp(C, Opts, pp_bytecode, fun aeb_disassemble:pp/1).
pp_assembler(aevm, C, Opts) -> pp(C, Opts, pp_assembler, fun aeb_asm:pp/1);
pp_assembler(fate, C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp(Code, Options, Option, PPFun) -> pp(Code, Options, Option, PPFun) ->
case proplists:lookup(Option, Options) of case proplists:lookup(Option, Options) of
{Option, true} -> {Option, true} ->
@@ -577,16 +567,88 @@ pp(Code, Options, Option, PPFun) ->
ok ok
end. end.
%% ------------------------------------------------------------------- %% -- Byte code validation ---------------------------------------------------
-spec parse_stdlib() -> none() | aeso_syntax:ast(). -define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
parse_stdlib() ->
lists:foldr( -spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
fun ({Lib, LibCode}, Acc) -> validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
parse(LibCode, [{src_file, binary_to_list(Lib)}]) ++ Acc Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
end, case proplists:get_value(backend, Options, aevm) of
[], B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
aeso_stdlib:stdlib_list()). fate ->
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(fate, Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end
end.
compare_fate_code(FCode1, FCode2) ->
Funs1 = aeb_fate_code:functions(FCode1),
Funs2 = aeb_fate_code:functions(FCode2),
Syms1 = aeb_fate_code:symbols(FCode1),
Syms2 = aeb_fate_code:symbols(FCode2),
FunHashes1 = maps:keys(Funs1),
FunHashes2 = maps:keys(Funs2),
case FunHashes1 == FunHashes2 of
false ->
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
{error, Msg};
true ->
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
maps:to_list(Funs2)) ]) of
[] -> ok;
Errs -> {error, Errs}
end
end.
compare_fate_fun(_Name, Fun, Fun) -> [];
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
[io_lib:format("- The attributes of the function ~s differ:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
[io_lib:format("- The type of the function ~s differs:\n"
" Byte code: ~s\n"
" Source code: ~s\n",
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
pp_fate_sig({[Arg], Res}) ->
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
pp_fate_sig({Args, Res}) ->
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
pp_fate_type(T) -> io_lib:format("~w", [T]).
%% -------------------------------------------------------------------
sophia_type_to_typerep(String) -> sophia_type_to_typerep(String) ->
{ok, Ast} = aeso_parser:type(String), {ok, Ast} = aeso_parser:type(String),
@@ -601,37 +663,7 @@ parse(Text, Options) ->
-spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast(). -spec parse(string(), sets:set(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Included, Options) -> parse(Text, Included, Options) ->
%% Try and return something sensible here! aeso_parser:string(Text, Included, Options).
case aeso_parser:string(Text, Included, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
-spec parse_error(aeso_parse_lib:pos(), string()) -> none().
parse_error(Pos, ErrorString) ->
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
file:read_file(Name). file:read_file(Name).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
-42
View File
@@ -1,42 +0,0 @@
-module(aeso_constants).
-export([string/1, get_type/1]).
string(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [{letval, _, _, _, E}]} -> {ok, E};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
get_type(Str) ->
case aeso_parser:string("let _ = " ++ Str) of
{ok, [Ast]} ->
AstT = aeso_ast_infer_types:infer_constant(Ast),
T = ast_to_type(AstT),
{ok, T};
{ok, Other} -> error({internal_error, should_be_letval, Other});
Err -> Err
end.
ast_to_type({id, _, T}) ->
T;
ast_to_type({tuple_t, _, []}) -> "()";
ast_to_type({tuple_t, _, Ts}) ->
"(" ++ list_ast_to_type(Ts) ++ ")";
ast_to_type({app_t,_, {id, _, "list"}, [T]}) ->
lists:flatten("list(" ++ ast_to_type(T) ++ ")");
ast_to_type({app_t,_, {id, _, "option"}, [T]}) ->
lists:flatten("option(" ++ ast_to_type(T) ++ ")").
list_ast_to_type([T]) ->
ast_to_type(T);
list_ast_to_type([T|Ts]) ->
ast_to_type(T)
++ ", "
++ list_ast_to_type(Ts).
+112
View File
@@ -0,0 +1,112 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, Aeternity Anstalt
%%% @doc ADT for structured error messages + formatting.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_errors).
-type src_file() :: no_file | iolist().
-record(pos, { file = no_file :: src_file()
, line = 0 :: non_neg_integer()
, col = 0 :: non_neg_integer()
}).
-type pos() :: #pos{}.
-type error_type() :: type_error | parse_error | code_error
| file_error | data_error | internal_error.
-record(err, { pos = #pos{} :: pos()
, type :: error_type()
, message :: iolist()
, context = none :: none | iolist()
}).
-opaque error() :: #err{}.
-export_type([error/0, pos/0]).
-export([ err_msg/1
, msg/1
, new/2
, new/3
, new/4
, pos/2
, pos/3
, pp/1
, to_json/1
, throw/1
, type/1
]).
new(Type, Msg) ->
new(Type, pos(0, 0), Msg).
new(Type, Pos, Msg) ->
#err{ type = Type, pos = Pos, message = Msg }.
new(Type, Pos, Msg, Ctxt) ->
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
pos(Line, Col) ->
#pos{ line = Line, col = Col }.
pos(File, Line, Col) ->
#pos{ file = File, line = Line, col = Col }.
-spec throw(_) -> ok | no_return().
throw([]) -> ok;
throw(Errs) when is_list(Errs) ->
SortedErrs = lists:sort(fun(E1, E2) -> E1#err.pos =< E2#err.pos end, Errs),
erlang:throw({error, SortedErrs});
throw(#err{} = Err) ->
erlang:throw({error, [Err]}).
msg(#err{ message = Msg, context = none }) -> Msg;
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
err_msg(#err{ pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s", [str_pos(Pos), msg(Err)])).
str_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format("~p:~p:", [L, C]);
str_pos(#pos{file = F, line = L, col = C}) ->
io_lib:format("~s:~p:~p:", [F, L, C]).
type(#err{ type = Type }) -> Type.
pp(#err{ type = Kind, pos = Pos } = Err) ->
lists:flatten(io_lib:format("~s~s:\n~s", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
pp_kind(type_error) -> "Type error";
pp_kind(parse_error) -> "Parse error";
pp_kind(code_error) -> "Code generation error";
pp_kind(file_error) -> "File error";
pp_kind(data_error) -> "Data error";
pp_kind(internal_error) -> "Internal error".
pp_pos(#pos{file = no_file, line = 0, col = 0}) ->
"";
pp_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format(" at line ~p, col ~p", [L, C]);
pp_pos(#pos{file = F, line = L, col = C}) ->
io_lib:format(" in '~s' at line ~p, col ~p", [F, L, C]).
to_json(#err{pos = Pos, type = Type, message = Msg, context = Cxt}) ->
Json = #{ pos => pos_to_json(Pos),
type => atom_to_binary(Type, utf8),
message => iolist_to_binary(Msg) },
case Cxt of
none -> Json;
_ -> Json#{ context => iolist_to_binary(Cxt) }
end.
pos_to_json(#pos{ file = File, line = Line, col = Col }) ->
Json = #{ line => Line, col => Col },
case File of
no_file -> Json;
_ -> Json#{ file => iolist_to_binary(File) }
end.
+416 -276
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -16,6 +16,7 @@
set_payable/2, set_payable/2,
enter_namespace/2, enter_namespace/2,
get_namespace/1, get_namespace/1,
in_main_contract/1,
qualify/2, qualify/2,
set_functions/2, set_functions/2,
map_typerep/2, map_typerep/2,
@@ -74,10 +75,12 @@ builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
#{ "bool" => Word #{ "bool" => Word
, "int" => Word , "int" => Word
, "char" => Word
, "bits" => Word , "bits" => Word
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
, "hash" => Word , "hash" => Word
, "unit" => fun([]) -> {tuple, []} end
, "signature" => fun([]) -> {tuple, [word, word]} end , "signature" => fun([]) -> {tuple, [word, word]} end
, "oracle" => fun([_, _]) -> word end , "oracle" => fun([_, _]) -> word end
, "oracle_query" => fun([_, _]) -> word end , "oracle_query" => fun([_, _]) -> word end
@@ -119,6 +122,10 @@ enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
enter_namespace(NS, Icode) -> enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }. Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon(). -spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false). get_namespace(Icode) -> maps:get(namespace, Icode, false).
+2 -2
View File
@@ -27,7 +27,7 @@ convert(#{ contract_name := _ContractName
}, },
_Options) -> _Options) ->
%% Create a function dispatcher %% Create a function dispatcher
DispatchFun = {"_main", [], [{"arg", "_"}], DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"}, {switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun), [{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]}, {tuple, make_args(Args)}]},
@@ -44,7 +44,7 @@ convert(#{ contract_name := _ContractName
%% taken from the stack %% taken from the stack
StopLabel = make_ref(), StopLabel = make_ref(),
StatefulStopLabel = make_ref(), StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "_main"), MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType), StateTypeValue = aeso_ast_to_icode:type_value(StateType),
+60 -4
View File
@@ -9,12 +9,14 @@
-module(aeso_parse_lib). -module(aeso_parse_lib).
-export([parse/2, -export([parse/2,
return/1, fail/0, fail/1, map/2, bind/2, return/1, fail/0, fail/1, fail/2, map/2, bind/2,
lazy/1, choice/1, choice/2, tok/1, layout/0, lazy/1, choice/1, choice/2, tok/1, layout/0,
left/2, right/2, between/3, optional/1, left/2, right/2, between/3, optional/1,
many/1, many1/1, sep/2, sep1/2, many/1, many1/1, sep/2, sep1/2,
infixl/2, infixr/2]). infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
%% -- Types ------------------------------------------------------------------ %% -- Types ------------------------------------------------------------------
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
@@ -98,6 +100,10 @@ apply_p(X, K) -> K(X).
-spec lazy(fun(() -> parser(A))) -> parser(A). -spec lazy(fun(() -> parser(A))) -> parser(A).
lazy(Delayed) -> ?lazy(Delayed). lazy(Delayed) -> ?lazy(Delayed).
%% @doc A parser that always fails at a known location.
-spec fail(pos(), term()) -> parser(none()).
fail(Pos, Err) -> ?fail({Pos, Err}).
%% @doc A parser that always fails. %% @doc A parser that always fails.
-spec fail(term()) -> parser(none()). -spec fail(term()) -> parser(none()).
fail(Err) -> ?fail(Err). fail(Err) -> ?fail(Err).
@@ -155,7 +161,7 @@ layout() -> ?layout.
-spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}. -spec parse(parser(A), tokens()) -> {ok, A} | {error, term()}.
parse(P, S) -> parse(P, S) ->
case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of case parse1(apply_p(P, fun(X) -> {return_plus, X, {fail, no_error}} end), S) of
{[], {Pos, Err}} -> {error, {Pos, parse_error, flatten_error(Err)}}; {[], {Pos, Err}} -> {error, {add_current_file(Pos), parse_error, flatten_error(Err)}};
{[A], _} -> {ok, A}; {[A], _} -> {ok, A};
{As, _} -> {error, {{1, 1}, ambiguous_parse, As}} {As, _} -> {error, {{1, 1}, ambiguous_parse, As}}
end. end.
@@ -285,7 +291,7 @@ parse1({tok_bind, Map}, Ts, Acc, Err) ->
%% y + y)(4) %% y + y)(4)
case maps:get(vclose, Map, '$not_found') of case maps:get(vclose, Map, '$not_found') of
'$not_found' -> '$not_found' ->
{Acc, unexpected_token_error(Ts, T)}; {Acc, unexpected_token_error(Ts, maps:keys(Map), T)};
F -> F ->
VClose = {vclose, pos(T)}, VClose = {vclose, pos(T)},
Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }), Ts2 = pop_layout(VClose, Ts#ts{ last = VClose }),
@@ -322,12 +328,52 @@ current_pos(#ts{ tokens = [T | _] }) -> pos(T);
current_pos(#ts{ last = T }) -> end_pos(pos(T)). current_pos(#ts{ last = T }) -> end_pos(pos(T)).
-spec mk_error(#ts{}, term()) -> error(). -spec mk_error(#ts{}, term()) -> error().
mk_error(_Ts, {Pos, Err}) ->
{Pos, Err};
mk_error(Ts, Err) -> mk_error(Ts, Err) ->
{current_pos(Ts), Err}. {current_pos(Ts), Err}.
-spec unexpected_token_error(#ts{}, token()) -> error(). -spec unexpected_token_error(#ts{}, token()) -> error().
unexpected_token_error(Ts, T) -> unexpected_token_error(Ts, T) ->
mk_error(Ts, io_lib:format("Unexpected token ~p", [tag(T)])). unexpected_token_error(Ts, [], T).
unexpected_token_error(Ts, Expect, {Tag, _}) when Tag == vclose; Tag == vsemi ->
Braces = [')', ']', '}'],
Fix = case lists:filter(fun(T) -> lists:member(T, Braces) end, Expect) of
[] -> " Probable causes:\n"
" - something is missing in the previous statement, or\n"
" - this line should be indented more.";
[T | _] -> io_lib:format(" Did you forget a ~p?", [T])
end,
Msg = io_lib:format("Unexpected indentation.~s", [Fix]),
mk_error(Ts, Msg);
unexpected_token_error(Ts, Expect, T) ->
ExpectCon = lists:member(con, Expect),
ExpectId = lists:member(id, Expect),
Fix = case T of
{id, _, X} when ExpectCon, hd(X) /= $_ -> io_lib:format(" Did you mean ~s?", [mk_upper(X)]);
{con, _, X} when ExpectId -> io_lib:format(" Did you mean ~s?", [mk_lower(X)]);
{qcon, _, Xs} when ExpectCon -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
{qid, _, Xs} when ExpectId -> io_lib:format(" Did you mean ~s?", [lists:last(Xs)]);
_ -> ""
end,
mk_error(Ts, io_lib:format("Unexpected ~s.~s", [describe(T), Fix])).
mk_upper([C | Rest]) -> string:to_upper([C]) ++ Rest.
mk_lower([C | Rest]) -> string:to_lower([C]) ++ Rest.
describe({id, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({con, _, X}) -> io_lib:format("identifier ~s", [X]);
describe({qid, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({qcon, _, Xs}) -> io_lib:format("qualified identifier ~s", [string:join(Xs, ".")]);
describe({tvar, _, X}) -> io_lib:format("type variable ~s", [X]);
describe({char, _, _}) -> "character literal";
describe({string, _, _}) -> "string literal";
describe({hex, _, _}) -> "integer literal";
describe({int, _, _}) -> "integer literal";
describe({bytes, _, _}) -> "bytes literal";
describe(T) -> io_lib:format("token '~s'", [tag(T)]).
%% Get the next token from a token stream. Inserts layout tokens if necessary. %% Get the next token from a token stream. Inserts layout tokens if necessary.
-spec next_token(#ts{}) -> false | {token(), #ts{}}. -spec next_token(#ts{}) -> false | {token(), #ts{}}.
@@ -411,3 +457,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1)) end, Map2, maps:to_list(Map1))
end. end.
%% Current source file
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
add_current_file({L, C}) -> {current_file(), L, C};
add_current_file(Pos) -> Pos.
+1 -1
View File
@@ -19,7 +19,7 @@
-import(aeso_parse_lib, -import(aeso_parse_lib,
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2, [tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0, infixl/1, infixr/1, choice/1, choice/2, return/1, layout/0,
fail/0, fail/1, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2, fail/0, fail/1, fail/2, map/2, infixl/2, infixr/2, infixl1/2, infixr1/2,
left/2, right/2, optional/1]). left/2, right/2, optional/1]).
+99 -42
View File
@@ -11,10 +11,9 @@
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-type parse_result() :: {ok, aeso_syntax:ast()} -type parse_result() :: aeso_syntax:ast() | none().
| {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), atom()}}.
-type include_hash() :: {string(), binary()}. -type include_hash() :: {string(), binary()}.
@@ -33,21 +32,50 @@ string(String, Opts) ->
string(String, Included, Opts) -> string(String, Included, Opts) ->
case parse_and_scan(file(), String, Opts) of case parse_and_scan(file(), String, Opts) of
{ok, AST} -> {ok, AST} ->
expand_includes(AST, Included, Opts); case expand_includes(AST, Included, Opts) of
Err = {error, _} -> {ok, AST1} -> AST1;
Err {error, Err} -> parse_error(Err)
end;
{error, Err} ->
parse_error(Err)
end. end.
type(String) -> type(String) ->
parse_and_scan(type(), String, []). case parse_and_scan(type(), String, []) of
{ok, AST} -> {ok, AST};
{error, Err} -> {error, [mk_error(Err)]}
end.
parse_and_scan(P, S, Opts) -> parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)), set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error {error, {{Input, Pos}, _}} ->
{error, {Pos, scan_error, Input}}
end. end.
-dialyzer({nowarn_function, parse_error/1}).
parse_error(Err) ->
aeso_errors:throw(mk_error(Err)).
mk_p_err(Pos, Msg) ->
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
mk_error({Pos, scan_error, Input}) ->
mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
mk_error({Pos, parse_error, Err}) ->
Msg = io_lib:format("~s\n", [Err]),
mk_p_err(Pos, Msg);
mk_error({Pos, ambiguous_parse, As}) ->
Msg = io_lib:format("Ambiguous parse result: ~p\n", [As]),
mk_p_err(Pos, Msg);
mk_error({Pos, include_error, File}) ->
Msg = io_lib:format("Couldn't find include file '~s'\n", [File]),
mk_p_err(Pos, Msg).
mk_pos({Line, Col}) -> aeso_errors:pos(Line, Col);
mk_pos({File, Line, Col}) -> aeso_errors:pos(File, Line, Col).
%% -- Parsing rules ---------------------------------------------------------- %% -- Parsing rules ----------------------------------------------------------
file() -> choice([], block(decl())). file() -> choice([], block(decl())).
@@ -60,6 +88,7 @@ decl() ->
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) , ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4}) , ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, pragma()
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -77,6 +106,16 @@ decl() ->
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
])). ])).
pragma() ->
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
version() ->
?RULE(token(int), many({tok('.'), token(int)}), mk_version(_1, _2)).
mk_version({int, _, Maj}, Rest) ->
[Maj | [N || {_, {int, _, N}} <- Rest]].
fun_or_entry() -> fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}), choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]). ?RULE(keyword(entrypoint), {entrypoint, _1})]).
@@ -150,7 +189,7 @@ type() -> ?LAZY_P(type100()).
type100() -> type200(). type100() -> type200().
type200() -> type200() ->
?RULE(many({fun_domain(), keyword('=>')}), type300(), fun_t(_1, _2)). ?RULE(many({type300(), keyword('=>')}), type300(), fun_t(_1, _2)).
type300() -> type300() ->
?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)). ?RULE(sep1(type400(), tok('*')), tuple_t(get_ann(lists:nth(1, _1)), _1)).
@@ -169,16 +208,15 @@ type400() ->
typeAtom() -> typeAtom() ->
?LAZY_P(choice( ?LAZY_P(choice(
[ parens(type()) [ parens(type())
, args_t()
, id(), token(con), token(qcon), token(qid), tvar() , id(), token(con), token(qcon), token(qid), tvar()
])). ])).
fun_domain() -> ?LAZY_P(choice( args_t() ->
[ ?RULE(tok('('), tok(')'), []) ?LAZY_P(choice(
%% Note avoidance of ambiguity: `(int)` can be treated as: [ ?RULE(tok('('), tok(')'), {args_t, get_ann(_1), []})
%% - literally `int` %% Singleton case handled separately
%% - list of arguments with just one element int. This approach is dropped. , ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), {args_t, get_ann(_1), [_2|_4]})
, ?RULE(tok('('), type(), tok(','), sep1(type(), tok(',')), tok(')'), [_2|_4])
, ?RULE(type300(), [_1])
])). ])).
%% -- Statements ------------------------------------------------------------- %% -- Statements -------------------------------------------------------------
@@ -286,7 +324,7 @@ map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
elim(E, []) -> E; elim(E, []) -> E;
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es); elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
elim(E, [{app, Ann, Args} | Es]) -> elim({app, Ann, E, Args}, Es); elim(E, [{app, _Ann, Args} | Es]) -> elim({app, aeso_syntax:get_ann(E), E, Args}, Es);
elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es); elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es);
elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es); elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es);
elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es). elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es).
@@ -380,7 +418,7 @@ token(Tag) ->
id(Id) -> id(Id) ->
?LET_P({id, A, X} = Y, id(), ?LET_P({id, A, X} = Y, id(),
if X == Id -> Y; if X == Id -> Y;
true -> fail({A, "expected 'bytes'"}) true -> fail({A, "expected '" ++ Id ++ "'"})
end). end).
id_or_addr() -> id_or_addr() ->
@@ -426,12 +464,6 @@ bracket_list(P) -> brackets(comma_sep(P)).
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(file, Ann), {proplists:get_value(file, Ann),
proplists:get_value(line, Ann), proplists:get_value(line, Ann),
@@ -501,7 +533,8 @@ tuple_t(_Ann, [Type]) -> Type; %% Not a tuple
tuple_t(Ann, Types) -> {tuple_t, Ann, Types}. tuple_t(Ann, Types) -> {tuple_t, Ann, Types}.
fun_t(Domains, Type) -> 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). Type, Domains).
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
@@ -533,14 +566,9 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}. {field, Ann, F, parse_pattern(E)}.
return_error({no_file, L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()). -spec ret_doc_err(ann(), prettypr:document()) -> aeso_parse_lib:parser(none()).
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
return_error(ann_pos(Ann), prettypr:format(Doc)). fail(ann_pos(Ann), prettypr:format(Doc)).
-spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()). -spec bad_expr_err(string(), aeso_syntax:expr()) -> aeso_parse_lib:parser(none()).
bad_expr_err(Reason, E) -> bad_expr_err(Reason, E) ->
@@ -549,12 +577,16 @@ bad_expr_err(Reason, E) ->
prettypr:nest(2, aeso_pretty:expr(E))])). prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions ------------------------------------------------------- %% -- Helper functions -------------------------------------------------------
expand_includes(AST, Included, Opts) -> expand_includes(AST, Included, Opts) ->
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) -> expand_includes([], _Included, Acc, _Opts) ->
{ok, lists:reverse(Acc)}; {ok, lists:reverse(Acc)};
expand_includes([{include, Ann, {string, SAnn, File}} | AST], Included, Acc, Opts) -> expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) ->
case get_include_code(File, Ann, Opts) of case get_include_code(File, Ann, Opts) of
{ok, Code} -> {ok, Code} ->
Hashed = hash_include(File, Code), Hashed = hash_include(File, Code),
@@ -562,12 +594,9 @@ expand_includes([{include, Ann, {string, SAnn, File}} | AST], Included, Acc, Opt
false -> false ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
Included1 = sets:add_element(Hashed, Included), Included1 = sets:add_element(Hashed, Included),
case string(Code, Included1, Opts1) of case parse_and_scan(file(), Code, Opts1) of
{ok, AST1} -> {ok, AST1} ->
Dependencies = [ {include, Ann, {string, SAnn, Dep}} expand_includes(AST1 ++ AST, Included1, Acc, Opts);
|| Dep <- aeso_stdlib:dependencies(File)
],
expand_includes(Dependencies ++ AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} -> Err = {error, _} ->
Err Err
end; end;
@@ -590,15 +619,35 @@ read_file(File, Opts) ->
case maps:get(binary_to_list(File), Files, not_found) of case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found}; not_found -> {error, not_found};
Src -> {ok, Src} Src -> {ok, Src}
end;
escript ->
try
Escript = escript:script_name(),
{ok, Sections} = escript:extract(Escript, []),
Archive = proplists:get_value(archive, Sections),
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
{ok, [{_, Src}]} -> {ok, Src};
_ -> {error, not_found}
end
catch _:_ ->
{error, not_found}
end end
end. end.
stdlib_options() ->
StdLibDir = aeso_stdlib:stdlib_include_path(),
case filelib:is_dir(StdLibDir) of
true -> [{include, {file_system, [StdLibDir]}}];
false -> [{include, escript}]
end.
get_include_code(File, Ann, Opts) -> get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), maps:find(File, aeso_stdlib:stdlib())} of case {read_file(File, Opts), read_file(File, stdlib_options())} of
{{ok, _}, {ok,_ }} -> {{ok, _}, {ok,_ }} ->
return_error(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File);
{_, {ok, Lib}} -> {_, {ok, Bin}} ->
{ok, Lib}; {ok, binary_to_list(Bin)};
{{ok, Bin}, _} -> {{ok, Bin}, _} ->
{ok, binary_to_list(Bin)}; {ok, binary_to_list(Bin)};
{_, _} -> {_, _} ->
@@ -610,3 +659,11 @@ hash_include(File, Code) when is_binary(File) ->
hash_include(binary_to_list(File), Code); hash_include(binary_to_list(File), Code);
hash_include(File, Code) when is_list(File) -> hash_include(File, Code) when is_list(File) ->
{filename:basename(File), crypto:hash(sha256, Code)}. {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(_) -> [].
+7
View File
@@ -149,6 +149,7 @@ decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) -> decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({pragma, _, Pragma}) -> pragma(Pragma);
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
@@ -170,6 +171,10 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]); hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", D). decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
-spec pragma(aeso_syntax:pragma()) -> doc().
pragma({compiler, Op, Ver}) ->
text("@compiler " ++ atom_to_list(Op) ++ " " ++ string:join([integer_to_list(N) || N <- Ver], ".")).
-spec expr(aeso_syntax:expr(), options()) -> doc(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
with_options(Options, fun() -> expr(E) end). with_options(Options, fun() -> expr(E) end).
@@ -243,6 +248,8 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), args_type(Args)); beside(type(Type), args_type(Args));
type({tuple_t, _, Args}) -> type({tuple_t, _, Args}) ->
tuple_type(Args); tuple_type(Args);
type({args_t, _, Args}) ->
args_type(Args);
type({bytes_t, _, any}) -> text("bytes(_)"); type({bytes_t, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) -> type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"])); text(lists:concat(["bytes(", Len, ")"]));
+20 -9
View File
@@ -13,14 +13,15 @@
override/2, push/2, pop/1]). override/2, push/2, pop/1]).
lexer() -> lexer() ->
Number = fun(Digit) -> [Digit, "+(_", Digit, "+)*"] end,
DIGIT = "[0-9]", DIGIT = "[0-9]",
HEXDIGIT = "[0-9a-fA-F]", HEXDIGIT = "[0-9a-fA-F]",
LOWER = "[a-z_]", LOWER = "[a-z_]",
UPPER = "[A-Z]", UPPER = "[A-Z]",
CON = [UPPER, "[a-zA-Z0-9_]*"], CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"], INT = Number(DIGIT),
HEX = ["0x", HEXDIGIT, "+"], HEX = ["0x", Number(HEXDIGIT)],
BYTES = ["#", HEXDIGIT, "+"], BYTES = ["#", Number(HEXDIGIT)],
WS = "[\\000-\\ ]+", WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"], ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID], TVAR = ["'", ID],
@@ -53,7 +54,7 @@ lexer() ->
, {CHAR, token(char, fun parse_char/1)} , {CHAR, token(char, fun parse_char/1)}
, {STRING, token(string, fun parse_string/1)} , {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)} , {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)} , {INT, token(int, fun parse_int/1)}
, {BYTES, token(bytes, fun parse_bytes/1)} , {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!) %% Identifiers (qualified first!)
@@ -95,9 +96,11 @@ parse_char([$', C, $']) -> C.
unescape(Str) -> unescape(Str, []). unescape(Str) -> unescape(Str, []).
%% TODO: numeric escapes
unescape([$"], Acc) -> unescape([$"], Acc) ->
list_to_binary(lists:reverse(Acc)); list_to_binary(lists:reverse(Acc));
unescape([$\\, $x, D1, D2 | Chars ], Acc) ->
C = list_to_integer([D1, D2], 16),
unescape(Chars, [C | Acc]);
unescape([$\\, Code | Chars], Acc) -> unescape([$\\, Code | Chars], Acc) ->
Ok = fun(C) -> unescape(Chars, [C | Acc]) end, Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
case Code of case Code of
@@ -115,10 +118,18 @@ unescape([$\\, Code | Chars], Acc) ->
unescape([C | Chars], Acc) -> unescape([C | Chars], Acc) ->
unescape(Chars, [C | Acc]). unescape(Chars, [C | Acc]).
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). strip_underscores(S) ->
lists:filter(fun(C) -> C /= $_ end, S).
parse_bytes("#" ++ Chars) -> parse_hex("0x" ++ S) ->
N = list_to_integer(Chars, 16), list_to_integer(strip_underscores(S), 16).
Digits = (length(Chars) + 1) div 2,
parse_int(S) ->
list_to_integer(strip_underscores(S)).
parse_bytes("#" ++ S0) ->
S = strip_underscores(S0),
N = list_to_integer(S, 16),
Digits = (length(S) + 1) div 2,
<<N:Digits/unit:8>>. <<N:Digits/unit:8>>.
+3 -420
View File
@@ -10,425 +10,8 @@
-module(aeso_stdlib). -module(aeso_stdlib).
-export([stdlib/0, stdlib_list/0, dependencies/1]). -export([stdlib_include_path/0]).
stdlib() -> stdlib_include_path() ->
maps:from_list(stdlib_list()). filename:join([code:priv_dir(aesophia), "stdlib"]).
stdlib_list() ->
[ {<<"List.aes">>, std_list()}
, {<<"Func.aes">>, std_func()}
, {<<"Option.aes">>, std_option()}
, {<<"Pair.aes">>, std_pair()}
, {<<"Triple.aes">>, std_triple()}
].
dependencies(Q) ->
case Q of
<<"Option.aes">> ->
[<<"List.aes">>];
_ -> []
end.
std_func() ->
"
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)
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))
function curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) =
(x) => (y) => f(x, y)
function curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) =
(x) => (y) => (z) => f(x, y, z)
function uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c =
(x, y) => f(x)(y)
function uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd =
(x, y, z) => f(x)(y)(z)
function tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c =
(t) => switch(t)
(x, y) => f(x, y)
function tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd =
(t) => switch(t)
(x, y, z) => f(x, y, z)
function untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c =
(x, y) => f((x, y))
function untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd =
(x, y, z) => f((x, y, z))
".
std_list() ->"
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 find(p : 'a => bool, l : list('a)) : option('a) = switch(l)
[] => None
h::t => if(p(h)) Some(h) else find(p, t)
function find_all(p : 'a => bool, l : list('a)) : list('a) = find_all_(p, l, [])
private function find_all_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => find_all_(p, t, if(p(h)) h::acc else acc)
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(\"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)
/* 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)
function insert_by(f : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
switch(l)
[] => [x]
(e :: l') =>
if(f(x, e))
e :: insert_by(f, x, l')
else
x :: l
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(f : 'a => unit, l : list('a)) : unit =
switch(l)
[] => ()
e :: l' =>
f(e)
foreach(f, l')
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)
function flat_map(f : 'a => list('b), l : list('a)) : list('b) = flat_map_(f, l, [])
private function flat_map_(f : 'a => list('b), l : list('a), acc : list('b)) : list('b) = switch(l)
[] => reverse(acc)
h::t => flat_map_(f, t, reverse(f(h)) ++ acc)
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)
function concats(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)
// TODO: Improve?
function sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => switch (partition((x) => lesser_cmp(x, h), t))
(lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger)
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)
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)
".
std_option() -> "
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
function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o)
None => n
Some(x) => s(x)
function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o)
function force(o : option('a)) : 'a = default(abort(\"Forced None value\"), o)
function on_elem(f : 'a => unit, o : option('a)) : 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
function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o))
(Some(ff), Some(xx)) => Some(ff(xx))
_ => None
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]
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)
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)
function choose(o1 : option('a), o2 : option('a)) : option('a) =
if(is_some(o1)) o1 else o2
function choose_first(l : list(option('a))) : option('a) = switch(l)
[] => None
None::t => choose_first(t)
Some(x)::_ => Some(x)
".
std_pair() -> "
namespace Pair =
function fst(t : ('a * 'b)) : 'a = switch(t)
(x, _) => x
function snd(t : ('a * 'b)) : 'b = switch(t)
(_, y) => y
function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t)
(x, y) => (f(x), y)
function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t)
(x, y) => (x, f(y))
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)
".
std_triple() -> "
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
function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t)
(x, y, z) => (f(x), y, z)
function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t)
(x, y, z) => (x, f(y), z)
function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t)
(x, y, z) => (x, y, f(z))
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)
function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t)
(x, y, z) => (z, x, y)
function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t)
(x, y, z) => (y, z, x)
".
+10 -4
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]). -export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]). -export_type([decl/0, letbind/0, typedef/0, pragma/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]). -export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]). -export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]). -export_type([ast/0]).
@@ -36,11 +36,16 @@
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
| letbind(). | letbind().
-type compiler_version() :: [non_neg_integer()].
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letbind() -type letbind()
:: {letval, ann(), id(), type(), expr()} :: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}. | {letfun, ann(), id(), [arg()], type(), expr()}.
@@ -59,6 +64,7 @@
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()} -type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
| {app_t, ann(), type(), [type()]} | {app_t, ann(), type(), [type()]}
| {tuple_t, ann(), [type()]} | {tuple_t, ann(), [type()]}
| {args_t, ann(), [type()]} %% old tuple syntax, old for error messages
| {bytes_t, ann(), integer() | any} | {bytes_t, ann(), integer() | any}
| id() | qid() | id() | qid()
| con() | qcon() %% contracts | con() | qcon() %% contracts
@@ -105,9 +111,9 @@
| id() | qid() | con() | qcon() | id() | qid() | con() | qcon()
| constant(). | constant().
-type comprehension_exp() :: [{ comprehension_bind, ann(), id(), expr()} -type comprehension_exp() :: [ {comprehension_bind, id(), expr()}
| {comprehension_if, expr()} | {comprehension_if, ann(), expr()}
| letbind()]. | letbind() ].
-type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}. -type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}.
+7 -7
View File
@@ -49,7 +49,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T); {fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]); {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))]); {letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
%% typedef() %% typedef()
{alias_t, T} -> Type(T); {alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs); {record_t, Fs} -> Type(Fs);
@@ -74,7 +74,7 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{list_comp, _, Y, []} -> Expr(Y); {list_comp, _, Y, []} -> Expr(Y);
{list_comp, A, Y, [{comprehension_bind, I, E}|R]} -> {list_comp, A, Y, [{comprehension_bind, I, E}|R]} ->
Plus(Expr(E), Scoped(BindExpr(I), Expr({list_comp, A, Y, R}))); Plus(Expr(E), Scoped(BindExpr(I), Expr({list_comp, A, Y, R})));
{list_comp, A, Y, [{comprehension_if, E}|R]} -> {list_comp, A, Y, [{comprehension_if, _, E}|R]} ->
Plus(Expr(E), Expr({list_comp, A, Y, R})); Plus(Expr(E), Expr({list_comp, A, Y, R}));
{list_comp, A, Y, [D = {letval, _, F, _, _} | R]} -> {list_comp, A, Y, [D = {letval, _, F, _, _} | R]} ->
Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R}))); Plus(Decl(D), Scoped(BindExpr(F), Expr({list_comp, A, Y, R})));
@@ -92,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]);
{field, _, LV, _, E} -> Expr([LV, E]); {field, _, LV, _, E} -> Expr([LV, E]);
%% arg() %% arg()
{arg, _, X, T} -> Plus(Expr(X), Type(T)); {arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
%% alt() %% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E)); {'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim() %% elim()
@@ -124,12 +124,12 @@ used_types([Top] = _CurrentNS, T) ->
entity_alg() -> entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end, IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end, Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end, Remove = fun(Keys, Map) -> maps:without(Keys, Map) end,
Scoped = fun(Xs, Ys) -> Scoped = fun(Xs, Ys) ->
Bound = [E || E <- maps:keys(Ys), IsBound(E)], Bound = [E || E <- maps:keys(Xs), IsBound(E)],
Others = Remove(Bound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ], Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
maps:merge(Remove(Bound1, Xs), Others) Others = Remove(Bound1, Ys),
maps:merge(Remove(Bound, Xs), Others)
end, end,
#alg{ zero = #{} #alg{ zero = #{}
, plus = fun maps:merge/2 , plus = fun maps:merge/2
+27 -5
View File
@@ -18,9 +18,14 @@ from_aevm(word, {id, _, "address"}, N) -> address_literal(ac
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1}; from_aevm(word, {id, _, "int"}, N0) ->
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N}); <<N:256/signed>> = <<N0:256>>,
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 -> from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>, <<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>}; {bytes, [], <<Bytes:Len/unit:8>>};
@@ -55,6 +60,7 @@ from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
VmTypes = lists:nth(Tag + 1, VmCons), VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons), ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args); from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args) from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types), when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) -> length(VmTypes) == length(Args) ->
@@ -70,8 +76,10 @@ from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin}); from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N}; from_fate({id, _, "int"}, N) when is_integer(N) ->
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B}; from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S}; from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) -> from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
@@ -105,9 +113,23 @@ from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
from_fate(ConType, ArgList); from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia) _ -> throw(cannot_translate_to_sophia)
end; end;
from_fate({constr_t, _, Con, []}, []) -> Con;
from_fate({constr_t, _, Con, Types}, Args) from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) -> when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg) {app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]}; || {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) -> from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia). throw(cannot_translate_to_sophia).
make_bits(N) ->
Id = fun(F) -> {qid, [], ["Bits", F]} end,
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
true -> make_bits(Id("set"), Id("none"), 0, N) end.
make_bits(_Set, Zero, _I, 0) -> Zero;
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
make_bits(Set, Zero, I + 1, N div 2);
make_bits(Set, Zero, I, N) ->
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
+1 -1
View File
@@ -1,6 +1,6 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for aeternity"}, [{description, "Contract Language for aeternity"},
{vsn, "4.0.0-rc1"}, {vsn, "4.1.0"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+63 -9
View File
@@ -62,7 +62,7 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"), ok = Check("int", "- 42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
@@ -72,6 +72,58 @@ encode_decode_sophia_test() ->
ok = Check("r", "{x = (\"foo\", 0), y = Red}"), ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok. ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
ExpErr1 = "Type error at line 5, col 34:\nCannot unify int\n and bool\n"
"when checking the application at line 5, column 34 of\n"
" x : (int) => string\nto arguments\n true : bool\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
ok.
decode_calldata_neg_test() ->
Code1 = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
ok.
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract MakeCall =\n" Code = [ "contract MakeCall =\n"
@@ -80,11 +132,11 @@ encode_decode_sophia_string(SophiaType, String) ->
, " record r = {x : an_alias(int), y : variant}\n" , " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n" , " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ], , " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_implicit_stdlib]) of case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
{ok, _, {[Type], _}, [Arg]} -> {ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]), io:format("Type ~p~n", [Type]),
Data = encode(Arg), Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_implicit_stdlib]) of case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
{ok, Sophia} -> {ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))])); lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} -> {error, Err} ->
@@ -152,7 +204,7 @@ oracle_test() ->
" Oracle.get_question(o, q)\n", " Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_implicit_stdlib]), "oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
ok. ok.
@@ -161,8 +213,10 @@ permissive_literals_fail_test() ->
"contract OracleTest =\n" "contract OracleTest =\n"
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n" " stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n", " Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} = {error, [Err]} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], [no_implicit_stdlib]), aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)),
?assertEqual(type_error, aeso_errors:type(Err)),
ok. ok.
encode_decode_calldata(FunName, Types, Args) -> encode_decode_calldata(FunName, Types, Args) ->
@@ -173,8 +227,8 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetType). encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) -> encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, [no_implicit_stdlib]), {ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_implicit_stdlib]), {ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
?assertEqual(RetType, RetVMType), ?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]}, CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata), {ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
@@ -182,7 +236,7 @@ encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
"init" -> "init" ->
ok; ok;
_ -> _ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, [no_implicit_stdlib]), {ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ], Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values}) ?assertMatch({X, X}, {Args, Values})
end, end,
+4 -5
View File
@@ -9,7 +9,7 @@ simple_aci_test_() ->
test_contract(N) -> test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N), {Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract, [no_implicit_stdlib]), {ok,JSON} = aeso_aci:contract_interface(json, Contract),
?assertEqual([MapACI], JSON), ?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)). ?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
@@ -90,8 +90,7 @@ aci_test_() ->
fun() -> aci_test_contract(ContractName) end} fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()]. || ContractName <- all_contracts()].
all_contracts() -> [C || C <- aeso_compiler_tests:compilable_contracts() all_contracts() -> aeso_compiler_tests:compilable_contracts().
, not aeso_compiler_tests:wants_stdlib(C)].
aci_test_contract(Name) -> aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
@@ -108,11 +107,11 @@ aci_test_contract(Name) ->
check_stub(Stub, Options) -> check_stub(Stub, Options) ->
case aeso_parser:string(binary_to_list(Stub), Options) of case aeso_parser:string(binary_to_list(Stub), Options) of
{ok, Ast} -> Ast ->
try try
%% io:format("AST: ~120p\n", [Ast]), %% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, []) aeso_ast_infer_types:infer(Ast, [])
catch _:{type_errors, TE} -> catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]), io:format("Type error:\n~s\n", [TE]),
error(TE); error(TE);
_:R -> _:R ->
+32 -20
View File
@@ -21,21 +21,18 @@ calldata_test_() ->
ContractString = aeso_test_utils:read_contract(ContractName), ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs = AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}] true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined false -> undefined
end, end,
FateExprs = FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}] true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() -> calldata_aci_test_() ->
@@ -47,29 +44,42 @@ calldata_aci_test_() ->
io:format("ACI:\n~s\n", [ContractACIBin]), io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs = AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}] true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined false -> undefined
end, end,
FateExprs = FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}] true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST).
strip_ann(T) when is_tuple(T) ->
strip_ann1(setelement(2, T, []));
strip_ann(X) -> strip_ann1(X).
strip_ann1({map, [], KVs}) ->
{map, [], [{strip_ann(K), strip_ann(V)} || {K, V} <- KVs]};
strip_ann1(T) when is_tuple(T) ->
list_to_tuple(strip_ann1(tuple_to_list(T)));
strip_ann1(L) when is_list(L) ->
lists:map(fun strip_ann/1, L);
strip_ann1(X) -> X.
ast_exprs(ContractString, Fun, Args, Opts) -> ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)), {ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)), {ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)), ?assert(is_list(Exprs)),
Exprs. strip_ann(Exprs).
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -89,7 +99,9 @@ compilable_contracts() ->
{"maps", "init", []}, {"maps", "init", []},
{"funargs", "menot", ["false"]}, {"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]}, {"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]}, {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test
{"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
+541 -174
View File
@@ -12,62 +12,92 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
run_test(Test) ->
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
[ begin
io:format("~s\n", [Label]),
Fun()
end || {Label, Fun} <- ?MODULE:TestFun() ],
ok.
%% Very simply test compile the given contracts. Only basic checks %% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend", [ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
fun() -> fun() ->
case compile(Backend, ContractName) of case compile(Backend, ContractName) of
#{byte_code := ByteCode, #{byte_code := ByteCode,
contract_source := _, contract_source := _,
type_info := _} when Backend == aevm -> type_info := _} when Backend == aevm ->
?assertMatch(Code when is_binary(Code), ByteCode); ?assertMatch(Code when is_binary(Code), ByteCode);
#{fate_code := Code} when Backend == fate -> #{fate_code := Code} when Backend == fate ->
Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)), Code1 = aeb_fate_code:deserialize(aeb_fate_code:serialize(Code)),
?assertMatch({X, X}, {Code1, Code}); ?assertMatch({X, X}, {Code1, Code});
ErrBin -> ErrBin ->
io:format("\n~s", [ErrBin]), io:format("\n~s", [ErrBin]),
error(ErrBin) error(ErrBin)
end end
end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate], end} || ContractName <- compilable_contracts(), Backend <- [aevm, fate],
not lists:member(ContractName, not_yet_compilable(Backend))] ++ not lists:member(ContractName, not_yet_compilable(Backend))] ++
[ {"Testing error messages of " ++ ContractName, [ {"Test file not found error",
fun() -> fun() ->
case compile(aevm, ContractName) of {error, Errors} = aeso_compiler:file("does_not_exist.aes"),
<<"Type errors\n", ErrorString/binary>> -> ExpErr = <<"File error:\ndoes_not_exist.aes: no such file or directory">>,
check_errors(lists:sort(ExpectedErrors), ErrorString); check_errors([ExpErr], Errors)
<<"Parse errors\n", ErrorString/binary>> -> end} ] ++
check_errors(lists:sort(ExpectedErrors), ErrorString) [ {"Testing error messages of " ++ ContractName,
end fun() ->
end} || Errors = compile(aevm, ContractName),
{ContractName, ExpectedErrors} <- failing_contracts() ] ++ check_errors(ExpectedErrors, Errors)
[ {"Testing include with explicit files", end} ||
fun() -> {ContractName, ExpectedErrors} <- failing_contracts() ] ++
FileSystem = maps:from_list( [ {"Testing " ++ atom_to_list(Backend) ++ " code generation error messages of " ++ ContractName,
[ begin fun() ->
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])), Errors = compile(Backend, ContractName),
{File, Bin} Expect =
end || File <- ["included.aes", "../contracts/included2.aes"] ]), case is_binary(ExpectedError) of
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]), true -> [ExpectedError];
#{byte_code := Code2} = compile(aevm, "include"), false ->
?assertMatch(true, Code1 == Code2) case proplists:get_value(Backend, ExpectedError, no_error) of
end} ] ++ no_error -> no_error;
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend), Err -> [Err]
fun() -> end
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"), end,
#{ byte_code := DeadCode } = compile(Backend, "deadcode"), check_errors(Expect, Errors)
SizeNoDeadCode = byte_size(NoDeadCode), end} ||
SizeDeadCode = byte_size(DeadCode), {ContractName, ExpectedError} <- failing_code_gen_contracts(),
Delta = if Backend == aevm -> 40; Backend <- [aevm, fate] ] ++
Backend == fate -> 20 end, [ {"Testing include with explicit files",
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}), fun() ->
ok FileSystem = maps:from_list(
end} || Backend <- [aevm, fate] ]. [ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile(aevm, "include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile(aevm, "include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination for " ++ atom_to_list(Backend),
fun() ->
#{ byte_code := NoDeadCode } = compile(Backend, "nodeadcode"),
#{ byte_code := DeadCode } = compile(Backend, "deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
Delta = if Backend == aevm -> 40;
Backend == fate -> 20 end,
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + Delta < SizeNoDeadCode}),
ok
end} || Backend <- [aevm, fate] ] ++
[].
check_errors(Expect, ErrorString) -> check_errors(no_error, Actual) -> ?assertMatch(#{}, Actual);
%% This removes the final single \n as well. check_errors(Expect, #{}) ->
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]), ?assertEqual({error, Expect}, ok);
check_errors(Expect0, Actual0) ->
Expect = lists:sort(Expect0),
Actual = [ list_to_binary(string:trim(aeso_errors:pp(Err))) || Err <- Actual0 ],
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@@ -76,14 +106,14 @@ check_errors(Expect, ErrorString) ->
compile(Backend, Name) -> compile(Backend, Name) ->
compile(Backend, Name, compile(Backend, Name,
[{include, {file_system, [aeso_test_utils:contract_path()]}}] [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
++ [no_implicit_stdlib || not wants_stdlib(Name)]).
compile(Backend, Name, Options) -> compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name), String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name}, {backend, Backend} | Options]) of case aeso_compiler:from_string(String, [{src_file, Name ++ ".aes"}, {backend, Backend} | Options]) of
{ok, Map} -> Map; {ok, Map} -> Map;
{error, ErrorString} -> ErrorString {error, ErrorString} when is_binary(ErrorString) -> ErrorString;
{error, Errors} -> Errors
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@@ -121,6 +151,7 @@ compilable_contracts() ->
"address_chain", "address_chain",
"namespace_bug", "namespace_bug",
"bytes_to_x", "bytes_to_x",
"bytes_concat",
"aens", "aens",
"tuple_match", "tuple_match",
"cyclic_include", "cyclic_include",
@@ -128,7 +159,9 @@ compilable_contracts() ->
"double_include", "double_include",
"manual_stdlib_include", "manual_stdlib_include",
"list_comp", "list_comp",
"payable" "payable",
"unapplied_builtins",
"underscore_number_literals"
]. ].
not_yet_compilable(fate) -> []; not_yet_compilable(fate) -> [];
@@ -136,251 +169,585 @@ not_yet_compilable(aevm) -> [].
%% Contracts that should produce type errors %% Contracts that should produce type errors
-define(Pos(Kind, File, Line, Col), (list_to_binary(Kind))/binary, " error in '",
(list_to_binary(File))/binary, ".aes' at line " ??Line ", col " ??Col ":\n").
-define(Pos(Line, Col), ?Pos(__Kind, __File, Line, Col)).
-define(ERROR(Kind, Name, Errs),
(fun() ->
__Kind = Kind,
__File = ??Name,
{__File, Errs}
end)()).
-define(TYPE_ERROR(Name, Errs), ?ERROR("Type", Name, Errs)).
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
failing_contracts() -> failing_contracts() ->
[ {"name_clash", {ok, V} = aeso_compiler:numeric_version(),
[<<"Duplicate definitions of abort at\n" Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
%% Parse errors
[ ?PARSE_ERROR(field_parse_error,
[<<?Pos(5, 26)
"Cannot use nested fields or keys in record construction: p.x">>])
, ?PARSE_ERROR(vsemi, [<<?Pos(3, 3) "Unexpected indentation. Did you forget a '}'?">>])
, ?PARSE_ERROR(vclose, [<<?Pos(4, 3) "Unexpected indentation. Did you forget a ']'?">>])
, ?PARSE_ERROR(indent_fail, [<<?Pos(3, 2) "Unexpected token 'entrypoint'.">>])
%% Type errors
, ?TYPE_ERROR(name_clash,
[<<?Pos(14, 3)
"Duplicate definitions of abort at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 14, column 3">>, " - line 14, column 3">>,
<<"Duplicate definitions of require at\n" <<?Pos(15, 3)
"Duplicate definitions of require at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 15, column 3">>, " - line 15, column 3">>,
<<"Duplicate definitions of double_def at\n" <<?Pos(11, 3)
"Duplicate definitions of double_def at\n"
" - line 10, column 3\n" " - line 10, column 3\n"
" - line 11, column 3">>, " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n" <<?Pos(5, 3)
"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n" " - line 4, column 3\n"
" - line 5, column 3">>, " - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n" <<?Pos(8, 3)
"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n" " - line 7, column 3\n"
" - line 8, column 3">>, " - line 8, column 3">>,
<<"Duplicate definitions of put at\n" <<?Pos(16, 3)
"Duplicate definitions of put at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 16, column 3">>, " - line 16, column 3">>,
<<"Duplicate definitions of state at\n" <<?Pos(17, 3)
"Duplicate definitions of state at\n"
" - (builtin location)\n" " - (builtin location)\n"
" - line 17, column 3">>]} " - line 17, column 3">>])
, {"type_errors", , ?TYPE_ERROR(type_errors,
[<<"Unbound variable zz at line 17, column 23">>, [<<?Pos(17, 23)
<<"Cannot unify int\n" "Unbound variable zz at line 17, column 23">>,
<<?Pos(26, 9)
"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int">>, " x : int">>,
<<"Cannot unify string\n" <<?Pos(9, 48)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 48)\n" " x : map(string, string) (at line 9, column 48)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
<<"Cannot unify int\n" <<?Pos(34, 47)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 47\n" "when checking the type of the expression at line 34, column 47\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string">>, " string">>,
<<"Cannot unify string\n" <<?Pos(34, 52)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 52\n" "when checking the type of the expression at line 34, column 52\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(32, 18)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify string\n" <<?Pos(11, 58)
"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 58\n" "when checking the type of the expression at line 11, column 58\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int">>, " int">>,
<<"Cannot unify int\n" <<?Pos(38, 13)
"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)">>, " - z : string (at line 39, column 10)">>,
<<"Not a record type: string\n" <<?Pos(22, 40)
"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 40)">>, "arising from the projection of the field y (at line 22, column 40)">>,
<<"Not a record type: string\n" <<?Pos(21, 44)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 44)">>, "arising from an assignment of the field y (at line 21, column 44)">>,
<<"Not a record type: string\n" <<?Pos(20, 40)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 40)">>, "arising from an assignment of the field y (at line 20, column 40)">>,
<<"Not a record type: string\n" <<?Pos(19, 37)
"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 37)">>, "arising from an assignment of the field y (at line 19, column 37)">>,
<<"Ambiguous record type with field y (at line 13, column 27) could be one of\n" <<?Pos(13, 27)
"Ambiguous record type with field y (at line 13, column 27) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)">>, " - r' (at line 5, column 10)">>,
<<"Repeated name x in pattern\n" <<?Pos(26, 7)
"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)">>, " x :: x (at line 26, column 7)">>,
<<"Repeated argument x to function repeated_arg (at line 44, column 14).">>, <<?Pos(44, 14)
<<"Repeated argument y to function repeated_arg (at line 44, column 14).">>, "Repeated argument x to function repeated_arg (at line 44, column 14).">>,
<<"No record type with fields y, z (at line 14, column 24)">>, <<?Pos(44, 14)
<<"The field z is missing when constructing an element of type r2 (at line 15, column 26)">>, "Repeated argument y to function repeated_arg (at line 44, column 14).">>,
<<"Record type r2 does not have field y (at line 15, column 24)">>, <<?Pos(14, 24)
<<"Let binding at line 47, column 5 must be followed by an expression">>, "No record type with fields y, z (at line 14, column 24)">>,
<<"Let binding at line 50, column 5 must be followed by an expression">>, <<?Pos(15, 26)
<<"Let binding at line 54, column 5 must be followed by an expression">>, "The field z is missing when constructing an element of type r2 (at line 15, column 26)">>,
<<"Let binding at line 58, column 5 must be followed by an expression">>]} <<?Pos(15, 24)
, {"init_type_error", "Record type r2 does not have field y (at line 15, column 24)">>,
[<<"Cannot unify string\n" <<?Pos(47, 5)
"Let binding at line 47, column 5 must be followed by an expression">>,
<<?Pos(50, 5)
"Let binding at line 50, column 5 must be followed by an expression">>,
<<?Pos(54, 5)
"Let binding at line 54, column 5 must be followed by an expression">>,
<<?Pos(58, 5)
"Let binding at line 58, column 5 must be followed by an expression">>,
<<?Pos(63, 5)
"Cannot unify int\n"
" and bool\n"
"when checking the type of the expression at line 63, column 5\n"
" id(n) : int\n"
"against the expected type\n"
" bool">>])
, ?TYPE_ERROR(init_type_error,
[<<?Pos(7, 3)
"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>])
, {"missing_state_type", , ?TYPE_ERROR(missing_state_type,
[<<"Cannot unify string\n" [<<?Pos(5, 3)
"Cannot unify string\n"
" and unit\n" " and unit\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>])
, {"missing_fields_in_record_expression", , ?TYPE_ERROR(missing_fields_in_record_expression,
[<<"The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>, [<<?Pos(7, 42)
<<"The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>, "The field x is missing when constructing an element of type r('a) (at line 7, column 42)">>,
<<"The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>]} <<?Pos(8, 42)
, {"namespace_clash", "The field y is missing when constructing an element of type r(int) (at line 8, column 42)">>,
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]} <<?Pos(6, 42)
, {"bad_events", "The fields y, z are missing when constructing an element of type r('a) (at line 6, column 42)">>])
[<<"The indexed type string (at line 9, column 25) is not a word type">>, , ?TYPE_ERROR(namespace_clash,
<<"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>]} [<<?Pos(4, 10)
, {"bad_events2", "The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>])
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>, , ?TYPE_ERROR(bad_events,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>]} [<<?Pos(9, 25)
, {"type_clash", "The indexed type string (at line 9, column 25) is not a word type">>,
[<<"Cannot unify int\n" <<?Pos(10, 25)
"The indexed type alias_string (at line 10, column 25) equals string which is not a word type">>])
, ?TYPE_ERROR(bad_events2,
[<<?Pos(9, 7)
"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<?Pos(10, 7)
"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>])
, ?TYPE_ERROR(type_clash,
[<<?Pos(12, 42)
"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the record projection at line 12, column 42\n" "when checking the record projection at line 12, column 42\n"
" r.foo : (gas : int, value : int) => Remote.themap\n" " r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n" "against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]} " (gas : int, value : int) => map(string, int)">>])
, {"bad_include_and_ns", , ?TYPE_ERROR(bad_include_and_ns,
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>, [<<?Pos(2, 11)
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]} "Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
, {"bad_address_literals", <<?Pos(3, 13)
[<<"The type bytes(32) is not a contract type\n" "Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>])
"when checking that the contract literal at line 32, column 5\n" , ?TYPE_ERROR(bad_address_literals,
[<<?Pos(32, 5)
"The type bytes(32) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" bytes(32)">>, " bytes(32)">>,
<<"The type oracle(int, bool) is not a contract type\n" <<?Pos(30, 5)
"when checking that the contract literal at line 30, column 5\n" "The type oracle(int, bool) is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"The type address is not a contract type\n" <<?Pos(28, 5)
"when checking that the contract literal at line 28, column 5\n" "The type address is not a contract type\n"
"when checking that the contract literal\n"
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
<<"Cannot unify oracle_query('a, 'b)\n" <<?Pos(25, 5)
"Cannot unify oracle_query('a, 'b)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 25, column 5\n" "when checking the type of the expression at line 25, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('a, 'b)\n" " oracle_query('a, 'b)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle_query('c, 'd)\n" <<?Pos(23, 5)
"Cannot unify oracle_query('c, 'd)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 23, column 5\n" "when checking the type of the expression at line 23, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('c, 'd)\n" " oracle_query('c, 'd)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle_query('e, 'f)\n" <<?Pos(21, 5)
"Cannot unify oracle_query('e, 'f)\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 21, column 5\n" "when checking the type of the expression at line 21, column 5\n"
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
" oracle_query('e, 'f)\n" " oracle_query('e, 'f)\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify oracle('g, 'h)\n" <<?Pos(18, 5)
"Cannot unify oracle('g, 'h)\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 18, column 5\n" "when checking the type of the expression at line 18, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('g, 'h)\n" " oracle('g, 'h)\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify oracle('i, 'j)\n" <<?Pos(16, 5)
"Cannot unify oracle('i, 'j)\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 16, column 5\n" "when checking the type of the expression at line 16, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('i, 'j)\n" " oracle('i, 'j)\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>, " bytes(32)">>,
<<"Cannot unify oracle('k, 'l)\n" <<?Pos(14, 5)
"Cannot unify oracle('k, 'l)\n"
" and oracle_query(int, bool)\n" " and oracle_query(int, bool)\n"
"when checking the type of the expression at line 14, column 5\n" "when checking the type of the expression at line 14, column 5\n"
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
" oracle('k, 'l)\n" " oracle('k, 'l)\n"
"against the expected type\n" "against the expected type\n"
" oracle_query(int, bool)">>, " oracle_query(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(11, 5)
"Cannot unify address\n"
" and oracle(int, bool)\n" " and oracle(int, bool)\n"
"when checking the type of the expression at line 11, column 5\n" "when checking the type of the expression at line 11, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<"Cannot unify address\n" <<?Pos(9, 5)
"Cannot unify address\n"
" and Remote\n" " and Remote\n"
"when checking the type of the expression at line 9, column 5\n" "when checking the type of the expression at line 9, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" Remote">>, " Remote">>,
<<"Cannot unify address\n" <<?Pos(7, 5)
"Cannot unify address\n"
" and bytes(32)\n" " and bytes(32)\n"
"when checking the type of the expression at line 7, column 5\n" "when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\n" "against the expected type\n"
" bytes(32)">>]} " bytes(32)">>,
, {"stateful", <<?Pos(34, 5),
[<<"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>, "The type address is not a contract type\n"
<<"Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>, "when checking that the call to\n"
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>, " Address.to_contract\n"
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>, "has the type\n"
<<"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>, " address">>])
<<"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>, , ?TYPE_ERROR(stateful,
<<"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>, [<<?Pos(13, 35)
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]} "Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
, {"bad_init_state_access", <<?Pos(14, 35)
[<<"The init function should return the initial state as its result and cannot write the state,\n" "Cannot reference stateful function local_spend (at line 14, column 35)\nin the definition of non-stateful function fail2.">>,
<<?Pos(16, 15)
"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
<<?Pos(20, 31)
"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
<<?Pos(35, 47)
"Cannot reference stateful function Chain.spend (at line 35, column 47)\nin the definition of non-stateful function fail5.">>,
<<?Pos(48, 57)
"Cannot pass non-zero value argument 1000 (at line 48, column 57)\nin the definition of non-stateful function fail6.">>,
<<?Pos(49, 56)
"Cannot pass non-zero value argument 1000 (at line 49, column 56)\nin the definition of non-stateful function fail7.">>,
<<?Pos(52, 17)
"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>])
, ?TYPE_ERROR(bad_init_state_access,
[<<?Pos(11, 5)
"The init function should return the initial state as its result and cannot write the state,\n"
"but it calls\n" "but it calls\n"
" - set_state (at line 11, column 5), which calls\n" " - set_state (at line 11, column 5), which calls\n"
" - roundabout (at line 8, column 38), which calls\n" " - roundabout (at line 8, column 38), which calls\n"
" - put (at line 7, column 39)">>, " - put (at line 7, column 39)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(12, 5)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - new_state (at line 12, column 5), which calls\n" " - new_state (at line 12, column 5), which calls\n"
" - state (at line 5, column 29)">>, " - state (at line 5, column 29)">>,
<<"The init function should return the initial state as its result and cannot read the state,\n" <<?Pos(13, 13)
"The init function should return the initial state as its result and cannot read the state,\n"
"but it calls\n" "but it calls\n"
" - state (at line 13, column 13)">>]} " - state (at line 13, column 13)">>])
, {"field_parse_error", , ?TYPE_ERROR(modifier_checks,
[<<"line 6, column 1: In field_parse_error at 5:26:\n" [<<?Pos(11, 3)
"Cannot use nested fields or keys in record construction: p.x\n">>]} "The function all_the_things (at line 11, column 3) cannot be both public and private.">>,
, {"modifier_checks", <<?Pos(3, 3)
[<<"The function all_the_things (at line 11, column 3) cannot be both public and private.">>, "Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>,
<<"Namespaces cannot contain entrypoints (at line 3, column 3). Use 'function' instead.">>, <<?Pos(5, 10)
<<"The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>, "The contract Remote (at line 5, column 10) has no entrypoints. Since Sophia version 3.2, public\ncontract functions must be declared with the 'entrypoint' keyword instead of\n'function'.">>,
<<"The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>, <<?Pos(12, 3)
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>, "The entrypoint wha (at line 12, column 3) cannot be private. Use 'function' instead.">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>, <<?Pos(6, 3)
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]} "Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
, {"list_comp_not_a_list", <<?Pos(10, 3)
[<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">> "Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
]} <<?Pos(6, 3)
, {"list_comp_if_not_bool", "Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>])
[<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">> , ?TYPE_ERROR(list_comp_not_a_list,
]} [<<?Pos(2, 36)
, {"list_comp_bad_shadow", "Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
[<<"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">> ])
]} , ?TYPE_ERROR(list_comp_if_not_bool,
[<<?Pos(2, 44)
"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
])
, ?TYPE_ERROR(list_comp_bad_shadow,
[<<?Pos(2, 53)
"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
])
, ?TYPE_ERROR(map_as_map_key,
[<<?Pos(5, 25)
"Invalid key type\n"
" map(int, int)\n"
"Map keys cannot contain other maps.">>,
<<?Pos(6, 25)
"Invalid key type\n"
" lm\n"
"Map keys cannot contain other maps.">>])
, ?TYPE_ERROR(calling_init_function,
[<<?Pos(7, 28)
"The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.">>])
, ?TYPE_ERROR(bad_top_level_decl,
[<<?Pos(1, 1) "The definition of 'square' must appear inside a contract or namespace.">>])
, ?TYPE_ERROR(missing_event_type,
[<<?Pos(3, 5)
"Unbound variable Chain.event at line 3, column 5\n"
"Did you forget to define the event type?">>])
, ?TYPE_ERROR(bad_bytes_concat,
[<<?Pos(12, 40)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - 'g (at line 12, column 20)\n"
" - 'h (at line 12, column 23)\n"
"and result type\n"
" - bytes(10) (at line 12, column 28)">>,
<<?Pos(13, 28)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - 'd (at line 13, column 20)\n"
" - 'e (at line 13, column 23)\n"
"and result type\n"
" - 'f (at line 13, column 14)">>,
<<?Pos(15, 5)
"Cannot unify bytes(26)\n"
" and bytes(25)\n"
"at line 15, column 5">>,
<<?Pos(17, 5)
"Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
" - bytes(6) (at line 16, column 24)\n"
" - 'b (at line 16, column 34)\n"
"and result type\n"
" - 'c (at line 16, column 39)">>,
<<?Pos(19, 25)
"Cannot resolve length of byte array.">>])
, ?TYPE_ERROR(bad_bytes_split,
[<<?Pos(13, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - 'f (at line 12, column 20)\n"
"and result types\n"
" - 'e (at line 13, column 5)\n"
" - bytes(20) (at line 12, column 29)">>,
<<?Pos(16, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - bytes(15) (at line 15, column 24)\n"
"and result types\n"
" - 'c (at line 16, column 5)\n"
" - 'd (at line 16, column 5)">>,
<<?Pos(19, 5)
"Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
" - 'b (at line 18, column 20)\n"
"and result types\n"
" - bytes(20) (at line 18, column 25)\n"
" - 'a (at line 19, column 5)">>])
, ?TYPE_ERROR(wrong_compiler_version,
[<<?Pos(1, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " < 1.0">>,
<<?Pos(2, 1)
"Cannot compile with this version of the compiler,\n"
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
, ?TYPE_ERROR(multiple_contracts,
[<<?Pos(2, 3)
"Only the main contract can contain defined functions or entrypoints.\n"
"Fix: replace the definition of 'foo' by a type signature.">>])
, ?TYPE_ERROR(contract_as_namespace,
[<<?Pos(5, 28)
"Invalid call to contract entrypoint 'Foo.foo'.\n"
"It must be called as 'c.foo' for some c : Foo.">>])
]. ].
wants_stdlib(Name) -> -define(Path(File), "code_errors/" ??File).
lists:member -define(Msg(File, Line, Col, Err), <<?Pos("Code generation", ?Path(File), Line, Col) Err>>).
(Name,
[ "stdlib_include", -define(SAME(File, Line, Col, Err), {?Path(File), ?Msg(File, Line, Col, Err)}).
"list_comp", -define(AEVM(File, Line, Col, Err), {?Path(File), [{aevm, ?Msg(File, Line, Col, Err)}]}).
"list_comp_not_a_list", -define(FATE(File, Line, Col, Err), {?Path(File), [{fate, ?Msg(File, Line, Col, Err)}]}).
"list_comp_if_not_bool", -define(BOTH(File, Line, Col, ErrAEVM, ErrFATE),
"list_comp_bad_shadow" {?Path(File), [{aevm, ?Msg(File, Line, Col, ErrAEVM)},
]). {fate, ?Msg(File, Line, Col, ErrFATE)}]}).
failing_code_gen_contracts() ->
[ ?SAME(last_declaration_must_be_contract, 1, 1,
"Expected a contract as the last declaration instead of the namespace 'LastDeclarationIsNotAContract'")
, ?SAME(missing_definition, 2, 14,
"Missing definition of function 'foo'.")
, ?AEVM(polymorphic_entrypoint, 2, 17,
"The argument\n"
" x : 'a\n"
"of entrypoint 'id' has a polymorphic (contains type variables) type.\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?AEVM(polymorphic_entrypoint_return, 2, 3,
"The return type\n"
" 'a\n"
"of entrypoint 'fail' is polymorphic (contains type variables).\n"
"Use the FATE backend if you want polymorphic entrypoints.")
, ?SAME(higher_order_entrypoint, 2, 20,
"The argument\n"
" f : (int) => int\n"
"of entrypoint 'apply' has a higher-order (contains function types) type.")
, ?SAME(higher_order_entrypoint_return, 2, 3,
"The return type\n"
" (int) => int\n"
"of entrypoint 'add' is higher-order (contains function types).")
, ?SAME(missing_init_function, 1, 10,
"Missing init function for the contract 'MissingInitFunction'.\n"
"The 'init' function can only be omitted if the state type is 'unit'.")
, ?SAME(parameterised_state, 3, 8,
"The state type cannot be parameterized.")
, ?SAME(parameterised_event, 3, 12,
"The event type cannot be parameterized.")
, ?SAME(polymorphic_aens_resolve, 4, 5,
"Invalid return type of AENS.resolve:\n"
" 'a\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?SAME(bad_aens_resolve, 6, 5,
"Invalid return type of AENS.resolve:\n"
" list(int)\n"
"It must be a string or a pubkey type (address, oracle, etc).")
, ?AEVM(polymorphic_compare, 4, 5,
"Cannot compare values of type\n"
" 'a\n"
"The AEVM only supports '==' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare, 4, 5,
"Cannot compare values of type\n"
" (string * int)\n"
"The AEVM only supports '!=' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"- type string\n"
"- tuple or record of word type\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(complex_compare_leq, 4, 5,
"Cannot compare values of type\n"
" (int * int)\n"
"The AEVM only supports '=<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(higher_order_compare, 4, 5,
"Cannot compare values of type\n"
" (int) => int\n"
"The AEVM only supports '<' on values of\n"
"- word type (int, bool, bits, address, oracle(_, _), etc)\n"
"Use FATE if you need to compare arbitrary types.")
, ?AEVM(unapplied_contract_call, 6, 19,
"The AEVM does not support unapplied contract call to\n"
" r : Remote\n"
"Use FATE if you need this.")
, ?AEVM(unapplied_named_arg_builtin, 4, 15,
"The AEVM does not support unapplied use of Oracle.register.\n"
"Use FATE if you need this.")
, ?AEVM(polymorphic_map_keys, 4, 34,
"Invalid map key type\n"
" 'a\n"
"Map keys cannot be polymorphic in the AEVM. Use FATE if you need this.")
, ?AEVM(higher_order_map_keys, 4, 42,
"Invalid map key type\n"
" (int) => int\n"
"Map keys cannot be higher-order.")
, ?SAME(polymorphic_query_type, 3, 5,
"Invalid oracle type\n"
" oracle('a, 'b)\n"
"The query type must not be polymorphic (contain type variables).")
, ?SAME(polymorphic_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, 'r)\n"
"The response type must not be polymorphic (contain type variables).")
, ?SAME(higher_order_query_type, 3, 5,
"Invalid oracle type\n"
" oracle((int) => int, string)\n"
"The query type must not be higher-order (contain function types).")
, ?SAME(higher_order_response_type, 3, 5,
"Invalid oracle type\n"
" oracle(string, (int) => int)\n"
"The response type must not be higher-order (contain function types).")
, ?AEVM(higher_order_state, 3, 3,
"Invalid state type\n"
" {f : (int) => int}\n"
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
].
validation_test_() ->
[{"Validation fail: " ++ C1 ++ " /= " ++ C2,
fun() ->
Actual = case validate(C1, C2) of
{error, Errs} -> Errs;
ok -> #{}
end,
check_errors(Expect, Actual)
end} || {C1, C2, Expect} <- validation_fails()] ++
[{"Validation of " ++ C,
fun() ->
?assertEqual(ok, validate(C, C))
end} || C <- compilable_contracts()].
validation_fails() ->
[{"deadcode", "nodeadcode",
[<<"Data error:\n"
"Byte code does not match source code.\n"
"- Functions in the source code but not in the byte code:\n"
" .MyList.map2">>]},
{"validation_test1", "validation_test2",
[<<"Data error:\n"
"Byte code does not match source code.\n"
"- The implementation of the function code_fail is different.\n"
"- The attributes of the function attr_fail differ:\n"
" Byte code: payable\n"
" Source code: \n"
"- The type of the function type_fail differs:\n"
" Byte code: integer => integer\n"
" Source code: {tvar,0} => {tvar,0}">>]},
{"validation_test1", "validation_test3",
[<<"Data error:\n"
"Byte code contract is not payable, but source code contract is.">>]}].
validate(Contract1, Contract2) ->
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
Source = aeso_test_utils:read_contract(Contract2),
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source,
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
+11 -10
View File
@@ -15,7 +15,7 @@ simple_contracts_test_() ->
?assertMatch( ?assertMatch(
[{contract, _, {con, _, "Identity"}, [{contract, _, {con, _, "Identity"},
[{letfun, _, {id, _, "id"}, [{arg, _, {id, _, "x"}, {id, _, "_"}}], {id, _, "_"}, [{letfun, _, {id, _, "id"}, [{arg, _, {id, _, "x"}, {id, _, "_"}}], {id, _, "_"},
{id, _, "x"}}]}], parse_string(Text, [no_implicit_stdlib])), {id, _, "x"}}]}], parse_string(Text)),
ok ok
end}, end},
{"Operator precedence test.", {"Operator precedence test.",
@@ -39,7 +39,7 @@ simple_contracts_test_() ->
RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end, RightAssoc = fun(Op) -> CheckParens({a, Op, {b, Op, c}}) end,
NonAssoc = fun(Op) -> NonAssoc = fun(Op) ->
OpAtom = list_to_atom(Op), OpAtom = list_to_atom(Op),
?assertError({error, {_, parse_error, _}}, ?assertThrow({error, [_]},
parse_expr(NoPar({a, Op, {b, Op, c}}))) end, parse_expr(NoPar({a, Op, {b, Op, c}}))) end,
Stronger = fun(Op1, Op2) -> Stronger = fun(Op1, Op2) ->
CheckParens({{a, Op1, b}, Op2, c}), CheckParens({{a, Op1, b}, Op2, c}),
@@ -74,24 +74,25 @@ roundtrip_contract(Name) ->
parse_string(Text) -> parse_string(Text, []). parse_string(Text) -> parse_string(Text, []).
parse_string(Text, Opts) -> parse_string(Text, Opts) ->
case aeso_parser:string(Text, Opts) of aeso_parser:string(Text, Opts).
{ok, Contract} -> Contract;
Err -> error(Err)
end.
parse_expr(Text) -> parse_expr(Text) ->
[{letval, _, _, _, Expr}] = [{letval, _, _, _, Expr}] =
parse_string("let _ = " ++ Text, [no_implicit_stdlib]), parse_string("let _ = " ++ Text),
Expr. Expr.
round_trip(Text) -> round_trip(Text) ->
Contract = parse_string(Text, [no_implicit_stdlib]), Contract = parse_string(Text),
Text1 = prettypr:format(aeso_pretty:decls(Contract)), Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))),
Contract1 = parse_string(Text1, [no_implicit_stdlib]), Contract1 = parse_string(Text1),
NoSrcLoc = remove_line_numbers(Contract), NoSrcLoc = remove_line_numbers(Contract),
NoSrcLoc1 = remove_line_numbers(Contract1), NoSrcLoc1 = remove_line_numbers(Contract1),
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)). ?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({line, _L}) -> {line, 0};
remove_line_numbers({col, _C}) -> {col, 0}; remove_line_numbers({col, _C}) -> {col, 0};
remove_line_numbers([H|T]) -> remove_line_numbers([H|T]) ->
+2
View File
@@ -11,4 +11,6 @@ contract AddressLiterals =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote = entrypoint contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr_addr() : Remote =
Address.to_contract(addr())
+5 -4
View File
@@ -22,14 +22,16 @@ contract AENSTest =
stateful entrypoint claim(addr : address, stateful entrypoint claim(addr : address,
name : string, name : string,
salt : int) : unit = salt : int,
AENS.claim(addr, name, salt) name_fee : int) : unit =
AENS.claim(addr, name, salt, name_fee)
stateful entrypoint signedClaim(addr : address, stateful entrypoint signedClaim(addr : address,
name : string, name : string,
salt : int, salt : int,
name_fee : int,
sign : signature) : unit = sign : signature) : unit =
AENS.claim(addr, name, salt, signature = sign) AENS.claim(addr, name, salt, name_fee, signature = sign)
// TODO: update() -- how to handle pointers? // TODO: update() -- how to handle pointers?
@@ -52,4 +54,3 @@ contract AENSTest =
name : string, name : string,
sign : signature) : unit = sign : signature) : unit =
AENS.revoke(owner, name, signature = sign) AENS.revoke(owner, name, signature = sign)
+2
View File
@@ -30,4 +30,6 @@ contract AddressLiterals =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) = entrypoint contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr4() : address =
Address.to_contract(Contract.address)
+19
View File
@@ -0,0 +1,19 @@
contract BytesConcat =
entrypoint test1(x : bytes(10), y : bytes(20)) =
Bytes.concat(x, y)
entrypoint test2(x : bytes(10), y) : bytes(15) =
Bytes.concat(x, y)
entrypoint test3(x, y : bytes(20)) : bytes(25) =
Bytes.concat(x, y)
entrypoint fail1(x, y) : bytes(10) = Bytes.concat(x, y)
entrypoint fail2(x, y) = Bytes.concat(x, y)
entrypoint fail3(x : bytes(6), y : bytes(20)) : bytes(25) =
Bytes.concat(x, y)
entrypoint fail4(x : bytes(6), y) : _ =
Bytes.concat(x, y)
entrypoint fail5(x) = Bytes.to_str(x)
+20
View File
@@ -0,0 +1,20 @@
contract BytesSplit =
entrypoint test1(x) : bytes(10) * bytes(20) =
Bytes.split(x)
entrypoint test2(x : bytes(15)) : bytes(10) * _ =
Bytes.split(x)
entrypoint test3(x : bytes(25)) : _ * bytes(20) =
Bytes.split(x)
entrypoint fail1(x) : _ * bytes(20) =
Bytes.split(x)
entrypoint fail2(x : bytes(15)) : _ =
Bytes.split(x)
entrypoint fail3(x) : bytes(20) * _ =
Bytes.split(x)
+3
View File
@@ -0,0 +1,3 @@
function square(x) = x ^ 2
contract Main =
entrypoint main() = square(10)
+2
View File
@@ -15,3 +15,5 @@ contract BasicAuth =
entrypoint to_sign(h : hash, n : int) = entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
entrypoint weird_string() : string =
"\x19Weird String\x42\nMore\n"
+4
View File
@@ -0,0 +1,4 @@
contract BytesConcat =
entrypoint rot(a : bytes(3)) =
switch (Bytes.split(a))
(b, c) => Bytes.concat(c : bytes(2), b)
+7
View File
@@ -0,0 +1,7 @@
contract CallingInitFunction =
type state = int * int
entrypoint init() = (1, 2)
entrypoint call_init() = init()
@@ -0,0 +1,9 @@
contract BadAENSresolve =
type t('a) = option(list('a))
function fail() : t(int) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()
@@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : string * int) =
("foo", 1) != x
@@ -0,0 +1,4 @@
contract ComplexCompare =
entrypoint test(x : int) =
(1, 2) =< (x, x + 1)
@@ -0,0 +1,8 @@
contract HigherOrderCompare =
function cmp(x : int => int, y) : bool =
x < y
entrypoint test() =
let f(x) = (y) => x + y
cmp(f(1), f(2))
@@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint apply(f : int => int, x : int) = f(x)
@@ -0,0 +1,2 @@
contract HigherOrderEntrypoint =
entrypoint add(x : int) = (y) => x + y
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t(int => int) = {[m] = 0}
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract HigherOrderQueryType =
stateful function foo(o) : oracle_query(_, string ) =
Oracle.query(o, (x) => x + 1, 100, RelativeTTL(100), RelativeTTL(100))
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract HigherOrderResponseType =
stateful function foo(o, q : oracle_query(string, _)) =
Oracle.respond(o, q, (x) => x + 1)
entrypoint main() = ()
@@ -0,0 +1,7 @@
contract HigherOrderState =
record state = {f : int => int}
entrypoint init() = {f = (x) => x}
entrypoint apply(n) = state.f(n)
stateful entrypoint inc() = put(state{ f = (x) => state.f(x + 1) })
@@ -0,0 +1,2 @@
namespace LastDeclarationIsNotAContract =
function add(x, y) = x + y
@@ -0,0 +1,3 @@
contract MissingDefinition =
entrypoint foo : int => int
entrypoint main() = foo(0)
@@ -0,0 +1,3 @@
contract MissingInitFunction =
type state = int * int
@@ -0,0 +1,4 @@
contract ParameterisedEvent =
datatype event('a) = Event(int)
@@ -0,0 +1,4 @@
contract ParameterisedState =
type state('a) = list('a)
@@ -0,0 +1,7 @@
contract PolymorphicAENSresolve =
function fail() : option('a) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()
@@ -0,0 +1,7 @@
contract PolymorphicCompare =
function cmp(x : 'a, y : 'a) : bool =
x == y
entrypoint test() =
cmp(4, 6) && cmp(true, false)
@@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint id(x : 'a) : 'a = x
@@ -0,0 +1,3 @@
contract PolymorphicEntrypoint =
entrypoint fail() : 'a = abort("fail")
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
function foo(m) : t('a) = {[m] = 0}
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract PolymorphicQueryType =
stateful function is_oracle(o) =
Oracle.check(o)
entrypoint main() = ()
@@ -0,0 +1,5 @@
contract PolymorphicResponseType =
function is_oracle(o : oracle(string, 'r)) =
Oracle.check(o)
entrypoint main(o : oracle(string, int)) = is_oracle(o)
@@ -0,0 +1,9 @@
contract Remote =
entrypoint foo : int => int
contract UnappliedContractCall =
function f(r) = r.foo
entrypoint test(r) = f(r)(0)
@@ -0,0 +1,5 @@
contract UnappliedNamedArgBuiltin =
// Allowed in FATE, but not AEVM
stateful entrypoint main(s) =
let reg = Oracle.register
reg(signature = s, Contract.address, 100, RelativeTTL(100)) : oracle(int, int)
+1 -1
View File
@@ -50,7 +50,7 @@ contract ComplexTypes =
entrypoint remote_pair(n : int, s : string) : int * string = entrypoint remote_pair(n : int, s : string) : int * string =
state.worker.pair(gas = 10000, n, s) state.worker.pair(gas = 10000, n, s)
entrypoint map(f, xs) = function map(f, xs) =
switch(xs) switch(xs)
[] => [] [] => []
x :: xs => f(x) :: map(f, xs) x :: xs => f(x) :: map(f, xs)
+6
View File
@@ -0,0 +1,6 @@
contract Foo =
entrypoint foo : () => int
contract Fail =
entrypoint bad() : int = Foo.foo()
+3
View File
@@ -0,0 +1,3 @@
contract IndentFail =
entrypoint twoSpace() = ()
entrypoint oneSpace() = ()
+2 -2
View File
@@ -10,11 +10,11 @@ contract ListComp =
entrypoint l2_true() = [5,6,6,7,7,8] entrypoint l2_true() = [5,6,6,7,7,8]
entrypoint l3() = [x ++ y | x <- [[":)"] | x <- [1,2]] entrypoint l3() = [x ++ y | x <- [[":)"] | x <- [1,2]]
, y <- [[":("]]] , y <- [[":("]]]
entrypoint l3_true() = [[":)", ":("], [":)", ":("]] entrypoint l3_true() = [[":)", ":("], [":)", ":("]]
entrypoint l4() = [(a, b, c) | let is_pit(a, b, c) = a*a + b*b == c*c entrypoint l4() = [(a, b, c) | let is_pit(a, b, c) = a*a + b*b == c*c
, let base = [1,2,3,4,5,6,7,8,9,10] , let base = [1..10]
, a <- base , a <- base
, b <- base, if (b >= a) , b <- base, if (b >= a)
, c <- base, if (c >= b) , c <- base, if (c >= b)
+7
View File
@@ -0,0 +1,7 @@
contract Fail =
entrypoint tttt() : bool * int =
let f(x : 'a) : 'a = x
(f(true), f(1))
+1 -2
View File
@@ -1,5 +1,4 @@
// This contract should be compiled with no_implicit_stdlib option. // This should include Lists.aes implicitly, since Option.aes does.
// It should include Lists.aes implicitly however, because Option.aes depends on it.
include "Option.aes" include "Option.aes"
contract Test = contract Test =
+6
View File
@@ -0,0 +1,6 @@
contract MapAsMapKey =
type t('key) = map('key, int)
type lm = list(map(int, int))
entrypoint foo(m) : t(map(int, int)) = {[m] = 0}
entrypoint bar(m) : t(lm) = Map.delete(m, {})
+3
View File
@@ -0,0 +1,3 @@
contract MissingEventType =
entrypoint main() =
Chain.event("MAIN")
+5
View File
@@ -0,0 +1,5 @@
contract ContractOne =
entrypoint foo() = "foo"
contract ContractTwo =
entrypoint bar() = "bar"
+3
View File
@@ -1,3 +1,6 @@
include "List.aes"
include "Func.aes"
contract StdInc = contract StdInc =
entrypoint test() = List.map((x) => Func.id(x), [1,2,3,4]) entrypoint test() = List.map((x) => Func.id(x), [1,2,3,4])
+3 -2
View File
@@ -91,10 +91,11 @@ contract Identity =
// } // }
// let id(x) = x // let id(x) = x
// let main(xs) = map(double,xs) // let main(xs) = map(double,xs)
entrypoint z(f,x) = x function z(f,x) = x
function s(n) = (f,x)=>f(n(f,x)) function s(n) = (f,x)=>f(n(f,x))
function add(m,n) = (f,x)=>m(f,n(f,x)) function add(m,n) = (f,x)=>m(f,n(f,x))
entrypoint main(_) =
entrypoint main() =
let three=s(s(s(z))) let three=s(s(s(z)))
add(three,three) add(three,three)
(((i)=>i+1),0) (((i)=>i+1),0)
+1 -1
View File
@@ -1,6 +1,6 @@
contract TuplesMatch = contract TuplesMatch =
entrypoint tuplify3() = (t) => switch(t) function tuplify3() = (t) => switch(t)
(x, y, z) => 3 (x, y, z) => 3
entrypoint fst(p : int * string) = entrypoint fst(p : int * string) =
+5
View File
@@ -57,3 +57,8 @@ contract Test =
let f() = 0 let f() = 0
let g() = f() let g() = f()
function id(x : 'a) : 'a = x
entrypoint wrong_return(n : int) : bool =
id(n)
+63
View File
@@ -0,0 +1,63 @@
// Builtins without named arguments can appear unapplied in both AEVM and FATE.
// Named argument builtins are:
// Oracle.register
// Oracle.respond
// AENS.preclaim
// AENS.claim
// AENS.transfer
// AENS.revoke
// Oracle.extend
contract UnappliedBuiltins =
entrypoint main() = ()
type o = oracle(int, int)
type t = list(int * string)
type m = map(int, int)
datatype event = Event(int)
stateful function chain_spend() = Chain.spend
function chain_event() = Chain.event
function chain_balance() = Chain.balance
function chain_block_hash() = Chain.block_hash
function call_gas_left() = Call.gas_left
function b_abort() = abort
function b_require() = require
function oracle_query_fee() = Oracle.query_fee
stateful function oracle_query() = Oracle.query : (o, _, _, _, _) => _
function oracle_get_question() = Oracle.get_question : (o, _) => _
function oracle_get_answer() = Oracle.get_answer : (o, _) => _
function oracle_check() = Oracle.check : o => _
function oracle_check_query() = Oracle.check_query : (o, _) => _
function aens_resolve() = AENS.resolve : (_, _) => option(string)
function map_lookup() = Map.lookup : (_, m) => _
function map_lookup_default() = Map.lookup_default : (_, m, _) => _
function map_member() = Map.member : (_, m) => _
function map_size() = Map.size : m => _
function map_delete() = Map.delete : (_, m) => _
function map_from_list() = Map.from_list : _ => m
function map_to_list() = Map.to_list : m => _
function crypto_verify_sig() = Crypto.verify_sig
function crypto_verify_sig_secp256k1() = Crypto.verify_sig_secp256k1
function crypto_ecverify_secp256k1() = Crypto.ecverify_secp256k1
function crypto_ecrecover_secp256k1() = Crypto.ecrecover_secp256k1
function crypto_sha3() = Crypto.sha3 : t => _
function crypto_sha256() = Crypto.sha256 : t => _
function crypto_blake2b() = Crypto.blake2b : t => _
function string_sha256() = String.sha256
function string_blake2b() = String.blake2b
function string_length() = String.length
function string_concat() = String.concat
function string_sha3() = String.sha3
function bits_test() = Bits.test
function bits_set() = Bits.set
function bits_clear() = Bits.clear
function bits_union() = Bits.union
function bits_intersection() = Bits.intersection
function bits_difference() = Bits.difference
function bits_sum() = Bits.sum
function int_to_str() = Int.to_str
function address_to_str() = Address.to_str
function address_is_oracle() = Address.is_oracle
function address_is_contract() = Address.is_contract
function address_is_payable() = Address.is_payable
function bytes_to_int() = Bytes.to_int : bytes(10) => int
function bytes_to_str() = Bytes.to_str : bytes(99) => string
@@ -0,0 +1,13 @@
contract UnderscoreNumberLiterals =
entrypoint ints() : list(int) =
[ 1_999_000_000,
19_99_00_00_00,
0xfff_FFF_010 ]
entrypoint bytes() : list(bytes(4)) =
[ #abcd_ef_00,
#01_02_03_04,
#aaaa_FFFF ]
+4
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1
+4
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
entrypoint attr_fail() = ()
entrypoint type_fail(x) = x
entrypoint code_fail(x) = x - 1
+4
View File
@@ -0,0 +1,4 @@
payable contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1
+4
View File
@@ -0,0 +1,4 @@
contract VClose =
entrypoint missing_bracket() =
let x = [1, 2, 3
entrypoint bar() = ()
+3
View File
@@ -0,0 +1,3 @@
contract VSemi =
record missing_brace = { x : int
entrypoint foo() = ()
@@ -0,0 +1,7 @@
@compiler < 1.0
@compiler == 9.9.9
@compiler >= 0.1
@compiler =< 100.0.1
contract Fail =
entrypoint foo() = ()