Compare commits

..

207 Commits

Author SHA1 Message Date
Gaith Hallak 9a95df2108 Remove unexported functions 2023-05-06 20:08:16 +03:00
Gaith Hallak adb37fa510 Revert "Remove unused functions"
This reverts commit f835862a48.
2023-05-06 20:04:18 +03:00
Gaith Hallak f835862a48 Remove unused functions 2023-05-06 16:34:22 +03:00
Gaith Hallak 8475b024df Break the dependecy between constraints and env 2023-05-06 14:07:14 +03:00
Gaith Hallak b98af0fab6 Move fresh_uvar to type utils 2023-05-06 13:48:52 +03:00
Gaith Hallak 6193d144a2 Remove unknown wanring error from type errors 2023-05-06 13:28:57 +03:00
Gaith Hallak add858a1ad Move bind_consts to aeso_ast_infer_types
The reason for this is that it depends on infer_const
2023-05-06 13:22:59 +03:00
Gaith Hallak fa94b96997 Move freshening functions to aeso_tc_env 2023-05-06 12:14:24 +03:00
Gaith Hallak ac428d1e36 Move error reporting to aeso_tc_env 2023-04-30 21:15:14 +03:00
Gaith Hallak de4c8f5412 Remove the dependency of type_utils on errors 2023-04-30 13:48:23 +03:00
Gaith Hallak 0dc647f139 Move typedefs to a separate module 2023-04-30 11:12:26 +03:00
Gaith Hallak fceb124f89 Remove freshening from the type checker 2023-04-30 11:01:51 +03:00
Gaith Hallak dab0e4b758 Move opposite_variance to type utils 2023-04-30 10:00:46 +03:00
Gaith Hallak fbf12cf8b4 Split unfolding from the type checker 2023-04-30 09:20:08 +03:00
Gaith Hallak 2cdd3ed576 Split unification from the type checker 2023-04-29 14:26:32 +03:00
Gaith Hallak 5f277bed08 Remove aeso_ast module 2023-04-29 13:50:11 +03:00
Gaith Hallak 565863681c Split constraints from type checker 2023-04-29 11:25:55 +03:00
Gaith Hallak 9fe2696432 Split env from the type checker 2023-04-29 07:26:04 +03:00
Gaith Hallak 4c90b00fd0 Implement env getters and setters 2023-04-26 12:55:23 +03:00
Gaith Hallak 9f8f3c2ac8 Move blocks processing to desugar module 2023-04-24 17:45:32 +03:00
Gaith Hallak 4a8870fb1d Rename ets_manager and type_utils 2023-04-24 16:30:50 +03:00
Gaith Hallak a91470fe3c Split desugaring from the type checker 2023-04-24 16:23:18 +03:00
Gaith Hallak ab69b6c2a7 Split warnings from the type checker 2023-04-22 12:11:28 +03:00
Gaith Hallak 296b2a4bb0 Remove duplicated record definition 2023-04-21 21:17:10 +03:00
Gaith Hallak 1f0726fad7 Split type errors from type checker 2023-04-21 21:06:46 +03:00
Gaith Hallak 433d180c17 Split pretty printing from type checker 2023-04-21 20:28:56 +03:00
Gaith Hallak 30a179bfcc Move smaller functions outside of aeso_ast_infer_types 2023-04-21 20:07:53 +03:00
Gaith Hallak 0e4c24958c Remove the access of scopes 2023-04-21 19:51:40 +03:00
Gaith Hallak 0baedfeede Move typesig_to_fun_t to type utils 2023-04-20 20:23:58 +03:00
Gaith Hallak 36058df924 Split utils and ets management 2023-04-20 20:05:23 +03:00
Gaith Hallak 7b6eba5319 Introduce contract-level compile-time constants (#432)
* Allow compile-time constants as toplevel declarations

* Remove the test that fails on toplevel consts

* Warn when shadowing a constant

* Allow records to be used as compile time constants

* Allow data constructors in compile-time constants

* Disable some warnings for toplevel constants

Since variables and functions cannot be used in the definition of
a compile time constants, the following warnings are not going to be
reported:

* Used/Unused variable
* Used/Unused function

* Do not reverse constants declarations

* Add tests for all valid expressions

* Add test for accessing const from namespace

* Revert "Do not reverse constants declarations"

This reverts commit c4647fadacd134866e4be9c2ab4b0d54870a35fd.

* Add test for assigining constant to a constant

* Show empty map or record error when assigning to const

* Report all invalid constant expressions before fail

* Allow accessing records fields in toplevel consts

* Undo a mistake

* Add test for warning on const shadowing

* Show error message when using pattern matching for consts

* Remove unused error

* Ban toplevel constants in contract interfaces

* Varibles rename

* Change the error message for invalid_const_id

* Make constants public in namespaces and private in contracts

* Add a warning about unused constants in contracts

* Use ban_when_const for function applications

* Test for qualified access of constants in functions

* Add failing tests

* Add test for the unused const warning

* Update CHANGELOG

* Update all_syntax test file

* Treat expr and type inside bound as bound

* Allow typed ids to be used for constants

* List valid exprs in the error message for invalid exprs

* Fix tests

* Update the docs about constants

* Update syntax docs

* Check validity of const exprs in a separate functions

* Call both resolve_const and resolve_fun from resolve_var
2023-04-12 14:20:41 +03:00
Gaith Hallak 99bb3fe1fb Mark only included files as potentially unused (#442)
* Mark only included files as potentially unused

* Update CHANGELOG

* Add test
2023-03-21 13:55:18 +03:00
Hans Svensson 311bf49505 Prepare v7.1.0 release (#438) 2023-02-24 09:40:06 +01:00
Denis Davidyuk 0e3bcba07d Fix markup in sophia_features.md (#437) 2023-02-15 13:27:29 +03:00
Marco Walz 699d1f7ab8 fix: use latest pygments version to generate docs (#435) 2023-02-02 08:32:54 +01:00
Marco Walz 1a40a93157 chore(deps): mkdocs version update (#434) 2023-02-02 10:16:27 +03:00
Gaith Hallak c078119bc4 Add hole expression (#433)
* Add hole expressions

* Fix the issue of unreported holes

* Add tests

* New line in the end of the test file

* Update CHANGELOG

* Add hole expression to the docs

* Do not treat hole as a special type

* Update docs

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2023-01-12 16:23:36 +03:00
Gaith Hallak 31fd8fe24f Hide warning when calling with non-0 value arg (#431)
* Hide warning when calling with non-0 value arg

* Update the tests

* Update CHANGELOG
2022-12-12 11:44:24 +03:00
Nikita Fuchs 9ad8e26e88 Add clarification for Chain.timestamp in the stdlib docs (#429) 2022-12-07 17:34:55 +03:00
Gaith Hallak 5adeb6c93e Ban using contracts as namespaces (#428)
* Ban calling contracts functions as functions namespaces

* Ban using contracts as namespaces

* Add tests

* Update CHANGELOG

* Separate guards with a semicolon
2022-11-23 12:03:24 +03:00
Gaith Hallak 256df25af4 Check contracts and entrypoints modifiers when implementing interfaces (#427)
* Check contracts and entrypoints modifiers when implementing interfaces

* Fix existing tests

* Add passing tests

* Add failing tests

* Update docs

* Update CHANGELOG
2022-11-17 11:40:57 +03:00
Gaith Hallak 83abfae32b Ban the unification of uvars and var_args functions (#423)
* Ban the unification of uvar and var_args function

* Update CHANGELOG

* Fix the tests

* Undo indent

* Change the error message for unify_varargs
2022-11-01 18:10:57 +02:00
Denis Davidyuk 4ca90feea0 Rename type_defs to typedefs in ACI to increase compatibility (#421) 2022-11-01 08:55:00 +02:00
Gaith Hallak 09638daa90 Return mapping from variables to registers in fate compilation (#411)
* Return mapping from variables to registers

* Fix dialyzer issues

* Record real names

* Report saved fresh names as part of fcode env

* Undo whitespace changes

* Fix dialyzer warnings

* Formatting fix

* Use function names as strings

* Manually handle making function names

* Update CHANGELOG

* Make variables registers optional

* Update docs about the new flag

* Remove empty saved_fresh_names map from fcode env
2022-10-25 09:42:02 +03:00
Gaith Hallak d59023a9f4 Allow calling a different instance of the current contract (#379)
Add functions as fields before inferring

Unbound untyped fields before binding typed ones

Fix failing tests

Make complex_types contract non-compatible with aevm

Reduce code duplication

Undo changes to test.aes

Remove special handling of __constructor__ field

Resolve field constraint by arity of contract function

Update CHANGELOG

Update CHANGELOG.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Split bind_field function

Add a comment about rebinding
2022-10-23 15:01:28 +03:00
Radosław Rowicki 34b52739fd Include all functions in the symbols map (#418)
* Include all functions in the symbols map

* .

* remove improper wording

* Use update_symbols exported from aebytecode

* Extract adding child symbols into a separate fun

* Make child contracts symbols optional

* Document include_child_contract_symbols option

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-10-07 15:57:37 +03:00
Gaith Hallak 1c83287d45 Add separate flags for each scode optimization (#410)
* Add separate flags for each scode optimization

* Add a list of available optimizations to docs

* Update CONTRIBUTING.md

* Update docs/aeso_compiler.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Prefix rules functions with optimize_ instead of r_

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-10-07 12:09:53 +03:00
Gaith Hallak da92ddbd5d Polymorphism fixes (#415)
* Assume that void is a supertype of all types

* Add test for void supertype

* Unify functions with decls from implemented interfaces

* Rename delete_if_implementation

* Match only with function name and without typesig
2022-10-04 12:40:50 +03:00
Gaith Hallak c1c169273c Add options to enable/disable certain optimizations (#409)
* Add flags to enable/disable specific optimizations

* Fix typos

* Enable/disable scode optimization

* Update CHANGELOG.md

* Remove optimize_all option
2022-08-30 10:14:46 +03:00
Gaith Hallak ad4c341a4a Bump version to 7.0.1 (#408)
* Fix broken link in CONTRIBUTING.md

* Bump version to 7.0.1 and update CHANGELOG.md
2022-08-04 19:38:24 +02:00
Gaith Hallak f964fa89a1 Add CONTRIBUTING.md (#406)
* Add CONTRIBUTING.md

* Include CONTRIBUTING.md in README.md

* Fix broken links

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

* Use "If a PR" instead of "If the PR"

* Mention fold and pretty printing

* Add missing precedence of the operator '|>'

* Add a note about tests

* Rename to Sophia

* Add missing using keyword

* Update the entire list of keywords in sophia syntax doc

* Add a section about creating a new aesophia release
2022-08-04 19:35:48 +04:00
Gaith Hallak 8d8d9c6b83 Update Sophia syntax docs to include missing information about existing syntax (#405)
* Add main contract, contract interface, and guards to the docs syntax

* Use Sep1 instead of Sep for the GuardedDef

* Add guarded case for switches

* Change '=' to '::=' in GuardedDef

* Add Using

* Add '|>' binary operator to aeso_syntax

* Add assign patter

* Fix typos

* Add polymorphism implmented interface syntax
2022-08-03 22:25:42 +02:00
Gaith Hallak c98ea25e8b Fix: Get the type of Chain.create() from its application (#407)
* Get the type of Chain.create() from its application

* Add test contract

* Update CHANGELOG.md

* Update the tests

* Update tests again
2022-08-03 22:24:22 +02:00
Radosław Rowicki 4dbc9858fb Prepare 7.0.0 release, upgrade to OTP24 (#402)
* Prepare v7.0.0 release

* OTP25

* Try OTP24

* Update aebytecode

* aeb 3.1.1

* Update CHANGELOG.md

Co-authored-by: Hans Svensson <hanssv@gmail.com>

* Update rebar.lock

Co-authored-by: Hans Svensson <hanssv@gmail.com>
Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-07-28 21:38:18 +02:00
Gaith Hallak 51f9eaa934 Update the documentation and changelog to include polymorphism (#396)
* Update CHANGELOG

* Docs: first part

* Docs: first part (subtyping)

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Change "the same type" to "compatible types"

* Formatting for subtyping rules

* Note about type variance

* Update docs/sophia_features.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2022-07-25 18:47:16 +02:00
Radosław Rowicki 0ebcf006e2 Prepare for new repl (#401)
Minor tweaks that are used by aerepl
2022-07-25 18:46:54 +02:00
Gaith Hallak 381a7c98cd Handle all user generated code errors in the type checker (#394)
* Move the missing_definition error to the type checker

* Move the parameterised_event and parameterised_state errors to the type checker

* Remove check_state_and_event_types from ast_to_fcode

* Move missing_init_function to the type checker

* Remove the code error last_declaration_must_be_main_contract

* Expand the tests for missing init function

* Remove found_void error

* Move the higher order entrypoint error to type checker

* Move invalid_aens_resolve_type error to type checker

* Add more tests for AENS.resolve

* Add test for AENS.resolve with using

* Move invalid_oracle_type error to type checker

* Move old code errors tests to compilable_contracts

* Remove the file aeso_code_errors.erl

* Add comment about state type
2022-07-25 19:48:46 +04:00
Radosław Rowicki 4bec4e5107 Added support for EXIT op (#397)
* EXIT op

* changelog

* docs

* Update CHANGELOG.md
2022-07-08 15:56:29 +02:00
Marco Walz 4dd247b159 Merge pull request #399 from aeternity/mkdocs-updates
chore: update deps and remove caching of python libs
2022-07-06 13:15:58 +02:00
marc0olo d926c4a7e3 chore: update deps and remove caching of python libs 2022-07-06 10:05:44 +02:00
Nikita Fuchs 7b8957b46a Update sophia_stdlib.md (#350)
* Update sophia_stdlib.md

add more precise description of oracle TTLs

* Update sophia_stdlib.md
2022-06-18 12:03:46 +02:00
Gaith Hallak e46226a693 Polymorphism support (#357)
* Add polymorphism to syntax tree and parser

* Add polymorphism to infer types

* Fix pretty printing

* Add new tests and fix old tests

* Fix the comparison between unit and empty tuple

* Report undefined interface errors before checking implemented interfaces

* Add test for implementing multiple interfaces

* Add test for implementing two interfaces with entrypoints of same names and different types

* Add tests for interfaces implementing interfaces

* Draft: Add variance switching

* Revert "Draft: Add variance switching"

This reverts commit 92dc6ac169cfbff447ed59de04994f564876b3fb.

* Add variance switching

* Fix broken tests

* Fix broken abi tests

* Add tests for variance switching

* Fix tests after rebase

* Variance switching for custom datatypes

* Fix dialyzer warning

* Add testing for custom types variance switching

* Make opposite_variance a separate function

* Make is_subtype/4 a separate function

* Fix warning

* Mark tvars as invariant

* Add type_vars_uvar ets table to ets_tables()

* Don't destroy and recreate type errors table when not needed

* Fixes from the reviews

* Use is_list to check if a var is a list

* Compare named args in fun_t

* Test only for covariance and contravariance

* Remove arrows_in_type and use infer_type_vars_variance instead

* Add tests for option and type aliases

* Fix previous commit

* Rename check_implemented_interfaces_recursive to check_implemented_interfaces1

* Make interfaces declare functions from extended interfaces

* Restore test.aes

* Add test for variance switching in records

* Enable variance switching for record types

* Handle builtin types type variables separately

* Add tests for oracles and oracle queries

* Replace compare_types with non-throwing version of unify

* Add the context to unification error

* Test variance switching for bivariant records

* Give clear names to the records in records variance switching test

* Handle comments about polymorphism_variance_switching.aes

* Rename datatypes in custom types variance switching test for readability

* Change the variance of the oracle_query type vars

* Add test for accessing maps with the wrong type

* Default to invariant when the variance of the type vars is unknown

* Rename test files to have common prefix

* Rename functions in variance switching tests for readability

* Fix variance inference

* Eliminate redundant tests

* Test all cases for bivariant
2022-06-17 13:09:07 +04:00
Gaith Hallak b599d581ee Fix warnings reporting and stdlib warnings (#367)
* Fix stdlib warnings

* Mark unused includes when used from non-included files

* Do not mark indirectly included files as unused

* Show unused include warning only for files that are never used

* Remove unused include from Option.aes

* Consider functions passed as args as used

* Return warnings as a sorted list

* Fix failing tests

* Fix dialyzer warning

* Fix warning in Func.aes
2022-06-14 12:22:32 +04:00
Gaith Hallak b3767071a8 Allow binary operators to be used as lambdas (#385)
* Add operator lambdas

* Do not register anonymous functions as called functions

* Add tests

* Update CHANGELOG

* Update the docs

* Do not allow (..) to be used as a lambda

* Rename the function sum to any
2022-06-03 13:12:23 +04:00
Gaith Hallak b0e6418161 Ban empty record definitions (#384)
* Ban empty record declarations

* Use definition instead of declaration

* Fix the failing test
2022-05-25 17:59:46 +04:00
Gaith Hallak a894876f56 Show the file name in the location if the file is included (#383) 2022-05-10 18:27:06 +04:00
Radosław Rowicki 0af45dfd19 Deprecate AEVM (#375)
* Deprecate AEVM

* Fix test, changelog

* Restore old rebar

* rebar lock fix

* undo export

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>

* undo export

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>

* Solve GH suggestions

* Fix the docs

* update docs

* Remove unused tests

* undo weird change

Co-authored-by: Gaith Hallak <gaithhallak@gmail.com>
2022-05-10 15:33:59 +02:00
Gaith Hallak c5bfcd3bdc Add MCL_BLS12_381 types to from_fate_builtin (#382)
* Add MCL_BLS12_381 types to from_fate_builtin

* Add tests for mcl_bls12_381 types to sophia value
2022-05-05 13:19:43 +04:00
Dincho Todorov 85879f5380 Fix BLS12_381.fp and BLS12_381.fr size in the docs (#377) 2022-04-27 17:10:56 +03:00
Dincho Todorov 8897cc6cbd Fix bls12_381 anchor in the stdlib docs (#376) 2022-04-26 20:10:11 +03:00
Marco Walz 0ec7fdc6ac Merge pull request #368 from aeternity/docs/improve-stdlib-structure
docs: order namespaces alphabetically and place Set in includables
2022-04-12 12:49:40 +02:00
Gaith Hallak 74aff5401b Introduce pipe operator |> (#371)
* Add pipe operator

* Add tests

* Update docs and CHANGELOG
2022-04-12 12:40:32 +03:00
Marco Walz cfcf0a8a81 Merge pull request #372 from aeternity/pygments-version
chore(deps): bump pygments version
2022-03-28 12:09:20 +02:00
marc0olo ca31db7cad chore(deps): bump mkdocs version from 1.2.3 to 1.2.4 2022-03-28 10:43:37 +02:00
marc0olo 196460a607 chore(deps): bump pygments version 2022-03-22 13:19:11 +01:00
marc0olo bf04362f9a docs: order namespaces alphabetically and place Set in includables 2022-02-04 12:41:08 +01:00
Hans Svensson d4ea7d5d3b Clarify signature format for ecverify/ecrecover (#365) 2022-01-11 14:02:40 +01:00
Hans Svensson c1c3c29393 Add oneline error pretty-printing (#364) 2022-01-11 14:02:05 +01:00
Gaith Hallak b474bb22cd Implement caching for compiled child contracts (#363) 2022-01-11 16:50:59 +04:00
Hans Svensson c04f66a00a Merge pull request #362 from aeternity/ghallak/354
Simplify error messages reported by the compiler
2022-01-11 11:48:11 +04:00
Gaith Hallak 37d86ad45b Simplify error messages reported by the compiler
Add raw error message for 2 errors

The errors: `unnamed_map_update_with_default` and `unbound_variable`.

Revert "Add raw error message for 2 errors"

This reverts commit 0db6d16140.

Remove trailing new lines and at POS from error messages

Convert multiple line error messages into single line error messages

Remove at POS from pp_why_record context

Change error message with new line before code

Fix tests for changed error messages

Fix the rest of the error messages

Add new line after error message

Remove new line from the end of data error messages
2022-01-11 11:48:08 +04:00
Gaith Hallak 60f3a484e6 Solve constraints together and in the order they are added (#360)
* Solve named argument constraints when record type dereferencing fails

* Revert "Solve named argument constraints when record type dereferencing fails"

This reverts commit ca38a171a9eefdddbc3f6a41f8a268c42662cd7a.

* Solve constraints together and in order

* Fix dialyzer warnings

* Add comment on solve_known_record_types

* Remove unused function
2021-12-16 13:54:06 +02:00
Hans Svensson 40c78c1707 Merge pull request #361 from aeternity/clarify_protected_calls
Clarify documentation on protected calls
2021-12-10 14:52:11 +01:00
Hans Svensson cf08aeee04 Clarify documentation on protected calls 2021-12-10 14:46:44 +01:00
Marco Walz a04dd6c86d Merge pull request #359 from marc0olo/feature/syntax-highlighting
feat: activate Sophia syntax highlighting by using specific pygments …
2021-12-03 17:26:14 +01:00
marc0olo f488b35f2e chore: make sure python libs are updated on install 2021-12-01 10:06:07 +01:00
marc0olo cc1de9baba feat: activate Sophia syntax highlighting by using specific pygments version 2021-12-01 08:37:49 +01:00
Gaith Hallak fe5f5545d3 Add compiler warnings (#346)
* Add compiler warnings

Add include_type annotation to position

Add warning for unused includes

Add warning for unused stateful annotation

Add warning for unused functions

Add warning for shadowed variables

Add division by zero warning

Add warning for negative spends

Add warning for unused variables

Add warning for unused parameters

Change the ets table type to set for unused vars

Add warning for unused type defs

Move unused variables warning to the top level

Temporarily disable unused functions warnings

Add all kinds of warnings to a single ets table

Enable warnings separately through options

Use when_option instead of enabled_warnings

Turn warnings into type errors with warn_error option

Enable warning package warn_all

Re-enable unused functions warnings

Report warnings as type errors in a separate function

Make unused_function a recognized warning

Report warnings as a result of compilation

Fix tests and error for unknown warnings options

Fix dialyzer warnings

Do not show warning for variables called "_"

Move warnings handling into a separate module

Do not show warning for unused public functions in namespaces

Add src file name to unused include warning

Mark public functions in namespaces as used

Add tests for added warnings

Add warning for unused return value

Add test for turning warnings into type errors

* Update CHANGELOG
2021-11-24 11:46:21 +02:00
Dincho Todorov 98a4049f03 Use OTP 21 for builds (#332) 2021-11-11 23:08:31 +02:00
seanhinde 3dce0e627b Merge pull request #353 from aeternity/otp-24-deps
Update aebytecode dep for otp-24
2021-11-11 10:51:40 +01:00
Sean Hinde 6b46fc268b Use older rebar3 for upgrade 2021-11-10 14:32:29 +01:00
Sean Hinde 30bedad164 Use older rebar3 for upgrade 2021-11-10 14:25:33 +01:00
Sean Hinde 4d6938c741 Update aebytecode dep for otp-24 2021-11-10 14:21:23 +01:00
Hans Svensson 10fc88a21d Merge pull request #349 from aeternity/fix_oracle_expiry_doc
Fix docs Oracle.expire -> Oracle.expiry
2021-10-21 14:16:32 +02:00
Hans Svensson 3218a2c172 Fix docs Oracle.expire -> Oracle.expiry 2021-10-21 14:08:03 +02:00
Gaith Hallak 5ad5270e38 Bump version to 6.1.0 and fix CHANGELOG (#348) 2021-10-20 12:42:48 +03:00
Gaith Hallak a982f25262 Pattern guards for functions and switch statements (#339)
* Add case guards to parser

* Add pattern guards to infer types and fcode generation

* Add functions guards

* Add test for patterns guards

* Update docs

* Update CHANGELOG.md

* Remove stateful context from Env for guards

* Elaborate on guards

* Add failing test for stateful pattern guards

* Implement multiple guards

* Fix tests

* Disable aevm related tests

* Split the sentence before if and otherwise

* Fix type in docs

* Implement multiple exprs in the same guard

* Fix pretty printing

* Change tests to include multiple guards

* Add test for non-boolean guards

* Desugar clauses with guards

* Fix incomplete patterns bug

* Fix docs

* Compile to icode when no guards are used

* Revert "Disable aevm related tests"

This reverts commit e828099bd97dffe11438f2e48f3a92ce3641e85b.
2021-10-20 11:04:00 +03:00
Hans Svensson 20cab3ae57 Merge pull request #347 from marc0olo/chore/update-mkdocs-version
chore: mkdocs v1.2.3
2021-10-18 10:33:10 +02:00
marc0olo 1ffb20178c chore: mkdocs v1.2.3 2021-10-15 12:03:46 +02:00
Hans Svensson 6d79d2d558 GH344 - Add builtin types in decoder (#345)
* Only use basic types in type_env

* Add builtin types to the calldata/result decoder for FATE
2021-09-30 19:11:00 +02:00
Hans Svensson 24c579a5d3 Merge pull request #342 from aeternity/fix_aci_rendering
Don't forget stateful when rendering the ACI
2021-09-24 14:21:42 +02:00
Hans Svensson 1be24c94c5 Don't forget stateful when rendering the ACI 2021-09-20 14:50:16 +02:00
Hans Svensson ebb1f9ecf9 Merge pull request #340 from aeternity/add_bitwise_stdlib
Add Bitwise.aes to stdlib
2021-09-16 10:49:00 +02:00
Hans Svensson 9cb3158dfd Fix documentation and comments 2021-09-12 16:24:08 +02:00
Hans Svensson becafe4001 Add Bitwise.aes to stdlib 2021-09-12 14:57:45 +02:00
Gaith Hallak e8a171dc45 Allow assigning patterns to variables (#336)
* Change syntax tree and parser

* Add assign pattern to type inference

* Use check_expr instead of hard-coded type

* Add fcode generation for assign pattern

* Implement rename_spat for assign pattern

* Add tests

* Update CHANGELOG.md

* Update docs and changelog

* Add letpat to aeso_syntax_utils:fold

* Use Plus instead of Scoped
2021-09-11 16:18:30 +02:00
Gaith Hallak a7b7aafced Implement loading namespaces with the using keyword (#338)
* Add using namespace as to scanner and parser

* Change the alias from id() to con()

* Add using namespace to AST type inference

* Allow using namespace to appear in the top level

* Allow using namespace to appear inside functions

* Add a compiler test for using namespace

* Handle name collisions

* Implement mk_error for ambiguous_name

* Add failing test for ambiguous names

* Limit the scope of the used namespaces

* Add test for wrong scope of using namespace

* Use a single using declaration

* Split long line

* Forbid using undefined namespaces

* Add a test for using undefined namespaces

* Change the type of used_namespaces

* Add using namespace parts to scanner and parser

* Add using namespace parts to ast type inference

* Add tests for using namespace parts

* Update CHANGELOG.md

* Code cleaning

* Update the docs

* Update the docs about the same alias for multiple namespaces
2021-09-07 17:45:28 +03:00
Marco Walz 262452fb70 Feature/mkdocs with versioning (#333)
* docs: restructuring & introduction of mkdocs with versioning provided by mike

* docs: ad repositories section to sophia examples

* docs: refactoring and consistent naming of æternity

* docs: hint for new file destination

* docs: revert capital letter

* docs: accept proposed changes

* docs: fix anchors in stdlib
2021-08-27 17:46:18 +03:00
Gaith Hallak 3029bf31cb Implement Set stdlib (#335)
* Implement Set stdlib

* Rename an argument of the function Set.fold

* Add docs for Set stdlib

* Correct the usage of articles in the docs

* Fix bug

* Fix the link to Set stdlib section

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
2021-08-07 10:08:45 +02:00
Radosław Rowicki 4896ad3b36 Add Option.force_msg (#328)
* Add Option.force_msg

* CHANGELOG update
2021-08-03 11:39:53 +02:00
Hans Svensson b20b9c5df5 Merge pull request #329 from callbay/fix_var_in_example
Fix var in example
2021-07-26 12:04:55 +02:00
Frank Feng d793660545 Fix var in example 2021-07-24 16:46:13 +08:00
Hans Svensson 4957d01e9e Merge pull request #327 from aeternity/fix_doc
Fix stdlib doc
2021-07-13 20:40:49 +02:00
Hans Svensson 9d76e6186a Fix stdlib doc 2021-07-13 20:01:54 +02:00
Radosław Rowicki ae3edac53e Prepare 6.0.2 (#326)
* Prepare 6.0.2

* Minor note
2021-07-06 17:31:35 +02:00
Ulf Norell acec32e744 Merge pull request #325 from aeternity/issue324
Fix #324: bug when compiling default init in the presence of child contracts
2021-07-05 10:33:26 +02:00
Hans Svensson 5784f074a6 Merge pull request #323 from aeternity/fromtostep
Add check in from_to_step
2021-07-05 09:39:32 +02:00
Ulf Norell d07b321b25 Fix #324: bug when compiling default init in the presence of child contracts 2021-07-05 09:29:43 +02:00
radrow 2e6c01cb75 Fix var 2021-06-26 19:10:49 +02:00
radrow b22eeffc3d Formatting in stdlib doc 2021-06-26 19:10:15 +02:00
radrow b366bed24b Add check in from_to_step 2021-06-25 11:19:19 +02:00
Hans Svensson 1975ccf804 Merge pull request #322 from aeternity/prepare_6.0.1
Prepare 6.0.1
2021-06-24 09:38:05 +02:00
Hans Svensson 4f68729631 Prepare v6.0.1 2021-06-24 09:34:05 +02:00
Denis Davidyuk 10c845d3cf Use consistent event definitions between examples 2021-06-24 09:34:05 +02:00
Hans Svensson 393d7710c1 Merge pull request #318 from aeternity/stdlib-tests
Fix Frac library and add stdlib compilation test
2021-06-24 09:29:12 +02:00
Hans Svensson 37e5a92b2e Merge pull request #314 from aeternity/call-fee-doc
Call.fee doc
2021-06-24 09:28:17 +02:00
Hans Svensson cb9c9df103 Merge pull request #321 from aeternity/calldata-fix
Fix calldata
2021-06-24 09:19:05 +02:00
radrow c09313a92c newline 2021-06-22 17:55:31 +02:00
radrow 75b2d6981f Fix calldata 2021-06-22 17:54:05 +02:00
Hans Svensson 78d94786b6 Merge pull request #320 from aeternity/nikita-fuchs-patch-1
fix child contract deployment example
2021-06-22 14:52:36 +02:00
Nikita Fuchs 216f7f8a25 fix child contract deployment example 2021-06-22 14:47:58 +02:00
radrow 254172e3a3 Properly refer the stdlib 2021-06-02 17:06:36 +02:00
radrow eadb4e8c83 Fix Frac library and add stdlib compilation test 2021-06-02 17:03:23 +02:00
Radosław Rowicki e2af89287d 6.0.0 (#317)
* Prepare 6.0.0

* Update docs

* Prepare 6.0.0 one more time

* the
2021-05-26 13:06:56 +02:00
radrow 3996b6a711 Call.fee doc 2021-05-25 10:53:40 +02:00
Radosław Rowicki e8b32a6875 Call.fee (#313)
* Call.fee

* nice align
2021-05-24 14:30:57 +02:00
Radosław Rowicki cca7bdff49 Prepare 5.1.0 (#312) 2021-05-24 11:01:59 +02:00
Radosław Rowicki 1d9f59fec3 Contract factories and bytecode introspection (#305)
* Support for CREATE, CLONE and BYTECODE_HASH

* Add missing files

* Pushed the clone example through the typechecker

* CLONE compiles

* Fix dependent type in CLONE

* Bytecode hash fixes

* Refactor

* Refactor 2

* move some logic away

* Fixed some error messages. Type inference of child contract still does some random shit\n(mistakes arguments with result type)

* CREATE sometimes compiles and sometimes not

* Fix some scoping/constraint issues

* works, needs cleanup

* cleanup

* Fix some tests. Remove optimization of singleton tuples

* Fix default argument for clone

* Cleanup

* CHANGELOG

* Mention void type

* Address review, fix some dialyzer errors

* Please dialyzer

* Fix failing tests

* Write negative tests

* Docs

* TOC

* missing 'the'

* missing 'the'

* missing 'the'

* missing 'the'

* mention pre-fund

* format

* pre-fund clarification

* format

* Grammar in docs
2021-05-18 12:21:57 +02:00
Hans Svensson d82b42518e Merge pull request #311 from aeternity/docs_fixing
Add missing Auth.tx in stdlib documentation
2021-05-10 16:00:32 +02:00
Hans Svensson 00a3a51d0d Add missing Auth.tx 2021-05-10 09:54:57 +02:00
Hans Svensson 6858329faa Merge pull request #309 from aeternity/prepare_5.0.0
Preparing v5.0.0
2021-04-30 15:17:48 +02:00
Hans Svensson c2a3e333c7 Preparing v5.0.0 2021-04-30 14:48:37 +02:00
Hans Svensson 4787830861 Merge pull request #308 from aeternity/merge_lima_to_master
Merge lima to master
2021-04-30 14:43:16 +02:00
Hans Svensson a0111066e7 Merge branch 'lima' into merge_lima_to_master 2021-04-30 14:07:06 +02:00
Hans Svensson 2311d19602 Merge pull request #291 from aeternity/GH-3282-aens_pointers_cleanup
Add note about legacy (Lima) AENS pointers in update/lookup
2021-04-15 10:26:56 +02:00
Hans Svensson 2a78189f31 Add note about legacy (Lima) AENS pointers in update/lookup 2021-01-07 12:14:04 +01:00
Radosław Rowicki 4504fb8dcf Merge pull request #286 from aeternity/expose-interface-fix
Fix interface exposure (master)
2020-10-21 14:00:48 +02:00
radrow 8798e0b2c9 Fix interface exposure 2020-10-21 12:40:13 +02:00
Radosław Rowicki 1dfc349065 Merge pull request #285 from aeternity/lima-master-merge
Lima master merge
2020-10-15 19:50:48 +02:00
radrow 1266d9ea99 Fix one test2 2020-10-13 10:42:38 +02:00
radrow bbb049cb2e Merge branch 'lima' into lima-master-merge 2020-10-13 10:33:30 +02:00
radrow 787551b8bc Fix one test 2020-10-13 10:29:14 +02:00
radrow ac673602b9 Merge branch 'lima' into master 2020-10-13 10:22:05 +02:00
Hans Svensson 079b3a45c9 Merge pull request #283 from aeternity/GH-3002-blockhash_at_current_height
Document Chain.block_hash at current height
2020-10-08 10:48:41 +02:00
Hans Svensson c0d9759e60 Whitespace fixes 2020-10-07 16:53:24 +02:00
Hans Svensson b7b242bc66 Document the (changed) behavior of Chain.block_hash 2020-10-07 16:53:10 +02:00
Hans Svensson 05b87fe200 Merge pull request #259 from kryptokrauts/master
fix param description for AENS interface
2020-05-07 16:07:10 +02:00
Marco Walz cc07e3a638 fix param description for AENS interface 2020-05-05 11:21:27 +02:00
Radosław Rowicki ea5850cf93 Merge pull request #256 from radrow/master
Merge lima – 4.3.0
2020-04-30 13:52:50 +02:00
radrow d2dcb9e249 Add AENS example, readd delegation signature chapter, fix links 2020-04-30 00:02:26 +02:00
radrow adb3cf5406 Update documentation to master 2020-04-29 15:27:40 +02:00
radrow ad78f440d9 Merge lima 2020-04-02 15:32:26 +02:00
Ulf Norell 42cd47d1b3 Merge pull request #241 from aeternity/GH-203-protected-calls
Add support for protected contract calls
2020-03-02 16:15:05 +01:00
Ulf Norell 93d2086ddf aebytecode commit 2020-03-02 12:08:41 +01:00
Ulf Norell 9487b79f42 Fix dialyzer complaints 2020-03-02 12:08:41 +01:00
Ulf Norell e64ac9396a Test cases for protected calls 2020-03-02 12:08:41 +01:00
Ulf Norell 4a812b6f3b Error message tweaks 2020-03-02 11:35:55 +01:00
Ulf Norell fe2d93ea8a Compile protected calls to CALL_PGR 2020-03-02 11:35:55 +01:00
Ulf Norell ecbc15db1b Add 'protected' named argument to remote calls
If protected = true, the return type is wrapped in an option() which comes back
None if the remote call fails for any reason.
2020-03-02 08:51:33 +01:00
Hans Svensson d0caee24d9 Merge pull request #240 from aeternity/GH-176-additional_documentation
Add some comments to String.aes
2020-02-28 11:16:38 +01:00
Hans Svensson 57eb77f2f8 Add some comments to String.aes 2020-02-28 11:07:02 +01:00
Hans Svensson 53ed60b498 Merge pull request #238 from aeternity/GH-176-more_string_functions
Change Char.to_lower/upper into String.to_lower/upper
2020-02-26 20:01:10 +01:00
Hans Svensson e49738c90c New aebytecode reference 2020-02-26 18:23:12 +01:00
Hans Svensson a38a365181 Change Char.to_lower/upper into String.to_lower/upper 2020-02-26 14:15:49 +01:00
Hans Svensson 0dddac3d86 Merge pull request #236 from aeternity/GH-176-more_string_functions
Gh 176 more string functions
2020-02-25 09:49:00 +01:00
Hans Svensson 3da694e798 New aebytecode commit hash 2020-02-25 09:02:18 +01:00
Hans Svensson e98edd4eef Handle UTF-8 in character literals
Also handle `\x{hhh..}` in strings... Character literals has to be a single character, not composite.

+ tests (and the corresponding fix to the char literal pretty printer)
2020-02-24 15:35:54 +01:00
Hans Svensson 2bad76314f More efficient implementations in String.aes 2020-02-24 15:34:23 +01:00
Hans Svensson b9acf24dca Make String.aes a stdlib + add more string functions
This means moving the FATE operations to StringInternal and adding to/from_list (and Char.to/from_int
+ Char.to_upper/lower).
2020-02-21 09:45:11 +01:00
Hans Svensson 6682b24156 Merge pull request #223 from aeternity/GH-202-Auth_tx_introspection
Gh 202 auth tx introspection
2020-02-11 15:48:46 +01:00
Hans Svensson b31be6227d Update aebytecode reference 2020-02-11 14:20:41 +01:00
Hans Svensson bbc8555331 Auth.tx implementation and types
The arities field of the Chain.base_tx constructor is 22 integers long... This isn't very pretty but
that is the design we've chosen.
2020-02-11 10:00:46 +01:00
Radosław Rowicki 13bc821211 Optimize stdlib (#215) 2020-02-07 19:51:12 +01:00
Hans Svensson 34c10e1518 Merge pull request #214 from aeternity/GH-188-AENS_lookup
Add AENS.lookup and Oracle.expiry
2020-02-03 14:37:55 +01:00
Hans Svensson bb79e7dd89 Update aebytecode reference 2020-02-03 14:26:13 +01:00
Hans Svensson c3426f0e65 Add AENS.lookup
Also move Pointee-constructors inside AENS namespace.
2020-02-03 12:52:00 +01:00
Hans Svensson db01e237c1 Add Oracle.expiry 2020-02-03 12:24:27 +01:00
Hans Svensson 760d2841d1 From Iris (with AENS.update) AENS.resolve is not Pure 2020-02-03 12:22:34 +01:00
Hans Svensson 43013ec920 Merge pull request #213 from aeternity/merge_lima_to_master
Merge lima to master
2020-01-15 15:54:41 +01:00
Hans Svensson d821de6381 Merge 'origin/lima' into 'origin/master' 2020-01-15 15:03:11 +01:00
Hans Svensson 282f743925 Merge pull request #209 from aeternity/merge_lima_to_master
Merge lima to master
2019-12-19 15:34:26 +01:00
Hans Svensson cf1072140e Merge 'origin/lima' into merge_lima_to_master 2019-12-19 15:09:48 +01:00
Hans Svensson 75797686ad Merge pull request #187 from aeternity/GH-2850-add_fancy_crypto_bls12_381
Add BLS12-381 operations (to FATE)
2019-11-29 15:27:38 +01:00
Tino Breddin ed9384c2af Merge pull request #185 from aeternity/tb-rebar-lock-check
Add CI check to verify rebar.lock
2019-11-27 17:28:31 +01:00
Hans Svensson 1c24a700dc Add BLS12-381 operations (to FATE) 2019-11-27 10:57:24 +01:00
Hans Svensson f2e9fbcc51 Merge pull request #186 from aeternity/merge_lima
Merge lima into master
2019-11-27 10:50:29 +01:00
Hans Svensson 2d49426fe0 Merge 'origin/lima' into master 2019-11-27 10:46:18 +01:00
Tino Breddin f5df2c1a5f Add CI workflow 2019-11-26 17:27:26 +01:00
Tino Breddin 04445e4dee Add CI check to verify rebar.lock 2019-11-26 17:24:02 +01:00
Ulf Norell eec70f03a5 Merge pull request #183 from aeternity/lima
Merge lima into master
2019-11-25 13:16:17 +01:00
skkw c2c8e297ae Merge pull request #178 from aeternity/PT-158904718-aens-update-sophia
changed type of client ttl to int option
2019-11-18 18:39:54 +01:00
skkw 5c5d3c60ef changed type of client ttl to int option 2019-11-18 16:25:47 +01:00
Ulf Norell 2a3274ba25 Merge pull request #175 from aeternity/GH-174-encode-decode-bits
Handle encoding/decoding bits
2019-11-18 12:12:45 +01:00
Ulf Norell 13b7bde44b Prefix format annotation for negative numbers 2019-11-18 11:55:04 +01:00
Ulf Norell baf527b5fa Handle encoding/decoding bits
Fixes GH-174
2019-11-18 11:35:08 +01:00
skkw 422baa5b65 Merge pull request #155 from aeternity/PT-158904718-aens-update-sophia
support for AENS.update call
2019-11-12 14:49:42 +01:00
skkw 126e04ae42 support for AENS.update call 2019-11-12 14:27:46 +01:00
240 changed files with 11963 additions and 9786 deletions
+17 -1
View File
@@ -3,11 +3,20 @@ version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder
- image: aeternity/builder:bionic-otp24
user: builder
working_directory: ~/aesophia
jobs:
verify_rebar_lock:
executor: aebuilder
steps:
- checkout
- run:
name: Ensure lock file is up-to-date
command: |
./rebar3 upgrade
git diff --quiet -- rebar.lock || (echo "rebar.lock is not up-to-date" && exit 1)
build:
executor: aebuilder
steps:
@@ -35,3 +44,10 @@ jobs:
- _build/default/rebar3_20.3.8_plt
- store_artifacts:
path: _build/test/logs
workflows:
version: 2
build_test:
jobs:
- build
- verify_rebar_lock
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+7
View File
@@ -0,0 +1,7 @@
import glob
import shutil
def pre_build(**kwargs):
for file in glob.glob('../docs/*.md'):
shutil.copy(file, 'docs')
shutil.copy('../CHANGELOG.md', 'docs')
+55
View File
@@ -0,0 +1,55 @@
site_name: æternity Sophia Language
plugins:
- search
- mkdocs-simple-hooks:
hooks:
on_pre_build: 'hook:pre_build'
repo_url: 'https://github.com/aeternity/aesophia'
edit_uri: ''
extra:
version:
provider: mike
theme:
favicon: favicon.png
name: material
custom_dir: overrides
language: en
palette:
- scheme: default
primary: pink
accent: pink
toggle:
icon: material/weather-night
name: Switch to dark mode
- scheme: slate
primary: pink
accent: pink
toggle:
icon: material/weather-sunny
name: Switch to light mode
features:
- content.tabs.link
- search.highlight
- search.share
- search.suggest
# Don't include MkDocs' JavaScript
include_search_page: false
search_index_only: true
markdown_extensions:
- admonition
- pymdownx.highlight
- pymdownx.superfences
- toc:
toc_depth: 3
nav:
- Introduction: index.md
- Syntax: sophia_syntax.md
- Features: sophia_features.md
- Standard library: sophia_stdlib.md
- Contract examples: sophia_examples.md
- Changelog: CHANGELOG.md
+8
View File
@@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block outdated %}
You're not viewing the latest version.
<a href="{{ '../' ~ base_url }}">
<strong>Click here to go to latest.</strong>
</a>
{% endblock %}
+21
View File
@@ -0,0 +1,21 @@
name: Publish development docs
on:
push:
branches: ['master']
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: 3.8
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: |
cd .docssite
mike deploy --push master
+22
View File
@@ -0,0 +1,22 @@
name: Publish release docs
on:
release:
types: [released]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: 3.8
- run: pip3 install -r .github/workflows/requirements.txt -U
- run: git config --global user.email "github-action@users.noreply.github.com"
- run: git config --global user.name "GitHub Action"
- run: echo "RELEASE_VERSION=${GITHUB_REF:10}" >> $GITHUB_ENV
- run: |
cd .docssite
mike deploy --push --update-aliases $RELEASE_VERSION latest
+5
View File
@@ -0,0 +1,5 @@
mkdocs==1.4.2
mkdocs-simple-hooks==0.1.5
mkdocs-material==9.0.9
mike==1.1.2
pygments==2.14.0
+3
View File
@@ -21,3 +21,6 @@ rebar3.crashdump
aesophia
.qcci
current_counterexample.eqc
test/contracts/test.aes
__pycache__
.docssite/docs/*.md
+186 -1
View File
@@ -6,8 +6,185 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Toplevel compile-time constants
```
namespace N =
let nc = 1
contract C =
let cc = 2
```
### Changed
### Removed
### Fixed
- Warning about unused include when there is no include.
## [7.1.0]
### Added
- Options to enable/disable certain optimizations.
- The ability to call a different instance of the current contract
```
contract Main =
entrypoint spend(x : int) : int = x
entrypoint f(c : Main) : int = c.spend(10)
```
- Return a mapping from variables to FATE registers in the compilation output.
- Hole expression.
### Changed
- Type definitions serialised to ACI as `typedefs` field instead of `type_defs` to increase compatibility.
- Check contracts and entrypoints modifiers when implementing interfaces.
- Contracts can no longer be used as namespaces.
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
### Fixed
- Typechecker crashes if Chain.create or Chain.clone are used without arguments.
## [7.0.1]
### Added
- Add CONTRIBUTING.md file.
### Changed
- Update Sophia syntax docs to include missing information about existing syntax.
### Fixed
- [404](https://github.com/aeternity/aesophia/issues/404) Contract polymorphism crashes on non-obvious child contract typing.
## [7.0.0]
### Added
- Added support for `EXIT` opcode via `exit : (string) => 'a` function (behaves same as `ABORT`, but consumes all gas).
- Compiler warnings for the following: shadowing, negative spends, division by zero, unused functions, unused includes, unused stateful annotations, unused variables, unused parameters, unused user-defined type, dead return value.
- The pipe operator |>
```
[1, 2, 3] |> List.first |> Option.is_some // Option.is_some(List.first([1, 2, 3]))
```
- Allow binary operators to be used as lambdas
```
function sum(l : list(int)) : int = foldl((+), 0, l)
function logical_and(x, y) = (&&)(x, y)
```
- Contract interfaces polymorphism
### Changed
- Error messages have been restructured (less newlines) to provide more unified errors. Also `pp_oneline/1` has been added.
- Ban empty record definitions (e.g. `record r = {}` would give an error).
### Removed
- Support for AEVM has been entirely wiped
## [6.1.0] - 2021-10-20
### Added
- `Bitwise` stdlib
- `Set` stdlib
- `Option.force_msg`
- Loading namespaces into the current scope (e.g. `using Pair`)
- Assign patterns to variables (e.g. `let x::(t = y::_) = [1, 2, 3, 4]` where `t == [2, 3, 4]`)
- Add builtin types (`AENS.name, AENS.pointee, Chain.ttl, Chain.base_tx, Chain.ga_meta_tx, Chain.paying_for_tx`) to
the calldata and result decoder
- Patterns guards
```
switch(x)
a::[] | a > 10 => 1
_ => 2
```
```
function
f(a::[]) | a > 10 = 1
f(_) = 2
```
### Changed
- Fixed the ACI renderer, it shouldn't drop the `stateful` modifier
## [6.0.2] 2021-07-05
### Changed
- `List.from_to_step` now forbids non-positive step (this change does
*not* alter the behavior of the previously deployed contracts)
- Fixed leaking state between contracts
## [6.0.1] 2021-06-24
### Changed
- Fixed a bug in calldata encoding for contracts containing multiple contracts
- Fixed a missing `include` in the `Frac` standard library
## [6.0.0] 2021-05-26
### Added
- Child contracts
- `Chain.clone`
- `Chain.create`
- `Chain.bytecode_hash`
- Minor support for variadic functions
- `void` type that represents an empty type
- `Call.fee` builtin
### Changed
- Contract interfaces must be now invocated by `contract interface` keywords
- `main` keyword to indicate the main contract in case there are child contracts around
- `List.sum` and `List.product` no longer use `List.foldl`
### Removed
## [5.0.0] 2021-04-30
### Added
- A new and improved [`String` standard library](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#string)
has been added. Use it by `include "String.aes"`. It includes functions for
turning strings into lists of characters for detailed manipulation. For
example:
```
include "String.aes"
contract C =
entrypoint filter_all_a(s: string) : string =
String.from_list(List.filter((c : char) => c != 'a', String.to_list(s)))
```
will return a list with all `a`'s removed.
There are also convenience functions `split`, `concat`, `to_upper`,
`to_lower`, etc.
All String functions in FATEv2 operate on unicode code points.
- Operations for pairing-based cryptography has been added the operations
are in the standard library [BLS12_381](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md#bls12_381).
With these operations it is possible to do Zero Knowledge-proofs, etc.
The operations are for the BLS12-381 curve (as the name suggests).
- Calls to functions in other contracts (i.e. _remote calls_) can now be
[`protected`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#protected-contract-calls).
If a contract call fails for any reason (for instance, the remote contract
crashes or runs out of gas, or the entrypoint doesn't exist or has the
wrong type) the parent call also fails. To make it possible to recover
from failures, contract calls takes a named argument `protected : bool`
(default `false`).
If `protected = true` the result of the contract call is wrapped in an
`option`, and `Some(value)` indicates a succesful execution and `None`
indicates that the contract call failed. Note: any gas consumed until
the failure is still charged, but all side effects in the remote
contract are rolled back on failure.
- A new chain operation [`AENS.update`](https://github.com/aeternity/aesophia/blob/master/docs/sophia.md#aens-interface)
is supported.
- New chain exploring operations `AENS.lookup` and `Oracle.expiry` to
look up an AENS record and the expiry of an Oracle respectively, are added.
- Transaction introspection (`Auth.tx`) has been added. When a Generalized
account is authorized, the authorization function needs access to the
transaction (and the transaction hash) for the wrapped transaction. The
transaction and the transaction hash is available `Auth.tx`, it is only
available during authentication if invoked by a normal contract call
it returns `None`. Example:
```
switch(Auth.tx)
None => abort("Not in Auth context")
Some(tx0) =>
switch(tx0.tx)
Chain.SpendTx(_, amount, _) => amount > 400
Chain.ContractCallTx(_, _) => true
_ => false
```
- A debug mode is a added to the compiler. Right now its only use is to
turn off hermetization.
### Changed
- The function `Chain.block_hash(height)` is now (in FATEv2) defined for
the current height - this used to be an error.
- Standard library: Sort is optimized to do `mergesort` and a `contains`
function is added.
- Improved type errors and explicit errors for some syntax errors (empty code
blocks, etc.).
- Compiler optimization: The ACI is generated alongside bytecode. This means
that multiple compiler passes can be avoided.
- Compiler optimization: Improved parsing (less stack used when transpiled).
- A bug where constraints were handled out of order fixed.
- Fixed calldata decoding for singleton records.
- Improved the documentation w.r.t. signatures, especially stressing the fact that
the network ID is a part of what is signed.
### Removed
## [4.3.0]
### Added
@@ -211,7 +388,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.1.0...HEAD
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0
[7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
[6.0.2]: https://github.com/aeternity/aesophia/compare/v6.0.1...v6.0.2
[6.0.1]: https://github.com/aeternity/aesophia/compare/v6.0.0...v6.0.1
[6.0.0]: https://github.com/aeternity/aesophia/compare/v5.0.0...v6.0.0
[5.0.0]: https://github.com/aeternity/aesophia/compare/v4.3.0...v5.0.0
[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0
[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0
[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0
+40
View File
@@ -0,0 +1,40 @@
# Contributing to Sophia
## Checklist For Creating New Pull Requests
The following points should be considered before creating a new PR to the Sophia compiler.
### Documentation
- The [Changelog](CHANGELOG.md) file should be updated for all PRs.
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
- If a PR introduces a new compiler option, the new option should be documented in the file
[aeso_compiler.md](docs/aeso_compiler.md).
### Tests
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the contract [all_syntax.aes](test/contracts/all_syntax.aes) should be updated to include the new syntax.
- If a PR fixes a bug, the code that replicates the bug should be added as a new passing test contract.
- If a PR introduces a new feature, add tests for both successful and failing usage of that feature. In order to run the entire compilation pipeline and to avoid erroring during intermediate steps, failing tests should not be mixed with the successful ones.
### Source Code
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the following code should be updated to handle the new syntax:
- The function `aeso_syntax_utils:fold/4` in the file [aeso_syntax_utils.erl](src/aeso_syntax_utils.erl).
- Any related pretty printing function in the file [aeso_pretty.erl](src/aeso_pretty.erl), depending on the type of the newly added syntax.
## Checklist For Creating a Release
- Update the version in the file [aesophia.app.src](src/aesophia.app.src).
- Update the version in the file [rebar.config](rebar.config).
- In the [Changelog](CHANGELOG.md):
- Update the `Unreleased` changes to be under the new version.
- Update the version at the bottom of the file.
- Commit and the changes and create a new PR (check the commit of [v6.1.0](https://github.com/aeternity/aesophia/commit/5ad5270e381f6e810d7b8b5cdc168d283e7a90bb) for reference).
- Create a release after merging the new PR to `master` branch.
- After releasing `aesophia`, refer to each of the following repositories and create new releases as well, using the new `aesophia` release:
- [aesophia_cli](https://github.com/aeternity/aesophia_cli)
- [aesophia_http](https://github.com/aeternity/aesophia_http)
- [aerepl](https://github.com/aeternity/aerepl)
+1 -1
View File
@@ -1,6 +1,6 @@
ISC License
Copyright (c) 2017, aeternity developers
Copyright (c) 2017, æternity developers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
+17 -11
View File
@@ -5,25 +5,31 @@ This is the __sophia__ compiler for the æternity system which compiles contract
The compiler is currently being used three places
- [The command line compiler](https://github.com/aeternity/aesophia_cli)
- [The HTTP compiler](https://github.com/aeternity/aesophia_http)
- In [Aeternity node](https://github.com/aeternity/aeternity) tests
- In [æternity node](https://github.com/aeternity/aeternity) tests
## Documentation
* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md).
* [Sophia Documentation](docs/sophia.md).
* [Sophia Standard Library](docs/sophia_stdlib.md).
* [Introduction](docs/index.md)
* [Syntax](docs/sophia_syntax.md)
* [Features](docs/sophia_features.md)
* [Standard library](docs/sophia_stdlib.md)
* [Contract examples](docs/sophia_examples.md)
* [Contributing](CONTRIBUTING.md)
Additionally you can check out the [contracts section](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) of the æternity blockchain specification.
## Versioning
`aesophia` has a version that is only loosely connected to the version of the
Aeternity node - in principle they will share the major version but not
minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc.
Versioning should follow the [semantic versioning](https://semver.org/spec/v2.0.0) guidelines. Id est, given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes
- MINOR version when you add functionality in a backwards compatible manner
- PATCH version when you make backwards compatible bug fixes
## Interface Modules
The basic modules for interfacing the compiler:
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](./docs/aeso_aci.md)
* [aeso_compiler: the Sophia compiler](docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](docs/aeso_aci.md)
+2 -2
View File
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
}
]
},
"type_defs": [
"typedefs": [
{
"name": "answers",
"typedef": {
@@ -138,7 +138,7 @@ be included inside another contract.
state =>
#{record =>
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
type_defs =>
typedefs =>
[#{name => <<"answers">>,
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
vars => []}]}}]}
+29 -12
View File
@@ -49,11 +49,37 @@ The **pp_** options all print to standard output the following:
`pp_typed_ast` - print the AST with type information at each node
`pp_icode` - print the internal code structure
`pp_assembler` - print the generated assembler code
`pp_bytecode` - print the bytecode instructions
The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers.
#### Options to control which compiler optimizations should run:
By default all optimizations are turned on, to disable an optimization, it should be
explicitly set to false and passed as a compiler option.
List of optimizations:
- optimize_inliner
- optimize_inline_local_functions
- optimize_bind_subexpressions
- optimize_let_floating
- optimize_simplifier
- optimize_drop_unused_lets
- optimize_push_consume
- optimize_one_shot_var
- optimize_write_to_dead_var
- optimize_inline_switch_target
- optimize_swap_push
- optimize_swap_pop
- optimize_swap_write
- optimize_constant_propagation
- optimize_prune_impossible_branches
- optimize_single_successful_branch
- optimize_inline_store
- optimize_float_switch_bod
#### check_call(ContractString, Options) -> CheckRet
@@ -66,15 +92,6 @@ Type = term()
```
Check a call in contract through the `__call` function.
#### sophia_type_to_typerep(String) -> TypeRep
Types
``` erlang
{ok,TypeRep} | {error, badtype}
```
Get the type representation of a type declaration.
#### version() -> {ok, Version} | {error, term()}
Types
+12
View File
@@ -0,0 +1,12 @@
# Introduction
Sophia is a functional language designed for smart contract development. It is strongly typed and has
restricted mutable state.
Sophia is customized for smart contracts, which can be published
to a blockchain. Thus some features of conventional
languages, such as floating point arithmetic, are not present in Sophia, and
some [æternity blockchain](https://aeternity.com) specific primitives, constructions and types have been added.
!!! Note
- For rapid prototyping of smart contracts check out [AEstudio](https://studio.aepps.com/)!
- For playing around and diving deeper into the language itself check out the [REPL](https://repl.aeternity.io/)!
+1 -1071
View File
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
# Contract examples
## Crowdfunding
```sophia
/*
* A simple crowd-funding example
*/
contract FundMe =
record spend_args = { recipient : address,
amount : int }
record state = { contributions : map(address, int),
total : int,
beneficiary : address,
deadline : int,
goal : int }
stateful function spend(args : spend_args) =
Chain.spend(args.recipient, args.amount)
entrypoint init(beneficiary, deadline, goal) : state =
{ contributions = {},
beneficiary = beneficiary,
deadline = deadline,
total = 0,
goal = goal }
function is_contributor(addr) =
Map.member(addr, state.contributions)
stateful entrypoint contribute() =
if(Chain.block_height >= state.deadline)
spend({ recipient = Call.caller, amount = Call.value }) // Refund money
false
else
let amount =
switch(Map.lookup(Call.caller, state.contributions))
None => Call.value
Some(n) => n + Call.value
put(state{ contributions[Call.caller] = amount,
total @ tot = tot + Call.value })
true
stateful entrypoint withdraw() =
if(Chain.block_height < state.deadline)
abort("Cannot withdraw before deadline")
if(Call.caller == state.beneficiary)
withdraw_beneficiary()
elif(is_contributor(Call.caller))
withdraw_contributor()
else
abort("Not a contributor or beneficiary")
stateful function withdraw_beneficiary() =
require(state.total >= state.goal, "Project was not funded")
spend({recipient = state.beneficiary,
amount = Contract.balance })
stateful function withdraw_contributor() =
if(state.total >= state.goal)
abort("Project was funded")
let to = Call.caller
spend({recipient = to,
amount = state.contributions[to]})
put(state{ contributions @ c = Map.delete(to, c) })
```
## Repositories
This is a list with repositories that include smart contracts written in Sophia:
- [aepp-sophia-examples](https://github.com/aeternity/aepp-sophia-examples)
- A repository that contains lots of different examples. The functionality of these examples is - to some extent - also covered by tests written in JavaScript.
File diff suppressed because it is too large Load Diff
+1803 -1179
View File
File diff suppressed because it is too large Load Diff
+288
View File
@@ -0,0 +1,288 @@
# Syntax
## Lexical syntax
### Comments
Single line comments start with `//` and block comments are enclosed in `/*`
and `*/` and can be nested.
### Keywords
```
contract include let switch type record datatype if elif else function
stateful payable true false mod public entrypoint private indexed namespace
interface main using as for hiding
```
### Tokens
- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter.
- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter.
- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`)
- `QCon = (Con\.)+Con` qualified constructor
- `TVar = 'Id` type variable (e.g `'a`, `'b`)
- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators
- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators
- `String` string literal enclosed in `"` with escape character `\`
- `Char` character literal enclosed in `'` with escape character `\`
- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix
- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix
- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix
- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix
Valid string escape codes are
| Escape | ASCII | |
|---------------|-------------|---|
| `\b` | 8 | |
| `\t` | 9 | |
| `\n` | 10 | |
| `\v` | 11 | |
| `\f` | 12 | |
| `\r` | 13 | |
| `\e` | 27 | |
| `\xHexDigits` | *HexDigits* | |
See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the
details on the base58 literals.
## Layout blocks
Sophia uses Python-style layout rules to group declarations and statements. A
layout block with more than one element must start on a separate line and be
indented more than the currently enclosing layout block. Blocks with a single
element can be written on the same line as the previous token.
Each element of the block must share the same indentation and no part of an
element may be indented less than the indentation of the block. For instance
```sophia
contract Layout =
function foo() = 0 // no layout
function bar() = // layout block starts on next line
let x = foo() // indented more than 2 spaces
x
+ 1 // the '+' is indented more than the 'x'
```
## Notation
In describing the syntax below, we use the following conventions:
- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with
some associated value (like `Id`).
- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`.
- Choices are separated by vertical bars: `|`.
- Optional elements are enclosed in `[` square brackets `]`.
- `(` Parentheses `)` are used for grouping.
- Zero or more repetitions are denoted by a postfix `*`, and one or more
repetitions by a `+`.
- `Block(X)` denotes a layout block of `X`s.
- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s
separated by `S`s.
- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty.
## Declarations
A Sophia file consists of a sequence of *declarations* in a layout block.
```c
File ::= Block(TopDecl)
TopDecl ::= ['payable'] ['main'] 'contract' Con [Implement] '=' Block(Decl)
| 'contract' 'interface' Con [Implement] '=' Block(Decl)
| 'namespace' Con '=' Block(Decl)
| '@compiler' PragmaOp Version
| 'include' String
| Using
Implement ::= ':' Sep1(Con, ',')
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
| 'record' Id ['(' TVar* ')'] '=' RecordType
| 'datatype' Id ['(' TVar* ')'] '=' DataType
| 'let' Id [':' Type] '=' Expr
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
| Using
FunDecl ::= Id ':' Type // Type signature
| Id Args [':' Type] '=' Block(Stmt) // Definition
| Id Args [':' Type] Block(GuardedDef) // Guarded definitions
GuardedDef ::= '|' Sep1(Expr, ',') '=' Block(Stmt)
Using ::= 'using' Con ['as' Con] [UsingParts]
UsingParts ::= 'for' '[' Sep1(Id, ',') ']'
| 'hiding' '[' Sep1(Id, ',') ']'
PragmaOp ::= '<' | '=<' | '==' | '>=' | '>'
Version ::= Sep1(Int, '.')
EModifier ::= 'payable' | 'stateful'
FModifier ::= 'stateful' | 'private'
Args ::= '(' Sep(Pattern, ',') ')'
```
Contract declarations must appear at the top-level.
For example,
```sophia
contract Test =
type t = int
entrypoint add (x : t, y : t) = x + y
```
There are three forms of type declarations: type aliases (declared with the
`type` keyword), record type definitions (`record`) and data type definitions
(`datatype`):
```c
TypeAlias ::= Type
RecordType ::= '{' Sep(FieldType, ',') '}'
DataType ::= Sep1(ConDecl, '|')
FieldType ::= Id ':' Type
ConDecl ::= Con ['(' Sep1(Type, ',') ')']
```
For example,
```sophia
record point('a) = {x : 'a, y : 'a}
datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a))
type int_shape = shape(int)
```
## Types
```c
Type ::= Domain '=>' Type // Function type
| Type '(' Sep(Type, ',') ')' // Type application
| '(' Type ')' // Parens
| 'unit' | Sep(Type, '*') // Tuples
| Id | QId | TVar
Domain ::= Type // Single argument
| '(' Sep(Type, ',') ')' // Multiple arguments
```
The function type arrow associates to the right.
Example,
```sophia
'a => list('a) => (int * list('a))
```
## Statements
Function bodies are blocks of *statements*, where a statement is one of the following
```c
Stmt ::= 'switch' '(' Expr ')' Block(Case)
| 'if' '(' Expr ')' Block(Stmt)
| 'elif' '(' Expr ')' Block(Stmt)
| 'else' Block(Stmt)
| 'let' LetDef
| Using
| Expr
LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition
| Pattern '=' Block(Stmt) // Value definition
Case ::= Pattern '=>' Block(Stmt)
| Pattern Block(GuardedCase)
GuardedCase ::= '|' Sep1(Expr, ',') '=>' Block(Stmt)
Pattern ::= Expr
```
`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example,
```sophia
let x : int = 4
switch(f(x))
None => 0
Some(y) =>
if(y > 10)
"too big"
elif(y < 3)
"too small"
else
"just right"
```
## Expressions
```c
Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1
| '(' BinOp ')' // Operator lambda (+)
| 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x
| Expr ':' Type // Type annotation 5 : int
| Expr BinOp Expr // Binary operator x + y
| UnOp Expr // Unary operator ! b
| Expr '(' Sep(Expr, ',') ')' // Application f(x, y)
| Expr '.' Id // Projection state.x
| Expr '[' Expr ']' // Map lookup map[key]
| Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y }
| '[' Sep(Expr, ',') ']' // List [1, 2, 3]
| '[' Expr '|' Sep(Generator, ',') ']'
// List comprehension [k | x <- [1], if (f(x)), let k = x+1]
| '[' Expr '..' Expr ']' // List range [1..n]
| '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val}
| '(' Expr ')' // Parens (1 + 2) * 3
| '(' Expr '=' Expr ')' // Assign pattern (y = x::_)
| Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
| AccountAddress | ContractAddress // Chain identifiers
| OracleAddress | OracleQueryId // Chain identifiers
| '???' // Hole expression 1 + ???
Generator ::= Pattern '<-' Expr // Generator
| 'if' '(' Expr ')' // Guard
| LetDef // Definition
LamArgs ::= '(' Sep(LamArg, ',') ')'
LamArg ::= Id [':' Type]
FieldUpdate ::= Path '=' Expr
Path ::= Id // Record field
| '[' Expr ']' // Map key
| Path '.' Id // Nested record field
| Path '[' Expr ']' // Nested map key
BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^'
| '|>'
UnOp ::= '-' | '!'
```
## Operators types
| Operators | Type
| --- | ---
| `-` `+` `*` `/` `mod` `^` | arithmetic operators
| `!` `&&` `||` | logical operators
| `==` `!=` `<` `>` `=<` `>=` | comparison operators
| `::` `++` | list operators
| `|>` | functional operators
## Operator precedence
In order of highest to lowest precedence.
| Operators | Associativity
| --- | ---
| `!` | right
| `^` | left
| `*` `/` `mod` | left
| `-` (unary) | right
| `+` `-` | left
| `::` `++` | right
| `<` `>` `=<` `>=` `==` `!=` | none
| `&&` | right
| `||` | right
| `|>` | left
+68
View File
@@ -0,0 +1,68 @@
namespace BLS12_381 =
type fr = MCL_BLS12_381.fr
type fp = MCL_BLS12_381.fp
record fp2 = { x1 : fp, x2 : fp }
record g1 = { x : fp, y : fp, z : fp }
record g2 = { x : fp2, y : fp2, z : fp2 }
record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp,
x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }
function pairing_check(us : list(g1), vs : list(g2)) =
switch((us, vs))
([], []) => true
(x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys)
function pairing_check_(acc : gt, us : list(g1), vs : list(g2)) =
switch((us, vs))
([], []) => gt_is_one(acc)
(x :: xs, y :: ys) =>
pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys)
function int_to_fr(x : int) = MCL_BLS12_381.int_to_fr(x)
function int_to_fp(x : int) = MCL_BLS12_381.int_to_fp(x)
function fr_to_int(x : fr) = MCL_BLS12_381.fr_to_int(x)
function fp_to_int(x : fp) = MCL_BLS12_381.fp_to_int(x)
function mk_g1(x : int, y : int, z : int) : g1 =
{ x = int_to_fp(x), y = int_to_fp(y), z = int_to_fp(z) }
function mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 =
{ x = {x1 = int_to_fp(x1), x2 = int_to_fp(x2)},
y = {x1 = int_to_fp(y1), x2 = int_to_fp(y2)},
z = {x1 = int_to_fp(z1), x2 = int_to_fp(z2)} }
function pack_g1(t) = switch(t)
(x, y, z) => {x = x, y = y, z = z} : g1
function pack_g2(t) = switch(t)
((x1, x2), (y1, y2), (z1, z2)) =>
{x = {x1 = x1, x2 = x2}, y = {x1 = y1, x2 = y2}, z = {x1 = z1, x2 = z2}} : g2
function pack_gt(t) = switch(t)
(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) =>
{x1 = x1, x2 = x2, x3 = x3, x4 = x4, x5 = x5, x6 = x6,
x7 = x7, x8 = x8, x9 = x9, x10 = x10, x11 = x11, x12 = x12} : gt
function g1_neg(p : g1) = pack_g1(MCL_BLS12_381.g1_neg((p.x, p.y, p.z)))
function g1_norm(p : g1) = pack_g1(MCL_BLS12_381.g1_norm((p.x, p.y, p.z)))
function g1_valid(p : g1) = MCL_BLS12_381.g1_valid((p.x, p.y, p.z))
function g1_is_zero(p : g1) = MCL_BLS12_381.g1_is_zero((p.x, p.y, p.z))
function g1_add(p : g1, q : g1) = pack_g1(MCL_BLS12_381.g1_add((p.x, p.y, p.z), (q.x, q.y, q.z)))
function g1_mul(k : fr, p : g1) = pack_g1(MCL_BLS12_381.g1_mul(k, (p.x, p.y, p.z)))
function g2_neg(p : g2) = pack_g2(MCL_BLS12_381.g2_neg(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
function g2_norm(p : g2) = pack_g2(MCL_BLS12_381.g2_norm(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
function g2_valid(p : g2) = MCL_BLS12_381.g2_valid(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))
function g2_is_zero(p : g2) = MCL_BLS12_381.g2_is_zero(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))
function g2_add(p : g2, q : g2) = pack_g2(MCL_BLS12_381.g2_add(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)),
((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
function g2_mul(k : fr, p : g2) = pack_g2(MCL_BLS12_381.g2_mul(k, ((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))))
function gt_inv(p : gt) = pack_gt(MCL_BLS12_381.gt_inv((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12)))
function gt_add(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_add((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12),
(q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12)))
function gt_mul(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_mul((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12),
(q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12)))
function gt_pow(p : gt, k : fr) = pack_gt(MCL_BLS12_381.gt_pow((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12), k))
function gt_is_one(p : gt) = MCL_BLS12_381.gt_is_one((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12))
function pairing(p : g1, q : g2) = pack_gt(MCL_BLS12_381.pairing((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
function miller_loop(p : g1, q : g2) = pack_gt(MCL_BLS12_381.miller_loop((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2))))
function final_exp(p : gt) = pack_gt(MCL_BLS12_381.final_exp((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12)))
+126
View File
@@ -0,0 +1,126 @@
@compiler >= 4.3
namespace Bitwise =
// bit shift 'x' right 'n' postions
function bsr(n : int, x : int) : int =
let step = 2^n
let res = x / step
if (x >= 0 || x mod step == 0)
res
else
res - 1
// bit shift 'x' left 'n' positions
function bsl(n : int, x : int) : int =
x * 2^n
// bit shift 'x' left 'n' positions, limit at 'lim' bits
function bsli(n : int, x : int, lim : int) : int =
(x * 2^n) mod (2^lim)
// bitwise 'and' for arbitrary precision integers
function band(a : int, b : int) : int =
if (a >= 0 && b >= 0)
uband_(a, b)
elif (b >= 0)
ubnand_(b, -1 - a)
elif (a >= 0)
ubnand_(a, -1 - b)
else
-1 - ubor_(-1 - a, -1 - b)
// bitwise 'or' for arbitrary precision integers
function
bor : (int, int) => int
bor(0, b) = b
bor(a, 0) = a
bor(a : int, b : int) : int =
if (a >= 0 && b >= 0)
ubor_(a, b)
elif (b >= 0)
-1 - ubnand_(-1 - a, b)
elif (a >= 0)
-1 - ubnand_(-1 - b, a)
else
-1 - uband_(-1 - a, -1 - b)
// bitwise 'xor' for arbitrary precision integers
function
bxor : (int, int) => int
bxor(0, b) = b
bxor(a, 0) = a
bxor(a, b) =
if (a >= 0 && b >= 0)
ubxor_(a, b)
elif (b >= 0)
-1 - ubxor_(-1 - a, b)
elif (a >= 0)
-1 - ubxor_(a, -1 - b)
else
ubxor_(-1 - a, -1 - b)
// bitwise 'not' for arbitrary precision integers
function bnot(a : int) = bxor(a, -1)
// Bitwise 'and' for non-negative integers
function uband(a : int, b : int) : int =
require(a >= 0 && b >= 0, "uband is only defined for non-negative integers")
switch((a, b))
(0, _) => 0
(_, 0) => 0
_ => uband__(a, b, 1, 0)
private function uband_(a, b) = uband__(a, b, 1, 0)
private function
uband__(0, b, val, acc) = acc
uband__(a, 0, val, acc) = acc
uband__(a, b, val, acc) =
switch (a mod 2 + b mod 2)
2 => uband__(a / 2, b / 2, val * 2, acc + val)
_ => uband__(a / 2, b / 2, val * 2, acc)
// Bitwise 'or' for non-negative integers
function ubor(a, b) =
require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers")
switch((a, b))
(0, _) => b
(_, 0) => a
_ => ubor__(a, b, 1, 0)
private function ubor_(a, b) = ubor__(a, b, 1, 0)
private function
ubor__(0, 0, val, acc) = acc
ubor__(a, b, val, acc) =
switch (a mod 2 + b mod 2)
0 => ubor__(a / 2, b / 2, val * 2, acc)
_ => ubor__(a / 2, b / 2, val * 2, acc + val)
//Bitwise 'xor' for non-negative integers
function
ubxor : (int, int) => int
ubxor(0, b) = b
ubxor(a, 0) = a
ubxor(a, b) =
require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers")
ubxor__(a, b, 1, 0)
private function ubxor_(a, b) = ubxor__(a, b, 1, 0)
private function
ubxor__(0, 0, val, acc) = acc
ubxor__(a, b, val, acc) =
switch(a mod 2 + b mod 2)
1 => ubxor__(a / 2, b / 2, val * 2, acc + val)
_ => ubxor__(a / 2, b / 2, val * 2, acc)
private function ubnand_(a, b) = ubnand__(a, b, 1, 0)
private function
ubnand__(0, b, val, acc) = acc
ubnand__(a, b, val, acc) =
switch((a mod 2, b mod 2))
(1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val)
_ => ubnand__(a / 2, b / 2, val * 2, acc)
+2
View File
@@ -1,3 +1,5 @@
include "String.aes"
namespace Frac =
private function gcd(a : int, b : int) =
+1 -1
View File
@@ -2,7 +2,7 @@ namespace Func =
function id(x : 'a) : 'a = x
function const(x : 'a) : 'b => 'a = (y) => x
function const(x : 'a) : 'b => 'a = (_) => x
function flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c = (b, a) => f(a, b)
+91 -82
View File
@@ -19,6 +19,16 @@ namespace List =
[x] => Some(x)
_::t => last(t)
function drop_last(l : list('a)) : option(list('a)) = switch(l)
[] => None
_ => Some(drop_last_unsafe(l))
function drop_last_unsafe(l : list('a)) : list('a) = switch(l)
[_] => []
h::t => h::drop_last_unsafe(t)
[] => abort("drop_last_unsafe: list empty")
function contains(e : 'a, l : list('a)) = switch(l)
[] => false
h::t => h == e || contains(e, t)
@@ -32,14 +42,15 @@ namespace List =
/** Returns list of all indices of elements from `l` that fulfill the predicate `p`.
*/
function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0, [])
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)
[] => []
h::t =>
let rest = find_indices_(p, t, n+1)
if(p(h)) n::rest else rest
function nth(n : int, l : list('a)) : option('a) =
switch(l)
@@ -68,44 +79,45 @@ namespace List =
* `a` and `b` jumping by given `step`. Includes `a` and takes
* `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0.
*/
function from_to_step(a : int, b : int, s : int) : list(int) = from_to_step_(a, b, s, [])
private function from_to_step_(a, b, s, acc) =
if (a > b) reverse(acc) else from_to_step_(a + s, b, s, a :: acc)
function from_to_step(a : int, b : int, s : int) : list(int) =
require(s > 0, "List.from_to_step: non-positive step")
from_to_step_(a, b - (b-a) mod s, s, [])
private function from_to_step_(a : int, b : int, s : int, acc : list(int)) : list(int) =
if(b < a) acc
else from_to_step_(a, b - s, s, b::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) =
if(n<0) abort("insert_at underflow") else replace_at_(n, e, l)
private function replace_at_(n : int, e : 'a, l : 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)
h::t => if (n == 0) e::t
else h::replace_at_(n-1, e, t)
/** 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
if(n<0) abort("insert_at underflow") else insert_at_(n, e, l)
private function insert_at_(n : int, e : 'a, l : list('a)) : list('a) =
if (n == 0) e::l
else switch(l)
[] => abort("insert_at overflow")
h::t => insert_at_(n-1, e, t, h::acc)
h::t => h::insert_at_(n-1, e, t)
/** Assuming that cmp represents `<` comparison, inserts `x` before
* the first element in the list `l` which is greater than it
*/
function insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) =
insert_by_(cmp, x, l, [])
private function insert_by_(cmp : (('a, 'a) => bool), x : 'a, l : list('a), acc : list('a)) : list('a) =
switch(l)
[] => reverse(x::acc)
[] => [x]
h::t =>
if(cmp(x, h)) // x < h
reverse(acc) ++ (x::l)
x::l
else
insert_by_(cmp, x, t, h::acc)
h::insert_by(cmp, x, t)
function foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b = switch(l)
@@ -123,49 +135,52 @@ namespace List =
f(e)
foreach(l', f)
function reverse(l : list('a)) : list('a) = foldl((lst, el) => el :: lst, [], l)
function reverse(l : list('a)) : list('a) = reverse_(l, [])
private function reverse_(l : list('a), acc : list('a)) : list('a) = switch(l)
[] => acc
h::t => reverse_(t, h::acc)
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 map(f : 'a => 'b, l : list('a)) : list('b) = switch(l)
[] => []
h::t => f(h)::map(f, t)
/** Effectively composition of `map` and `flatten`
*/
function flat_map(f : 'a => list('b), l : list('a)) : list('b) =
ListInternal.flat_map(f, l)
function filter(p : 'a => bool, l : list('a)) : list('a) = filter_(p, l, [])
private function filter_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
h::t => filter_(p, t, if(p(h)) h::acc else acc)
function filter(p : 'a => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t =>
let rest = filter(p, t)
if(p(h)) h::rest else rest
/** Take `n` first elements
/** Take up to `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)
if(n < 0) abort("Take negative number of elements") else take_(n, l)
private function take_(n : int, l : list('a)) : list('a) =
if(n == 0) []
else switch(l)
[] => reverse(acc)
h::t => take_(n-1, t, h::acc)
[] => []
h::t => h::take_(n-1, t)
/** Drop `n` first elements
/** Drop up to `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
if(n < 0) abort("Drop negative number of elements") else drop_(n, l)
private function drop_(n : int, l : list('a)) : list('a) =
if (n == 0) l
else switch(l)
[] => []
h::t => drop(n-1, t)
_::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)
function take_while(p : 'a => bool, l : list('a)) : list('a) = switch(l)
[] => []
h::t => if(p(h)) h::take_while(p, t) else []
/** Drop elements from `l` until `p` holds
*/
@@ -176,18 +191,15 @@ namespace List =
/** 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 partition(p : 'a => bool, lst : list('a)) : (list('a) * list('a)) = switch(lst)
[] => ([], [])
h::t =>
let (l, r) = partition(p, t)
if(p(h)) (h::l, r) else (l, h::r)
/** Flattens list of lists into a single list
*/
function flatten(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll)
function flatten(l : list(list('a))) : list('a) = switch(l)
[] => []
h::t => h ++ flatten(t)
function all(p : 'a => bool, l : list('a)) : bool = switch(l)
[] => true
@@ -197,34 +209,34 @@ namespace List =
[] => 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)
function sum(l : list(int)) : int = switch(l)
[] => 0
h::t => h + sum(t)
function product(l : list(int)) : int = switch(l)
[] => 1
h::t => h * sum(t)
/** Zips two list by applying bimapping function on respective elements.
* Drops longer tail.
* Drops the tail of the longer list.
*/
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
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)
(h1::t1, h2::t2) => f(h1, h2)::zip_with(f, t1, t2)
_ => []
/** Zips two lists into list of pairs. Drops longer tail.
/** Zips two lists into list of pairs.
* Drops the tail of the longer list.
*/
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)
function unzip(l : list('a * 'b)) : (list('a) * list('b)) = switch(l)
[] => ([], [])
(h1, h2)::t =>
let (t1, t2) = unzip(t)
(h1::t1, h2::t2)
/** Merges two sorted lists using `lt` comparator
@@ -291,17 +303,14 @@ namespace List =
/** Puts `delim` between every two members of the list
*/
function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, [])
private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l)
[] => reverse(acc)
[e] => reverse(e::acc)
h::t => intersperse_(delim, t, delim::h::acc)
function intersperse(delim : 'a, l : list('a)) : list('a) = switch(l)
[] => []
[e] => [e]
h::t => h::delim::intersperse(delim, t)
/** Effectively a zip with an infinite sequence of natural numbers
*/
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0, [])
private function enumerate_(l : list('a), n : int, acc : list(int * 'a)) : list(int * 'a) = switch(l)
[] => reverse(acc)
h::t => enumerate_(t, n + 1, (n, h)::acc)
function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0)
private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l)
[] => []
h::t => (n, h)::enumerate_(t, n + 1)
+2 -2
View File
@@ -2,8 +2,8 @@ namespace ListInternal =
// -- Flatmap ----------------------------------------------------------------
function flat_map(f : 'a => list('b), xs : list('a)) : list('b) =
switch(xs)
function flat_map(f : 'a => list('b), lst : list('a)) : list('b) =
switch(lst)
[] => []
x :: xs => f(x) ++ flat_map(f, xs)
+17 -13
View File
@@ -1,5 +1,3 @@
include "List.aes"
namespace Option =
function is_none(o : option('a)) : bool = switch(o)
@@ -26,6 +24,12 @@ namespace Option =
None => abort("Forced None value")
Some(x) => x
/** Assume it is `Some` with custom error message
*/
function force_msg(o : option('a), err : string) : 'a = switch(o)
None => abort(err)
Some(x) => x
function contains(e : 'a, o : option('a)) = o == Some(e)
function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o)
@@ -69,20 +73,21 @@ namespace Option =
/** Turns list of options into a list of elements that are under `Some`s.
* Safe.
*/
function filter_options(l : list(option('a))) : list('a) = filter_options_(l, [])
private function filter_options_(l : list (option('a)), acc : list('a)) : list('a) = switch(l)
[] => List.reverse(acc)
None::t => filter_options_(t, acc)
Some(x)::t => filter_options_(t, x::acc)
function filter_options(l : list(option('a))) : list('a) = switch(l)
[] => []
None::t => filter_options(t)
Some(x)::t => x::filter_options(t)
/** Just like `filter_options` but requires all elements to be `Some` and returns
* None if any of them is not
*/
function seq_options(l : list (option('a))) : option (list('a)) = seq_options_(l, [])
private function seq_options_(l : list (option('a)), acc : list('a)) : option(list('a)) = switch(l)
[] => Some(List.reverse(acc))
None::t => None
Some(x)::t => seq_options_(t, x::acc)
function seq_options(l : list (option('a))) : option (list('a)) = switch(l)
[] => Some([])
None::_ => None
Some(x)::t => switch(seq_options(t))
None => None
Some(st) => Some(x::st)
/** Choose `Some` out of two if possible
*/
@@ -95,4 +100,3 @@ namespace Option =
[] => None
None::t => choose_first(t)
Some(x)::_ => Some(x)
+51
View File
@@ -0,0 +1,51 @@
include "List.aes"
include "Option.aes"
include "Pair.aes"
namespace Set =
record set('a) = { to_map : map('a, unit) }
function new() : set('a) =
{ to_map = {} }
function member(e : 'a, s : set('a)) : bool =
Map.member(e, s.to_map)
function insert(e : 'a, s : set('a)) : set('a) =
{ to_map = s.to_map{[e] = ()} }
function delete(e : 'a, s : set('a)) : set('a) =
{ to_map = Map.delete(e, s.to_map) }
function size(s : set('a)) : int =
Map.size(s.to_map)
function to_list(s : set('a)) : list('a) =
List.map(Pair.fst, Map.to_list(s.to_map))
function from_list(l : list('a)) : set('a) =
{ to_map = Map.from_list(List.map((x) => (x, ()), l)) }
function filter(p : 'a => bool, s : set('a)) : set('a) =
from_list(List.filter(p, to_list(s)))
function fold(f : ('a, 'b) => 'b, acc : 'b, s : set('a)) : 'b =
List.foldr(f, acc, to_list(s))
function subtract(s1 : set('a), s2 : set('a)) : set('a) =
filter((x) => !member(x, s2), s1)
function intersection(s1 : set('a), s2 : set('a)) : set('a) =
filter((x) => member(x, s2), s1)
function intersection_list(sets : list(set('a))) : set('a) =
List.foldr(
intersection,
Option.default(new(), List.first(sets)),
Option.default([], List.tail(sets)))
function union(s1 : set('a), s2 : set('a)) : set('a) =
from_list(to_list(s1) ++ to_list(s2))
function union_list(sets : list(set('a))) : set('a) =
List.foldr(union, new(), sets)
+117
View File
@@ -0,0 +1,117 @@
include "List.aes"
namespace String =
// Computes the SHA3/Keccak hash of the string
function sha3(s : string) : hash = StringInternal.sha3(s)
// Computes the SHA256 hash of the string.
function sha256(s : string) : hash = StringInternal.sha256(s)
// Computes the Blake2B hash of the string.
function blake2b(s : string) : hash = StringInternal.blake2b(s)
// The length of a string - equivalent to List.lenght(to_list(s))
function length(s : string) : int = StringInternal.length(s)
// Concatenates `s1` and `s2`.
function concat(s1 : string, s2 : string) : string = StringInternal.concat(s1, s2)
// Concatenates a list of strings.
function
concats : (list(string)) => string
concats([]) = ""
concats(s :: ss) = List.foldl(StringInternal.concat, s, ss)
// Converts a `string` to a list of `char` - the code points are normalized, but
// composite characters are possibly converted to multiple `char`s.
function from_list(cs : list(char)) : string = StringInternal.from_list(cs)
// Converts a list of characters into a normalized UTF-8 string.
function to_list(s : string) : list(char) = StringInternal.to_list(s)
// Converts a string to lowercase.
function to_lower(s : string) = StringInternal.to_lower(s)
// Converts a string to uppercase.
function to_upper(s : string) = StringInternal.to_upper(s)
// Splits a string at (zero-based) index `ix`.
function split(i : int, s : string) : string * string =
let cs = StringInternal.to_list(s)
(StringInternal.from_list(List.take(i, cs)), StringInternal.from_list(List.drop(i, cs)))
// Returns the character/codepoint at (zero-based) index `ix`.
function at(ix : int, s : string) =
switch(List.drop(ix, StringInternal.to_list(s)))
[] => None
x :: _ => Some(x)
// Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring
// of `str` starting at position `ix`, otherwise returns `None`.
function contains(str : string, substr : string) : option(int) =
if(substr == "") Some(0)
else
contains_(0, StringInternal.to_list(str), StringInternal.to_list(substr))
// Splits `s` into tokens, `pat` is the divider of tokens.
function tokens(s : string, pat : string) =
require(pat != "", "String.tokens: empty pattern")
tokens_(StringInternal.to_list(pat), StringInternal.to_list(s), [])
// Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string
// into an integer. If the string doesn't contain a valid number `None` is returned.
function to_int(str : string) : option(int) =
let lst = StringInternal.to_list(str)
switch(is_prefix(['-'], lst))
None => to_int_pos(lst)
Some(s) => switch(to_int_pos(s))
None => None
Some(x) => Some(-x)
// Private helper functions below
private function to_int_pos(chs : list(char)) =
switch(is_prefix(['0', 'x'], chs))
None =>
to_int_(chs, ch_to_int_10, 0, 10)
Some(str) =>
to_int_(str, ch_to_int_16, 0, 16)
private function
tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))]
tokens_(pat, str, acc) =
switch(is_prefix(pat, str))
Some(str') =>
StringInternal.from_list(List.reverse(acc)) :: tokens_(pat, str', [])
None =>
let c :: cs = str
tokens_(pat, cs, c :: acc)
private function
contains_(_, [], _) = None
contains_(ix, str, substr) =
switch(is_prefix(substr, str))
None =>
let _ :: tailstr = str
contains_(ix + 1, tailstr, substr)
Some(_) =>
Some(ix)
private function
is_prefix([], ys) = Some(ys)
is_prefix(_, []) = None
is_prefix(x :: xs, y :: ys) =
if(x == y) is_prefix(xs, ys)
else None
private function
to_int_([], _, x, _) = Some(x)
to_int_(i :: is, value, x, b) =
switch(value(i))
None => None
Some(n) => to_int_(is, value, x * b + n, b)
private function ch_to_int_10(ch) =
let c = Char.to_int(ch)
if(c >= 48 && c =< 57) Some(c - 48)
else None
private function ch_to_int_16(ch) =
let c = Char.to_int(ch)
if(c >= 48 && c =< 57) Some(c - 48)
elif(c >= 65 && c =< 70) Some(c - 55)
elif(c >= 97 && c =< 102) Some(c - 87)
else None
+3 -4
View File
@@ -2,11 +2,10 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.2.0"}}}
, {getopt, "1.0.1"}
, {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
]}.
{dialyzer, [
@@ -15,7 +14,7 @@
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
]}.
{relx, [{release, {aesophia, "4.3.0"},
{relx, [{release, {aesophia, "7.1.0"},
[aesophia, aebytecode, getopt]},
{dev_mode, true},
+8 -5
View File
@@ -1,11 +1,11 @@
{"1.1.0",
{"1.2.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
{ref,"2a0a397afad6b45da52572170f718194018bf33c"}},
0},
{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"47aaa8f5434b365c50a35bfd1490340b19241991"}},
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
1},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
@@ -14,7 +14,7 @@
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
2},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
@@ -24,5 +24,8 @@
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]},
{pkg_hash_ext,[
{<<"eblake2">>, <<"3C4D300A91845B25D501929A26AC2E6F7157480846FAB2347A4C11AE52E08A99">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>}]}
].
BIN
View File
Binary file not shown.
+25 -14
View File
@@ -21,6 +21,8 @@
, json_encode_expr/1
, json_encode_type/1]).
-include("aeso_utils.hrl").
-type aci_type() :: json | string.
-type json() :: jsx:json_term().
-type json_text() :: binary().
@@ -68,9 +70,7 @@ do_contract_interface(Type, Contract, Options) when is_binary(Contract) ->
do_contract_interface(Type, ContractString, Options) ->
try
Ast = aeso_compiler:parse(ContractString, Options),
%% io:format("~p\n", [Ast]),
{TypedAst, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
%% io:format("~p\n", [TypedAst]),
{TypedAst, _, _} = aeso_ast_infer_types:infer(Ast, [dont_unfold | Options]),
from_typed_ast(Type, TypedAst)
catch
throw:{error, Errors} -> {error, Errors}
@@ -83,7 +83,7 @@ from_typed_ast(Type, TypedAst) ->
string -> do_render_aci_json(JArray)
end.
encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HEAD(Head) ->
C0 = #{name => encode_name(Name)},
Tdefs0 = [ encode_typedef(T) || T <- sort_decls(contract_types(Contract)) ],
@@ -91,7 +91,7 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
C1 = C0#{type_defs => Tdefs},
C1 = C0#{typedefs => Tdefs},
C2 = case Es of
[] -> C1;
@@ -107,11 +107,11 @@ encode_contract(Contract = {contract, _, {con, _, Name}, _}) ->
|| F <- sort_decls(contract_funcs(Contract)),
is_entrypoint(F) ],
#{contract => C3#{functions => Fdefs, payable => is_payable(Contract)}};
#{contract => C3#{kind => Head, functions => Fdefs, payable => is_payable(Contract)}};
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
#{namespace => #{name => encode_name(Name),
type_defs => Tdefs}}.
typedefs => Tdefs}}.
%% Encode a function definition. Currently we are only interested in
%% the interface and type.
@@ -232,15 +232,21 @@ do_render_aci_json(Json) ->
{ok, list_to_binary(string:join(DecodedContracts, "\n"))}.
decode_contract(#{contract := #{name := Name,
kind := Kind,
payable := Payable,
type_defs := Ts0,
typedefs := Ts0,
functions := Fs} = C}) ->
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
[ MkTDef(<<"event">>, maps:get(event, C)) || maps:is_key(event, C) ] ++ Ts0,
[payable(Payable), "contract ", io_lib:format("~s", [Name])," =\n",
[payable(Payable), case Kind of
contract_main -> "main contract ";
contract_child -> "contract ";
contract_interface -> "contract interface "
end,
io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts), decode_funcs(Fs)];
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
decode_contract(#{namespace := #{name := Name, typedefs := Ts}}) when Ts /= [] ->
["namespace ", io_lib:format("~s", [Name])," =\n",
decode_tdefs(Ts)];
decode_contract(_) -> [].
@@ -248,8 +254,8 @@ decode_contract(_) -> [].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
%% decode_func(#{name := init}) -> [];
decode_func(#{name := Name, payable := Payable, arguments := As, returns := T}) ->
[" ", payable(Payable), "entrypoint ", io_lib:format("~s", [Name]), " : ",
decode_func(#{name := Name, stateful:= Stateful, payable := Payable, arguments := As, returns := T}) ->
[" ", payable(Payable), stateful(Stateful), "entrypoint ", io_lib:format("~s", [Name]), " : ",
decode_args(As), " => ", decode_type(T), $\n].
decode_args(As) ->
@@ -330,12 +336,17 @@ decode_tvar(#{name := N}) -> io_lib:format("~s", [N]).
payable(true) -> "payable ";
payable(false) -> "".
stateful(true) -> "stateful ";
stateful(false) -> "".
%% #contract{Ann, Con, [Declarations]}.
contract_funcs({C, _, _, Decls}) when C == contract; C == namespace ->
contract_funcs({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
[ D || D <- Decls, is_fun(D)].
contract_types({C, _, _, Decls}) when C == contract; C == namespace ->
contract_types({namespace, _, _, Decls}) ->
[ D || D <- Decls, is_type(D) ];
contract_types({C, _, _, _, Decls}) when ?IS_CONTRACT_HEAD(C) ->
[ D || D <- Decls, is_type(D) ].
is_fun({letfun, _, _, _, _, _}) -> true;
-27
View File
@@ -1,27 +0,0 @@
-module(aeso_ast).
-export([int/2,
line/1,
pp/1,
pp_typed/1,
symbol/2,
symbol_name/1
]).
symbol(Line, Chars) -> {symbol, Line, Chars}.
int(Line, Int) -> {'Int', Line, Int}.
line({symbol, Line, _}) -> Line.
symbol_name({symbol, _, Name}) -> Name.
pp(Ast) ->
String = prettypr:format(aeso_pretty:decls(Ast, [])),
io:format("Ast:\n~s\n", [String]).
pp_typed(TypedAst) ->
%% io:format("Typed tree:\n~p\n",[TypedAst]),
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
io:format("Type ast:\n~s\n",[String]).
+809 -2059
View File
File diff suppressed because it is too large Load Diff
+355 -189
View File
@@ -12,6 +12,8 @@
-export([ast_to_fcode/2, format_fexpr/1]).
-export_type([fcode/0, fexpr/0, fun_def/0]).
-include("aeso_utils.hrl").
%% -- Type definitions -------------------------------------------------------
-type option() :: term().
@@ -36,7 +38,14 @@
bits_intersection | bits_union | bits_difference |
contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 |
crypto_sha3 | crypto_sha256 | crypto_blake2b |
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 |
mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid |
mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul |
mcl_bls12_381_g2_neg | mcl_bls12_381_g2_norm | mcl_bls12_381_g2_valid |
mcl_bls12_381_g2_is_zero | mcl_bls12_381_g2_add | mcl_bls12_381_g2_mul |
mcl_bls12_381_gt_inv | mcl_bls12_381_gt_add | mcl_bls12_381_gt_mul | mcl_bls12_381_gt_pow |
mcl_bls12_381_gt_is_one | mcl_bls12_381_pairing | mcl_bls12_381_miller_loop | mcl_bls12_381_final_exp |
mcl_bls12_381_int_to_fr | mcl_bls12_381_int_to_fp | mcl_bls12_381_fr_to_int | mcl_bls12_381_fp_to_int.
-type flit() :: {int, integer()}
| {string, binary()}
@@ -46,6 +55,7 @@
| {oracle_pubkey, binary()}
| {oracle_query_id, binary()}
| {bool, false | true}
| {contract_code, string()} %% for CREATE, by name
| {typerep, ftype()}.
-type fexpr() :: {lit, flit()}
@@ -86,7 +96,8 @@
| nil
| {'::', var_name(), var_name()}
| {con, arities(), tag(), [var_name()]}
| {tuple, [var_name()]}.
| {tuple, [var_name()]}
| {assign, var_name(), var_name()}.
-type ftype() :: integer
| boolean
@@ -129,24 +140,29 @@
-type type_env() :: #{ sophia_name() => type_def() }.
-type fun_env() :: #{ sophia_name() => {fun_name(), non_neg_integer()} }.
-type con_env() :: #{ sophia_name() => con_tag() }.
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none} }.
-type child_con_env() :: #{sophia_name() => fcode()}.
-type builtins() :: #{ sophia_name() => {builtin(), non_neg_integer() | none | variable} }.
-type context() :: {main_contract, string()}
-type context() :: {contract_def, string()}
| {namespace, string()}
| {abstract_contract, string()}.
-type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}.
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
event_type => aeso_syntax:typedef(),
builtins := builtins(),
options := [option()],
state_layout => state_layout(),
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() } }.
-type env() :: #{ type_env := type_env(),
fun_env := fun_env(),
con_env := con_env(),
child_con_env := child_con_env(),
event_type => aeso_syntax:typedef(),
builtins := builtins(),
options := [option()],
state_layout => state_layout(),
context => context(),
vars => [var_name()],
functions := #{ fun_name() => fun_def() },
consts := #{ var_name() => fexpr() },
saved_fresh_names => #{ var_name() => var_name() }
}.
-define(HASH_BYTES, 32).
@@ -154,58 +170,112 @@
%% Main entrypoint. Takes typed syntax produced by aeso_ast_infer_types:infer/1,2
%% and produces Fate intermediate code.
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> fcode().
-spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}.
ast_to_fcode(Code, Options) ->
init_fresh_names(Options),
{Env1, FCode1} = to_fcode(init_env(Options), Code),
FCode2 = optimize(FCode1, Options),
Env2 = Env1#{ child_con_env :=
maps:map(
fun (_, FC) -> optimize(FC, Options) end,
maps:get(child_con_env, Env1)
)},
Env3 =
case proplists:get_value(debug_info, Options, false) of
true -> Env2#{ saved_fresh_names => get(saved_fresh_names) };
false -> Env2
end,
clear_fresh_names(Options),
{Env3, FCode2}.
optimize(FCode1, Options) ->
Verbose = lists:member(pp_fcode, Options),
init_fresh_names(),
FCode1 = to_fcode(init_env(Options), Code),
[io:format("-- Before lambda lifting --\n~s\n\n", [format_fcode(FCode1)]) || Verbose],
FCode2 = optimize_fcode(FCode1),
FCode2 = optimize_fcode(FCode1, Options),
[ io:format("-- After optimization --\n~s\n\n", [format_fcode(FCode2)]) || Verbose, FCode2 /= FCode1 ],
FCode3 = lambda_lift(FCode2),
[ io:format("-- After lambda lifting --\n~s\n\n", [format_fcode(FCode3)]) || Verbose, FCode3 /= FCode2 ],
clear_fresh_names(),
FCode3.
%% -- Environment ------------------------------------------------------------
-spec init_env([option()]) -> env().
init_env(Options) ->
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
#{ type_env => init_type_env(),
fun_env => #{},
builtins => builtins(),
child_con_env => #{},
con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] },
["Some"] => #con_tag{ tag = 1, arities = [0, 1] },
["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] },
["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] }
["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] },
["AENS", "AccountPt"] => #con_tag{ tag = 0, arities = [1, 1, 1, 1] },
["AENS", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1] },
["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] },
["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] },
["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] },
["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] },
["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] },
["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities },
["Chain", "OracleRegisterTx"] => #con_tag{ tag = 1, arities = ChainTxArities },
["Chain", "OracleQueryTx"] => #con_tag{ tag = 2, arities = ChainTxArities },
["Chain", "OracleResponseTx"] => #con_tag{ tag = 3, arities = ChainTxArities },
["Chain", "OracleExtendTx"] => #con_tag{ tag = 4, arities = ChainTxArities },
["Chain", "NamePreclaimTx"] => #con_tag{ tag = 5, arities = ChainTxArities },
["Chain", "NameClaimTx"] => #con_tag{ tag = 6, arities = ChainTxArities },
["Chain", "NameUpdateTx"] => #con_tag{ tag = 7, arities = ChainTxArities },
["Chain", "NameRevokeTx"] => #con_tag{ tag = 8, arities = ChainTxArities },
["Chain", "NameTransferTx"] => #con_tag{ tag = 9, arities = ChainTxArities },
["Chain", "ChannelCreateTx"] => #con_tag{ tag = 10, arities = ChainTxArities },
["Chain", "ChannelDepositTx"] => #con_tag{ tag = 11, arities = ChainTxArities },
["Chain", "ChannelWithdrawTx"] => #con_tag{ tag = 12, arities = ChainTxArities },
["Chain", "ChannelForceProgressTx"] => #con_tag{ tag = 13, arities = ChainTxArities },
["Chain", "ChannelCloseMutualTx"] => #con_tag{ tag = 14, arities = ChainTxArities },
["Chain", "ChannelCloseSoloTx"] => #con_tag{ tag = 15, arities = ChainTxArities },
["Chain", "ChannelSlashTx"] => #con_tag{ tag = 16, arities = ChainTxArities },
["Chain", "ChannelSettleTx"] => #con_tag{ tag = 17, arities = ChainTxArities },
["Chain", "ChannelSnapshotSoloTx"] => #con_tag{ tag = 18, arities = ChainTxArities },
["Chain", "ContractCreateTx"] => #con_tag{ tag = 19, arities = ChainTxArities },
["Chain", "ContractCallTx"] => #con_tag{ tag = 20, arities = ChainTxArities },
["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities }
},
options => Options,
functions => #{} }.
functions => #{},
consts => #{}
}.
-spec builtins() -> builtins().
builtins() ->
MkName = fun(NS, Fun) ->
list_to_atom(string:to_lower(string:join(NS ++ [Fun], "_")))
end,
Scopes = [{[], [{"abort", 1}, {"require", 2}]},
Scopes = [{[], [{"abort", 1}, {"require", 2}, {"exit", 1}]},
{["Chain"], [{"spend", 2}, {"balance", 1}, {"block_hash", 1}, {"coinbase", none},
{"timestamp", none}, {"block_height", none}, {"difficulty", none},
{"gas_limit", none}]},
{"gas_limit", none}, {"bytecode_hash", 1}, {"create", variable}, {"clone", variable}]},
{["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]},
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none},
{["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"fee", none},
{"gas_left", 0}]},
{["Oracle"], [{"register", 4}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
{["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2},
{"respond", 4}, {"extend", 3}, {"get_answer", 2},
{"check", 1}, {"check_query", 2}]},
{["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4},
{"revoke", 3}]},
{"revoke", 3}, {"update", 6}, {"lookup", 1}]},
{["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2},
{"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]},
{["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3},
{"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2},
{"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["Auth"], [{"tx_hash", none}]},
{["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]},
{["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2},
{"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2},
{"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1},
{"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1},
{"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]},
{["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1},
{"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]},
{["Char"], [{"to_int", 1}, {"from_int", 1}]},
{["Auth"], [{"tx_hash", none}, {"tx", none}]},
{["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2},
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
{["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
@@ -224,20 +294,32 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}).
-spec init_type_env() -> type_env().
init_type_env() ->
#{ ["int"] => ?type(integer),
["bool"] => ?type(boolean),
["bits"] => ?type(bits),
["char"] => ?type(integer),
["string"] => ?type(string),
["address"] => ?type(address),
["hash"] => ?type(hash),
["signature"] => ?type(signature),
["oracle"] => ?type(Q, R, {oracle, Q, R}),
["oracle_query"] => ?type(_, _, oracle_query),
["list"] => ?type(T, {list, T}),
["map"] => ?type(K, V, {map, K, V}),
["option"] => ?type(T, {variant, [[], [T]]}),
["Chain", "ttl"] => ?type({variant, [[integer], [integer]]})
BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string],
[{bytes, 32}], [{bytes, 32}], [address, {bytes, 32}], [address],
[address, integer], [address, integer], [address],
[address], [address], [address], [address], [address],
[integer], [address, integer], []]},
#{ ["int"] => ?type(integer),
["bool"] => ?type(boolean),
["bits"] => ?type(bits),
["char"] => ?type(integer),
["string"] => ?type(string),
["address"] => ?type(address),
["hash"] => ?type(hash),
["signature"] => ?type(signature),
["oracle"] => ?type(Q, R, {oracle, Q, R}),
["oracle_query"] => ?type(_, _, oracle_query), %% TODO: not in Fate
["list"] => ?type(T, {list, T}),
["map"] => ?type(K, V, {map, K, V}),
["option"] => ?type(T, {variant, [[], [T]]}),
["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}),
["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}),
["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}),
["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}),
["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}),
["Chain", "base_tx"] => ?type(BaseTx),
["MCL_BLS12_381", "fr"] => ?type({bytes, 32}),
["MCL_BLS12_381", "fp"] => ?type({bytes, 48})
}.
is_no_code(Env) ->
@@ -251,31 +333,43 @@ get_option(Opt, Env, Default) ->
%% -- Compilation ------------------------------------------------------------
-spec to_fcode(env(), aeso_syntax:ast()) -> fcode().
to_fcode(Env, [{contract, Attrs, MainCon = {con, _, Main}, Decls}]) ->
#{ builtins := Builtins } = Env,
MainEnv = Env#{ context => {main_contract, Main},
builtins => Builtins#{[Main, "state"] => {get_state, none},
[Main, "put"] => {set_state, 1},
[Main, "Chain", "event"] => {chain_event, 1}} },
#{ functions := Funs } = Env1 =
decls_to_fcode(MainEnv, Decls),
StateType = lookup_type(Env1, [Main, "state"], [], {tuple, []}),
EventType = lookup_type(Env1, [Main, "event"], [], none),
StateLayout = state_layout(Env1),
Payable = proplists:get_value(payable, Attrs, false),
#{ contract_name => Main,
state_type => StateType,
state_layout => StateLayout,
event_type => EventType,
payable => Payable,
functions => add_init_function(Env1, MainCon, StateType,
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]) ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Con} }, Decls),
to_fcode(Env1, Code);
-spec to_fcode(env(), aeso_syntax:ast()) -> {env(), fcode()}.
to_fcode(Env, [{Contract, Attrs, {con, _, Name}, _Impls, Decls}|Rest])
when ?IS_CONTRACT_HEAD(Contract) ->
case Contract =:= contract_interface of
false ->
#{ builtins := Builtins } = Env,
ConEnv = maps:remove(state_layout,
Env#{ context => {contract_def, Name},
builtins => Builtins#{[Name, "state"] => {get_state, none},
[Name, "put"] => {set_state, 1},
[Name, "Chain", "event"] => {chain_event, 1}} }),
#{ functions := PrevFuns } = ConEnv,
#{ functions := Funs } = Env1 =
decls_to_fcode(ConEnv, Decls),
StateType = lookup_type(Env1, [Name, "state"], [], {tuple, []}),
EventType = lookup_type(Env1, [Name, "event"], [], none),
StateLayout = state_layout(Env1),
Payable = proplists:get_value(payable, Attrs, false),
ConFcode = #{ contract_name => Name,
state_type => StateType,
state_layout => StateLayout,
event_type => EventType,
payable => Payable,
functions => add_init_function(
Env1,
add_event_function(Env1, EventType, Funs)) },
case Contract of
contract_main -> [] = Rest, {Env1, ConFcode};
contract_child ->
Env2 = add_child_con(Env1, Name, ConFcode),
Env3 = Env2#{ functions := PrevFuns },
to_fcode(Env3, Rest)
end;
true ->
Env1 = decls_to_fcode(Env#{ context => {abstract_contract, Name} }, Decls),
to_fcode(Env1, Rest)
end;
to_fcode(Env, [{namespace, _, {con, _, Con}, Decls} | Code]) ->
Env1 = decls_to_fcode(Env#{ context => {namespace, Con} }, Decls),
to_fcode(Env1, Code).
@@ -285,38 +379,32 @@ decls_to_fcode(Env, Decls) ->
%% First compute mapping from Sophia names to fun_names and add it to the
%% environment.
Env1 = add_fun_env(Env, Decls),
lists:foldl(fun(D, E) ->
R = decl_to_fcode(E, D),
R
lists:foldl(fun(D, E) -> decl_to_fcode(E, D)
end, Env1, Decls).
-spec decl_to_fcode(env(), aeso_syntax:decl()) -> env().
decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) ->
case is_no_code(Env) of
false -> fcode_error({missing_definition, Id});
true -> Env
end;
decl_to_fcode(Env, {fun_decl, _, _, _}) -> Env;
decl_to_fcode(Env, {type_def, _Ann, Name, Args, Def}) ->
typedef_to_fcode(Env, Name, Args, Def);
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, Id = {id, _, Name}, Args, Ret, Body}) ->
decl_to_fcode(Env = #{ functions := Funs }, {letfun, Ann, {id, _, Name}, Args, Ret, [{guarded, _, [], Body}]}) ->
Attrs = get_attributes(Ann),
FName = lookup_fun(Env, qname(Env, Name)),
FArgs = args_to_fcode(Env, Args),
FRet = type_to_fcode(Env, Ret),
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,
args => FArgs,
return => FRet,
body => FBody },
NewFuns = Funs#{ FName => Def },
Env#{ functions := NewFuns }.
Env#{ functions := NewFuns };
decl_to_fcode(Env = #{ consts := Consts }, {letval, _, {typed, _, {id, _, X}, _}, Val}) ->
FVal = expr_to_fcode(Env, Val),
NewConsts = Consts#{ qname(Env, X) => FVal },
Env#{ consts := NewConsts }.
-spec typedef_to_fcode(env(), aeso_syntax:id(), [aeso_syntax:tvar()], aeso_syntax:typedef()) -> env().
typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
check_state_and_event_types(Env, Id, Xs),
typedef_to_fcode(Env, {id, _, Name}, Xs, Def) ->
Q = qname(Env, Name),
FDef = fun(Args) when length(Args) == length(Xs) ->
Sub = maps:from_list(lists:zip([X || {tvar, _, X} <- Xs], Args)),
@@ -354,7 +442,7 @@ typedef_to_fcode(Env, Id = {id, _, Name}, Xs, Def) ->
Env3 = compute_state_layout(Env2, Name, FDef),
bind_type(Env3, Q, FDef).
compute_state_layout(Env = #{ context := {main_contract, _} }, "state", Type) ->
compute_state_layout(Env = #{ context := {contract_def, _} }, "state", Type) ->
NoLayout = get_option(no_flatten_state, Env),
Layout =
case Type([]) of
@@ -380,14 +468,6 @@ compute_state_layout(R, [H | T]) ->
compute_state_layout(R, _) ->
{R + 1, {reg, R}}.
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().
type_to_fcode(Env, Type) ->
type_to_fcode(Env, #{}, Type).
@@ -407,10 +487,14 @@ type_to_fcode(_Env, _Sub, {bytes_t, _, N}) ->
{bytes, N};
type_to_fcode(_Env, Sub, {tvar, _, X}) ->
maps:get(X, Sub, {tvar, X});
type_to_fcode(_Env, _Sub, {fun_t, Ann, _, var_args, _}) ->
fcode_error({var_args_not_set, {id, Ann, "a very suspicious function"}});
type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) ->
FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named],
FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args],
{function, FNamed ++ FArgs, type_to_fcode(Env, Sub, Res)};
type_to_fcode(Env, Sub, {if_t, _, _, _, Else}) ->
type_to_fcode(Env, Sub, Else); %% Hacky: this is only for remote calls, in which case we want the unprotected type
type_to_fcode(_Env, _Sub, Type) ->
error({todo, Type}).
@@ -462,7 +546,7 @@ expr_to_fcode(_Env, _Type, {bytes, _, B}) -> {lit, {bytes, B}};
%% Variables
expr_to_fcode(Env, _Type, {id, _, X}) -> resolve_var(Env, [X]);
expr_to_fcode(Env, Type, {qid, Ann, X}) ->
expr_to_fcode(Env, Type, {qid, _, X}) ->
case resolve_var(Env, X) of
{builtin_u, B, Ar} when B =:= oracle_query;
B =:= oracle_get_question;
@@ -473,13 +557,11 @@ expr_to_fcode(Env, Type, {qid, Ann, X}) ->
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} ->
@@ -570,8 +652,8 @@ expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {ty
Arg = fresh_name(),
Env1 = bind_var(Env, Arg),
Bind = {lam, [Arg], expr_to_fcode(Env1, {switch, As, {typed, As, {id, As, Arg}, PatType},
[{'case', As, Pat, {list_comp, As, Yield, Rest}},
{'case', As, {id, As, "_"}, {list, As, []}}]})},
[{'case', As, Pat, [{guarded, As, [], {list_comp, As, Yield, Rest}}]},
{'case', As, {id, As, "_"}, [{guarded, As, [], {list, As, []}}]}]})},
{def_u, FlatMap, _} = resolve_fun(Env, ["ListInternal", "flat_map"]),
{def, FlatMap, [Bind, expr_to_fcode(Env, BindExpr)]};
expr_to_fcode(Env, Type, {list_comp, As, Yield, [{comprehension_if, _, Cond}|Rest]}) ->
@@ -591,9 +673,9 @@ expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) ->
expr_to_fcode(Env, Else));
%% Switch
expr_to_fcode(Env, _, {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
expr_to_fcode(Env, _, S = {switch, _, Expr = {typed, _, E, Type}, Alts}) ->
Switch = fun(X) ->
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts)}
{switch, alts_to_fcode(Env, type_to_fcode(Env, Type), X, Alts, S)}
end,
case E of
{id, _, X} -> Switch(X);
@@ -611,8 +693,11 @@ expr_to_fcode(Env, _Type, {block, _, Stmts}) ->
expr_to_fcode(Env, _Type, Expr = {app, _, {Op, _}, [_, _]}) when Op == '&&'; Op == '||' ->
Tree = expr_to_decision_tree(Env, Expr),
decision_tree_to_fcode(Tree);
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
{op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]};
expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) ->
case Op of
'|>' -> expr_to_fcode(Env, Type, {app, Ann, B, [A]});
_ -> {op, Op, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}
end;
expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
case Op of
'-' -> {op, '-', [{lit, {int, 0}}, expr_to_fcode(Env, A)]};
@@ -620,14 +705,31 @@ expr_to_fcode(Env, _Type, {app, _Ann, {Op, _}, [A]}) when is_atom(Op) ->
end;
%% Function calls
expr_to_fcode(Env, _Type, {app, _, Fun = {typed, _, _, {fun_t, _, NamedArgsT, _, _}}, Args}) ->
expr_to_fcode(Env, _, {app, _, Fun = {typed, _, FunE, {fun_t, _, NamedArgsT, ArgsT, Type}}, Args}) ->
Args1 = get_named_args(NamedArgsT, Args),
FArgs = [expr_to_fcode(Env, Arg) || Arg <- Args1],
case expr_to_fcode(Env, Fun) of
{builtin_u, B, _Ar, TypeArgs} -> builtin_to_fcode(state_layout(Env), B, FArgs ++ TypeArgs);
{builtin_u, chain_clone, _Ar} ->
case ArgsT of
var_args -> fcode_error({var_args_not_set, FunE});
_ ->
%% Here we little cheat on the typechecker, but this inconsistency
%% is to be solved in `aeso_fcode_to_fate:type_to_scode/1`
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
builtin_to_fcode(state_layout(Env), chain_clone, [{lit, FInitArgsT}|FArgs])
end;
{builtin_u, chain_create, _Ar} ->
case {ArgsT, Type} of
{var_args, _} -> fcode_error({var_args_not_set, FunE});
{_, {con, _, Contract}} ->
FInitArgsT = aeb_fate_data:make_typerep([type_to_fcode(Env, T) || T <- ArgsT]),
builtin_to_fcode(state_layout(Env), chain_create, [{lit, {contract_code, Contract}}, {lit, FInitArgsT}|FArgs]);
{_, _} -> fcode_error({not_a_contract_type, Type})
end;
{builtin_u, B, _Ar} -> builtin_to_fcode(state_layout(Env), B, FArgs);
{def_u, F, _Ar} -> {def, F, FArgs};
{remote_u, ArgsT, RetT, Ct, RFun} -> {remote, ArgsT, RetT, Ct, RFun, FArgs};
{remote_u, RArgsT, RRetT, Ct, RFun} -> {remote, RArgsT, RRetT, Ct, RFun, FArgs};
FFun ->
%% FFun is a closure, with first component the function name and
%% second component the environment
@@ -689,6 +791,13 @@ make_if(Cond, Then, Else) ->
X = fresh_name(),
{'let', X, Cond, make_if({var, X}, Then, Else)}.
make_if_no_else({var, X}, Then) ->
{switch, {split, boolean, X,
[{'case', {bool, true}, {nosplit, Then}}]}};
make_if_no_else(Cond, Then) ->
X = fresh_name(),
{'let', X, Cond, make_if_no_else({var, X}, Then)}.
-spec make_tuple([fexpr()]) -> fexpr().
make_tuple([E]) -> E;
make_tuple(Es) -> {tuple, Es}.
@@ -705,58 +814,11 @@ get_oracle_type(oracle_check, {fun_t, _, _, [OType | _], _}) -> OType;
get_oracle_type(oracle_check_query, {fun_t, _, _, [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}})
|| {{typed, 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 --
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()]) -> fsplit().
alts_to_fcode(Env, Type, X, Alts) ->
FAlts = [alt_to_fcode(Env, Alt) || Alt <- Alts],
-spec alts_to_fcode(env(), ftype(), var_name(), [aeso_syntax:alt()], aeso_syntax:expr()) -> fsplit().
alts_to_fcode(Env, Type, X, Alts, Switch) ->
FAlts = remove_guards(Env, Alts, Switch),
split_tree(Env, [{X, Type}], FAlts).
%% Intermediate format before case trees (fcase() and fsplit()).
@@ -767,7 +829,43 @@ alts_to_fcode(Env, Type, X, Alts) ->
| {string, binary()}
| nil | {'::', fpat(), fpat()}
| {tuple, [fpat()]}
| {con, arities(), tag(), [fpat()]}.
| {con, arities(), tag(), [fpat()]}
| {assign, fpat(), fpat()}.
remove_guards(_Env, [], _Switch) ->
[];
remove_guards(Env, [Alt = {'case', _, _, [{guarded, _, [], _Expr}]} | Rest], Switch) ->
[alt_to_fcode(Env, Alt) | remove_guards(Env, Rest, Switch)];
remove_guards(Env, [{'case', AnnC, Pat, [{guarded, AnnG, [Guard | Guards], Body} | GuardedBodies]} | Rest], Switch = {switch, Ann, Expr, _}) ->
FPat = pat_to_fcode(Env, Pat),
FGuard = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Guard),
FBody = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Body),
case Guards of
[] ->
R = case GuardedBodies of
[] -> Rest;
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
end,
case R of
[] ->
[{'case', [FPat], make_if_no_else(FGuard, FBody)} | remove_guards(Env, Rest, Switch)];
_ ->
FSwitch = expr_to_fcode(Env, {switch, Ann, Expr, R}),
[{'case', [FPat], make_if(FGuard, FBody, FSwitch)} | remove_guards(Env, Rest, Switch)]
end;
_ ->
R1 = case GuardedBodies of
[] -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body}]} | Rest];
_ -> [{'case', AnnC, Pat, [{guarded, AnnG, Guards, Body} | GuardedBodies]} | Rest]
end,
R2 = case GuardedBodies of
[] -> Rest;
_ -> [{'case', AnnC, Pat, GuardedBodies} | Rest]
end,
FSwitch1 = expr_to_fcode(Env, {switch, Ann, Expr, R1}),
FSwitch2 = expr_to_fcode(Env, {switch, Ann, Expr, R2}),
[{'case', [FPat], make_if(FGuard, FSwitch1, FSwitch2)} | remove_guards(Env, Rest, Switch)]
end.
%% %% Invariant: the number of variables matches the number of patterns in each falt.
-spec split_tree(env(), [{var_name(), ftype()}], [falt()]) -> fsplit().
@@ -867,6 +965,8 @@ split_pat({'::', P, Q}) -> {{'::', fresh_name(), fresh_name()}, [P, Q]};
split_pat({con, As, I, Pats}) ->
Xs = [fresh_name() || _ <- Pats],
{{con, As, I, Xs}, Pats};
split_pat({assign, X = {var, _}, P}) ->
{{assign, fresh_name(), fresh_name()}, [X, P]};
split_pat({tuple, Pats}) ->
Xs = [fresh_name() || _ <- Pats],
{{tuple, Xs}, Pats}.
@@ -877,6 +977,7 @@ split_vars({int, _}, integer) -> [];
split_vars({string, _}, string) -> [];
split_vars(nil, {list, _}) -> [];
split_vars({'::', X, Xs}, {list, T}) -> [{X, T}, {Xs, {list, T}}];
split_vars({assign, X, P}, T) -> [{X, T}, {P, T}];
split_vars({con, _, I, Xs}, {variant, Cons}) ->
lists:zip(Xs, lists:nth(I + 1, Cons));
split_vars({tuple, Xs}, {tuple, Ts}) ->
@@ -892,7 +993,7 @@ next_split(Pats) ->
end.
-spec alt_to_fcode(env(), aeso_syntax:alt()) -> falt().
alt_to_fcode(Env, {'case', _, Pat, Expr}) ->
alt_to_fcode(Env, {'case', _, Pat, [{guarded, _, [], Expr}]}) ->
FPat = pat_to_fcode(Env, Pat),
FExpr = expr_to_fcode(bind_vars(Env, pat_vars(FPat)), Expr),
{'case', [FPat], FExpr}.
@@ -932,6 +1033,8 @@ pat_to_fcode(Env, {record_t, Fields}, {record, _, FieldPats}) ->
end end,
make_tuple([pat_to_fcode(Env, FieldPat(Field))
|| Field <- Fields]);
pat_to_fcode(Env, _Type, {letpat, _, Id = {typed, _, {id, _, _}, _}, Pattern}) ->
{assign, pat_to_fcode(Env, Id), pat_to_fcode(Env, Pattern)};
pat_to_fcode(_Env, Type, Pat) ->
error({todo, Pat, ':', Type}).
@@ -968,8 +1071,8 @@ decision_tree_to_fcode({'if', A, Then, Else}) ->
stmts_to_fcode(Env, [{letval, _, {typed, _, {id, _, X}, _}, Expr} | Stmts]) ->
{'let', X, expr_to_fcode(Env, Expr), stmts_to_fcode(bind_var(Env, X), Stmts)};
stmts_to_fcode(Env, [{letval, Ann, Pat, Expr} | Stmts]) ->
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, {block, Ann, Stmts}}]});
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, Expr} | Stmts]) ->
expr_to_fcode(Env, {switch, Ann, Expr, [{'case', Ann, Pat, [{guarded, Ann, [], {block, Ann, Stmts}}]}]});
stmts_to_fcode(Env, [{letfun, Ann, {id, _, X}, Args, _Type, [{guarded, _, [], Expr}]} | Stmts]) ->
LamArgs = [ case Arg of
{typed, Ann1, Id, T} -> {arg, Ann1, Id, T};
_ -> internal_error({bad_arg, Arg}) %% pattern matching has been desugared
@@ -985,12 +1088,21 @@ stmts_to_fcode(Env, [Expr | Stmts]) ->
op_builtins() ->
[map_from_list, map_to_list, map_delete, map_member, map_size,
string_length, string_concat, string_sha3, string_sha256, string_blake2b,
stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list,
stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b,
char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper,
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
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_ecverify_secp256k1, crypto_ecrecover_secp256k1
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1,
mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid,
mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul,
mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid,
mcl_bls12_381_g2_is_zero, mcl_bls12_381_g2_add, mcl_bls12_381_g2_mul,
mcl_bls12_381_gt_inv, mcl_bls12_381_gt_add, mcl_bls12_381_gt_mul, mcl_bls12_381_gt_pow,
mcl_bls12_381_gt_is_one, mcl_bls12_381_pairing, mcl_bls12_381_miller_loop, mcl_bls12_381_final_exp,
mcl_bls12_381_int_to_fr, mcl_bls12_381_int_to_fp, mcl_bls12_381_fr_to_int, mcl_bls12_381_fp_to_int
].
set_state({reg, R}, Val) ->
@@ -1034,11 +1146,11 @@ builtin_to_fcode(_Layout, Builtin, Args) ->
%% -- Init function --
add_init_function(Env, Main, StateType, Funs0) ->
add_init_function(Env, Funs0) ->
case is_no_code(Env) of
true -> Funs0;
false ->
Funs = add_default_init_function(Env, Main, StateType, Funs0),
Funs = add_default_init_function(Env, Funs0),
InitName = {entrypoint, <<"init">>},
InitFun = #{ body := InitBody} = maps:get(InitName, Funs),
Funs1 = Funs#{ InitName => InitFun#{ return => {tuple, []},
@@ -1046,16 +1158,14 @@ add_init_function(Env, Main, StateType, Funs0) ->
Funs1
end.
add_default_init_function(_Env, Main, StateType, Funs) ->
add_default_init_function(_Env, Funs) ->
InitName = {entrypoint, <<"init">>},
case maps:get(InitName, Funs, none) of
%% Only add default init function if state is unit.
none when StateType == {tuple, []} ->
none ->
Funs#{ InitName => #{attrs => [],
args => [],
return => {tuple, []},
body => {tuple, []}} };
none -> fcode_error({missing_init_function, Main});
_ -> Funs
end.
@@ -1151,8 +1261,8 @@ lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExp
lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) ->
FVs = free_vars(Ct),
Ct1 = lambda_lift_expr(Layout, Ct),
GasAndValueArgs = 2,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ],
NamedArgCount = 3,
Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ],
Args = [{var, X} || X <- Xs],
make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args});
lambda_lift_expr(Layout, Expr) ->
@@ -1189,20 +1299,28 @@ lambda_lift_exprs(Layout, As) -> [lambda_lift_expr(Layout, A) || A <- As].
%% - Constant propagation
%% - Inlining
-spec optimize_fcode(fcode()) -> fcode().
optimize_fcode(Code = #{ functions := Funs }) ->
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def) end, Funs) },
-spec optimize_fcode(fcode(), [option()]) -> fcode().
optimize_fcode(Code = #{ functions := Funs }, Options) ->
Code1 = Code#{ functions := maps:map(fun(Name, Def) -> optimize_fun(Code, Name, Def, Options) end, Funs) },
eliminate_dead_code(Code1).
-spec optimize_fun(fcode(), fun_name(), fun_def()) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body }) ->
%% io:format("Optimizing ~p =\n~s\n", [_Fun, prettypr:format(pp_fexpr(_Body))]),
Def#{ body := drop_unused_lets(
simplifier(
let_floating(
bind_subexpressions(
inline_local_functions(
inliner(Fcode, Fun, Body)))))) }.
-spec optimize_fun(fcode(), fun_name(), fun_def(), [option()]) -> fun_def().
optimize_fun(Fcode, Fun, Def = #{ body := Body0 }, Options) ->
Inliner = proplists:get_value(optimize_inliner, Options, true),
InlineLocalFunctions = proplists:get_value(optimize_inline_local_functions, Options, true),
BindSubexpressions = proplists:get_value(optimize_bind_subexpressions, Options, true),
LetFloating = proplists:get_value(optimize_let_floating, Options, true),
Simplifier = proplists:get_value(optimize_simplifier, Options, true),
DropUnusedLets = proplists:get_value(optimize_drop_unused_lets, Options, true),
Body1 = if Inliner -> inliner (Fcode, Fun, Body0); true -> Body0 end,
Body2 = if InlineLocalFunctions -> inline_local_functions(Body1); true -> Body1 end,
Body3 = if BindSubexpressions -> bind_subexpressions (Body2); true -> Body2 end,
Body4 = if LetFloating -> let_floating (Body3); true -> Body3 end,
Body5 = if Simplifier -> simplifier (Body4); true -> Body4 end,
Body6 = if DropUnusedLets -> drop_unused_lets (Body5); true -> Body5 end,
Def#{ body := Body6 }.
%% --- Inlining ---
@@ -1413,6 +1531,7 @@ match_pat(L, {lit, L}) -> [];
match_pat(nil, nil) -> [];
match_pat({'::', X, Y}, {op, '::', [A, B]}) -> [{X, A}, {Y, B}];
match_pat({var, X}, E) -> [{X, E}];
match_pat({assign, X, P}, E) -> [{X, E}, {P, E}];
match_pat(_, _) -> false.
constructor_form(Env, Expr) ->
@@ -1547,6 +1666,10 @@ bind_constructors(Env = #{ con_env := ConEnv }, NewCons) ->
%% -- Names --
-spec add_child_con(env(), sophia_name(), fcode()) -> env().
add_child_con(Env = #{child_con_env := CEnv}, Name, Fcode) ->
Env#{ child_con_env := CEnv#{Name => Fcode} }.
-spec add_fun_env(env(), [aeso_syntax:decl()]) -> env().
add_fun_env(Env = #{ context := {abstract_contract, _} }, _) -> Env; %% no functions from abstract contracts
add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
@@ -1561,7 +1684,7 @@ add_fun_env(Env = #{ fun_env := FunEnv }, Decls) ->
make_fun_name(#{ context := Context }, Ann, Name) ->
Entrypoint = proplists:get_value(entrypoint, Ann, false),
case Context of
{main_contract, Main} ->
{contract_def, Main} ->
if Entrypoint -> {entrypoint, list_to_binary(Name)};
true -> {local_fun, [Main, Name]}
end;
@@ -1573,7 +1696,7 @@ make_fun_name(#{ context := Context }, Ann, Name) ->
current_namespace(#{ context := Cxt }) ->
case Cxt of
{abstract_contract, Con} -> Con;
{main_contract, Con} -> Con;
{contract_def, Con} -> Con;
{namespace, NS} -> NS
end.
@@ -1605,9 +1728,23 @@ bind_var(Env = #{ vars := Vars }, X) -> Env#{ vars := [X | Vars] }.
resolve_var(#{ vars := Vars } = Env, [X]) ->
case lists:member(X, Vars) of
true -> {var, X};
false -> resolve_fun(Env, [X])
false ->
case resolve_const(Env, [X]) of
false -> resolve_fun(Env, [X]);
Const -> Const
end
end;
resolve_var(Env, Q) -> resolve_fun(Env, Q).
resolve_var(Env, Q) ->
case resolve_const(Env, Q) of
false -> resolve_fun(Env, Q);
Const -> Const
end.
resolve_const(#{ consts := Consts }, Q) ->
case maps:get(Q, Consts, not_found) of
not_found -> false;
Val -> Val
end.
resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
case {maps:get(Q, Funs, not_found), maps:get(Q, Builtin, not_found)} of
@@ -1617,12 +1754,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) ->
{{Fun, Ar}, _} -> {def_u, Fun, Ar}
end.
init_fresh_names() ->
init_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(),
put('%fresh', 0).
clear_fresh_names() ->
clear_fresh_names(Options) ->
proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(),
erase('%fresh').
init_saved_fresh_names() ->
put(saved_fresh_names, #{}).
clear_saved_fresh_names() ->
erase(saved_fresh_names).
-spec fresh_name_save(string()) -> var_name().
fresh_name_save(Name) ->
Fresh = fresh_name(),
case get(saved_fresh_names) of
undefined -> ok;
Old -> put(saved_fresh_names, Old#{Fresh => Name})
end,
Fresh.
-spec fresh_name() -> var_name().
fresh_name() -> fresh_name("%").
@@ -1644,6 +1798,7 @@ pat_vars(nil) -> [];
pat_vars({'::', P, Q}) -> pat_vars(P) ++ pat_vars(Q);
pat_vars({tuple, Ps}) -> pat_vars(Ps);
pat_vars({con, _, _, Ps}) -> pat_vars(Ps);
pat_vars({assign, X, P}) -> pat_vars(X) ++ pat_vars(P);
pat_vars(Ps) when is_list(Ps) -> [X || P <- Ps, X <- pat_vars(P)].
-spec fsplit_pat_vars(fsplit_pat()) -> [var_name()].
@@ -1750,7 +1905,7 @@ bottom_up(F, Env, Expr) ->
(_) -> true end,
case ShouldFreshen(X) of
true ->
Z = fresh_name(),
Z = fresh_name_save(X),
Env1 = Env#{ Z => E1 },
{'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))};
false ->
@@ -1864,7 +2019,11 @@ rename_spat(Ren, {con, Ar, C, Xs}) ->
{{con, Ar, C, Zs}, Ren1};
rename_spat(Ren, {tuple, Xs}) ->
{Zs, Ren1} = rename_bindings(Ren, Xs),
{{tuple, Zs}, Ren1}.
{{tuple, Zs}, Ren1};
rename_spat(Ren, {assign, X, P}) ->
{X1, Ren1} = rename_binding(Ren, X),
{P1, Ren2} = rename_binding(Ren1, P),
{{assign, X1, P1}, Ren2}.
rename_split(Ren, {split, Type, X, Cases}) ->
{split, Type, rename_var(Ren, X), [rename_case(Ren, C) || C <- Cases]};
@@ -1911,7 +2070,9 @@ setnth(I, X, Xs) ->
-dialyzer({nowarn_function, [fcode_error/1, internal_error/1]}).
fcode_error(Error) ->
aeso_errors:throw(aeso_code_errors:format(Error)).
Pos = aeso_errors:pos(0, 0),
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Error])),
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
internal_error(Error) ->
Msg = lists:flatten(io_lib:format("~p\n", [Error])),
@@ -1920,8 +2081,11 @@ internal_error(Error) ->
%% -- Pretty printing --------------------------------------------------------
format_fcode(#{ functions := Funs }) ->
prettypr:format(pp_above(
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ])).
prettypr:format(format_funs(Funs)).
format_funs(Funs) ->
pp_above(
[ pp_fun(Name, Def) || {Name, Def} <- maps:to_list(Funs) ]).
format_fexpr(E) ->
prettypr:format(pp_fexpr(E)).
@@ -2038,7 +2202,9 @@ pp_fexpr({set_state, R, A}) ->
pp_call(pp_text("set_state"), [{lit, {int, R}}, A]);
pp_fexpr({get_state, R}) ->
pp_call(pp_text("get_state"), [{lit, {int, R}}]);
pp_fexpr({switch, Split}) -> pp_split(Split).
pp_fexpr({switch, Split}) -> pp_split(Split);
pp_fexpr({contract_code, Contract}) ->
pp_beside(pp_text("contract "), pp_text(Contract)).
pp_call(Fun, Args) ->
pp_beside(Fun, pp_fexpr({tuple, Args})).
File diff suppressed because it is too large Load Diff
-684
View File
@@ -1,684 +0,0 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2018, Aeternity Anstalt
%%% @doc
%%% Compiler builtin functions for Aeterinty Sophia language.
%%% @end
%%% Created : 20 Dec 2018
%%%
%%%-------------------------------------------------------------------
-module(aeso_builtins).
-export([ builtin_function/1
, bytes_to_raw_string/2
, check_event_type/1
, used_builtins/1 ]).
-import(aeso_ast_to_icode, [prim_call/5]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
used_builtins([H|T]) ->
lists:umerge(used_builtins(H), used_builtins(T));
used_builtins(T) when is_tuple(T) ->
used_builtins(tuple_to_list(T));
used_builtins(M) when is_map(M) ->
used_builtins(maps:to_list(M));
used_builtins(_) -> [].
builtin_deps(Builtin) ->
lists:usort(builtin_deps1(Builtin)).
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
builtin_deps1(map_member) -> [{map_lookup, word}];
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_from_list) -> [map_put];
builtin_deps1(str_equal) -> [str_equal_p];
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
builtin_deps1(string_reverse) -> [string_reverse_];
builtin_deps1(require) -> [abort];
builtin_deps1(_) -> [].
dep_closure(Deps) ->
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
[] -> Deps;
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
end.
%% Helper functions/macros
v(X) when is_atom(X) -> v(atom_to_list(X));
v(X) when is_list(X) -> #var_ref{name = X}.
option_none() -> {tuple, [{integer, 0}]}.
option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(HASH_BYTES, 32).
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
-define(I(X), {integer, X}).
-define(V(X), v(X)).
-define(A(Op), aeb_opcodes:mnemonic(Op)).
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
-define(NXT(Ptr), op('+', Ptr, 32)).
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
-define(EQ(A, B), op('==', A, B)).
-define(LT(A, B), op('<', A, B)).
-define(GT(A, B), op('>', A, B)).
-define(ADD(A, B), op('+', A, B)).
-define(SUB(A, B), op('-', A, B)).
-define(MUL(A, B), op('*', A, B)).
-define(DIV(A, B), op('div', A, B)).
-define(MOD(A, B), op('mod', A, B)).
-define(EXP(A, B), op('^', A, B)).
-define(AND(A, B), op('&&', A, B)).
%% Bit shift operations takes their arguments backwards!?
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
%% We generate a lot of B * 8 for integer B from BSL and BSR.
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
{integer, A * B};
simpl(Op) -> Op.
operand(A) when is_atom(A) -> v(A);
operand(I) when is_integer(I) -> {integer, I};
operand(T) -> T.
check_event_type(Icode) ->
case maps:get(event_type, Icode) of
{variant_t, Cons} ->
check_event_type(Cons, Icode);
_ ->
error({event_should_be_variant_type})
end.
check_event_type(Evts, Icode) ->
[ check_event_type(Name, Ix, T, Icode)
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
check_event_type(EvtName, Ix, Type, Icode) ->
VMType =
try
aeso_ast_to_icode:ast_typerep(Type, Icode)
catch _:_ ->
error({EvtName, could_not_resolve_type, Type})
end,
case {Ix, VMType, Type} of
{indexed, word, _} -> ok;
{notindexed, string, _} -> ok;
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
end.
bfun(B, {IArgs, IExpr, IRet}) ->
{{builtin, B}, [private], IArgs, IExpr, IRet}.
builtin_function(BF) ->
case BF of
{event, EventT} -> bfun(BF, builtin_event(EventT));
abort -> bfun(BF, builtin_abort());
block_hash -> bfun(BF, builtin_block_hash());
require -> bfun(BF, builtin_require());
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
map_put -> bfun(BF, builtin_map_put());
map_delete -> bfun(BF, builtin_map_delete());
map_size -> bfun(BF, builtin_map_size());
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
map_member -> bfun(BF, builtin_map_member());
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
map_from_list -> bfun(BF, builtin_map_from_list());
list_concat -> bfun(BF, builtin_list_concat());
string_length -> bfun(BF, builtin_string_length());
string_concat -> bfun(BF, builtin_string_concat());
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
string_copy -> bfun(BF, builtin_string_copy());
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
str_equal_p -> bfun(BF, builtin_str_equal_p());
str_equal -> bfun(BF, builtin_str_equal());
popcount -> bfun(BF, builtin_popcount());
int_to_str -> bfun(BF, builtin_int_to_str());
addr_to_str -> bfun(BF, builtin_addr_to_str());
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
string_reverse -> bfun(BF, builtin_string_reverse());
string_reverse_ -> bfun(BF, builtin_string_reverse_())
end.
%% Event primitive (dependent on Event type)
%%
%% We need to switch on the event and prepare the correct #event for icode_to_asm
%% NOTE: we assume all errors are already checked!
builtin_event(EventT) ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([{{id, _, "string"}, V}]) ->
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
end,
Ix =
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
(_, V) -> V end,
Clause =
fun(_Tag, {con, _, Con}, IxTypes) ->
Types = [ T || {_Ix, T} <- IxTypes ],
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
EvtIndex = {integer, EvtIndexN},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
end,
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
{variant_t, Cons} = EventT,
Tags = lists:seq(0, length(Cons) - 1),
{[{"e", event}],
{switch, v(e),
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
{tuple, []}}.
%% Abort primitive.
builtin_abort() ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
{[{"s", string}],
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
A(?REVERT)]}, %% Stack: 0,Ptr
{tuple,[]}}.
builtin_block_hash() ->
{[{"height", word}],
?LET(hash, #prim_block_hash{ height = ?V(height)},
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
aeso_icode:option_typerep(word)}.
builtin_require() ->
{[{"c", word}, {"msg", string}],
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
{tuple, []}}.
%% Map primitives
builtin_map_lookup(Type) ->
Ret = aeso_icode:option_typerep(Type),
{[{"m", word}, {"k", word}],
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
[#var_ref{name = "m"}, #var_ref{name = "k"}],
[word, word], Ret),
Ret}.
builtin_map_put() ->
%% We don't need the types for put.
{[{"m", word}, {"k", word}, {"v", word}],
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
[v(m), v(k), v(v)], [word, word, word], word),
word}.
builtin_map_delete() ->
{[{"m", word}, {"k", word}],
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
[v(m), v(k)], [word, word], word),
word}.
builtin_map_size() ->
{[{"m", word}],
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
[v(m)], [word], word),
word}.
%% Map builtins
builtin_map_get(Type) ->
%% function map_get(m, k) =
%% switch(map_lookup(m, k))
%% Some(v) => v
{[{"m", word}, {"k", word}],
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
Type}.
builtin_map_lookup_default(Type) ->
%% function map_lookup_default(m, k, default) =
%% switch(map_lookup(m, k))
%% None => default
%% Some(v) => v
{[{"m", word}, {"k", word}, {"default", Type}],
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
[{option_none(), v(default)},
{option_some(v(v)), v(v)}]},
Type}.
builtin_map_member() ->
%% function map_member(m, k) : bool =
%% switch(Map.lookup(m, k))
%% None => false
%% _ => true
{[{"m", word}, {"k", word}],
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
[{option_none(), {integer, 0}},
{{var_ref, "_"}, {integer, 1}}]},
word}.
builtin_map_upd(Type) ->
%% function map_upd(map, key, fun) =
%% map_put(map, key, fun(map_get(map, key)))
{[{"map", word}, {"key", word}, {"valfun", word}],
?call(map_put,
[v(map), v(key),
#funcall{ function = v(valfun),
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
word}.
builtin_map_upd_default(Type) ->
%% function map_upd(map, key, val, fun) =
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
?call(map_put,
[v(map), v(key),
#funcall{ function = v(valfun),
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
word}.
builtin_map_from_list() ->
%% function map_from_list(xs, acc) =
%% switch(xs)
%% [] => acc
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
{switch, v(xs),
[{{list, []}, v(acc)},
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
?call(map_from_list,
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
word}.
%% list_concat
%%
%% Concatenates two lists.
builtin_list_concat() ->
{[{"l1", {list, word}}, {"l2", {list, word}}],
{switch, v(l1),
[{{list, []}, v(l2)},
{{binop, '::', v(hd), v(tl)},
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
]
},
word}.
builtin_string_length() ->
%% function length(str) =
%% switch(str)
%% {n} -> n // (ab)use the representation
{[{"s", string}],
?DEREF(n, s, ?V(n)),
word}.
%% str_concat - concatenate two strings
%%
%% Unless the second string is the empty string, a new string is created at the
%% top of the Heap and the address to it is returned. The tricky bit is when
%% the words from the second string has to be shifted to fit next to the first
%% string.
builtin_string_concat() ->
{[{"s1", string}, {"s2", string}],
?DEREF(n1, s1,
?DEREF(n2, s2,
{ifte, ?EQ(n1, 0),
?V(s2), %% First string is empty return second string
{ifte, ?EQ(n2, 0),
?V(s1), %% Second string is empty return first string
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
?V(ret) %% Put the actual return value
]})}
}
)),
word}.
builtin_string_concat_inner1() ->
%% Copy all whole words from the first string, and set up for word fusion
%% Special case when the length of the first string is divisible by 32.
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
?LET(nx, ?MOD(n1, 32),
{ifte, ?EQ(nx, 0),
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
})),
word}.
builtin_string_copy() ->
{[{"n", word}, {"p", pointer}],
?DEREF(w, p,
{ifte, ?GT(n, 31),
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
?V(w)
}),
word}.
builtin_string_shift_copy() ->
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
?DEREF(w, p,
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
{ifte, ?GT(n, ?SUB(32, off)),
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
{inline_asm, [?A(?MSIZE)]}}]
}),
word}.
builtin_str_equal_p() ->
%% function str_equal_p(n, p1, p2) =
%% if(n =< 0) true
%% else
%% let w1 = *p1
%% let w2 = *p2
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
{ifte, ?LT(n, 1),
?I(1),
?DEREF(w1, p1,
?DEREF(w2, p2,
?AND(?EQ(w1, w2),
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
word}.
builtin_str_equal() ->
%% function str_equal(s1, s2) =
%% let n1 = length(s1)
%% let n2 = length(s2)
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
{[{"s1", string}, {"s2", string}],
?DEREF(n1, s1,
?DEREF(n2, s2,
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
)),
word}.
%% Count the number of 1s in a bit field.
builtin_popcount() ->
%% function popcount(bits, acc) =
%% if (bits == 0) acc
%% else popcount(bits bsr 1, acc + bits band 1)
{[{"bits", word}, {"acc", word}],
{ifte, ?EQ(bits, 0),
?V(acc),
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
}, word}.
builtin_int_to_str() ->
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
builtin_baseX_tab(_X = 10) ->
{[{"ix", word}], ?ADD($0, ix), word};
builtin_baseX_tab(_X = 58) ->
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
{[{"ix", word}],
{ifte, ?LT(ix, 32),
?BYTE(ix, Fst32),
?BYTE(?SUB(ix, 32), Lst26)
},
word}.
builtin_baseX_int(X) ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
word}.
builtin_baseX_int_pad(X = 10) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?LT(src, 0),
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
word};
builtin_baseX_int_pad(X = 16) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
word};
builtin_baseX_int_pad(X = 58) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
word}.
builtin_baseX_int_encode(X) ->
{[{"src", word}, {"ix", word}, {"dst", word}],
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
word}.
builtin_baseX_int_encode_(X) ->
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
{ifte, ?EQ(fac, 0),
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
{ifte, ?EQ(ix, 32),
%% We've filled a word, write it and start on new word
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
?call({baseX_int_encode_, X},
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
?DIV(fac, X), ?ADD(ix, 1)])}
},
word}.
builtin_baseX_digits(X) ->
{[{"x0", word}, {"dgts", word}],
?LET(x1, ?DIV(x0, X),
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
word}.
builtin_bytes_to_int(32) ->
{[{"w", word}], ?V(w), word};
builtin_bytes_to_int(N) when N < 32 ->
{[{"w", word}], ?BSR(w, 32 - N), word};
builtin_bytes_to_int(N) when N > 32 ->
LastFullWord = N div 32 - 1,
Body = case N rem 32 of
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
R ->
?DEREF(hi, ?ADD(b, LastFullWord * 32),
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
end,
{[{"b", pointer}], Body, word}.
%% Two versions of this helper function, worker for sections not even 16 bytes long
%% and worker_x for the full sized chunks.
builtin_bytes_to_str_worker_x() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}],
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
},
word}.
builtin_bytes_to_str_worker() ->
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(b, ?BYTE(offs, w),
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
},
word}.
builtin_bytes_to_str_body(Var, N) when N < 16 ->
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
builtin_bytes_to_str_body(Var, 16) ->
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N < 32 ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
builtin_bytes_to_str_body(Var, 32) ->
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
builtin_bytes_to_str_body(Var, N) when N > 32 ->
WholeWords = ((N + 31) div 32) - 1,
lists:append(
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|| I <- lists:seq(0, WholeWords - 1) ]) ++
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
builtin_bytes_to_str(N) when N =< 32 ->
{[{"w", word}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(w, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string};
builtin_bytes_to_str(N) when N > 32 ->
{[{"p", pointer}],
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
builtin_bytes_to_str_body(p, N) ++
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
string}.
builtin_string_reverse() ->
{[{"s", string}],
?DEREF(n, s,
?LET(ret, {inline_asm, [?A(?MSIZE)]},
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
word}.
builtin_string_reverse_() ->
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
{ifte, ?LT(i2, 0),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
?DEREF(w, p1,
?LET(b, ?BYTE(?MOD(i2, 32), w),
{ifte, ?LT(i1, 0),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_reverse_,
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
?call(string_reverse_,
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
word}.
builtin_addr_to_str() ->
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
%% At most one word
%% | ..... | ========= | ........ |
%% Offs ^ ^- Len -^ TotalLen ^
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
%% Bytes are packed into a single word
Masked =
case Offs of
0 -> Bytes;
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
end,
Unpadded =
case 32 - (Offs + Len) of
0 -> Masked;
N -> ?BSR(Masked, N)
end,
case Len of
32 -> Unpadded;
_ -> ?BSL(Unpadded, 32 - Len)
end;
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
%% Might read one word more than necessary.
Word = op('!', Offs, Bytes),
case Len == 32 of
true -> Word;
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
end.
builtin_bytes_concat(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Words = fun(N) -> (N + 31) div 32 end,
WordsRes = Words(A + B),
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
(I) when 32 * I < A ->
Len = A rem 32,
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
?ADD(Hi, ?BSR(Lo, Len));
(I) ->
Offs = 32 * I - A,
Len = min(32, B - Offs),
bytes_slice(Offs, Len, B, ?V(b))
end,
Body =
case {A, B} of
{0, _} -> ?V(b);
{_, 0} -> ?V(a);
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
end,
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
builtin_bytes_split(A, B) ->
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
MkBytes = fun([W]) -> W;
(Ws) -> {tuple, Ws} end,
Word = fun(I, Max) ->
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
end,
Body =
case {A, B} of
{0, _} -> [?I(0), ?V(c)];
{_, 0} -> [?V(c), ?I(0)];
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
end,
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
bytes_to_raw_string(N, Term) when N =< 32 ->
{tuple, [?I(N), Term]};
bytes_to_raw_string(N, Term) when N > 32 ->
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
end,
Words = (N + 31) div 32,
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
-120
View File
@@ -1,120 +0,0 @@
%%%-------------------------------------------------------------------
%%% @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)).
+128 -346
View File
@@ -2,7 +2,7 @@
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Compiler from Aeterinty Sophia language to both AEVM and FATE VM.
%%% Compiler from Aeterinty Sophia language to FATE.
%%% @end
%%% Created : 12 Dec 2017
%%%-------------------------------------------------------------------
@@ -12,14 +12,13 @@
, file/2
, from_string/2
, check_call/4
, create_calldata/3 %% deprecated
, create_calldata/3
, create_calldata/4
, version/0
, numeric_version/0
, sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/4
, to_sophia_value/5
, decode_calldata/3 %% deprecated
, decode_calldata/3
, decode_calldata/4
, parse/2
, add_include_path/2
@@ -27,20 +26,17 @@
]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
-include("aeso_utils.hrl").
-type option() :: pp_sophia_code
| pp_ast
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| no_code
| keep_included
| debug_mode
| {backend, aevm | fate}
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}
@@ -105,41 +101,24 @@ add_include_path(File, Options) ->
end.
-spec from_string(binary() | string(), options()) -> {ok, map()} | {error, [aeso_errors:error()]}.
from_string(Contract, Options) ->
from_string(proplists:get_value(backend, Options, aevm), Contract, Options).
from_string(Backend, ContractBin, Options) when is_binary(ContractBin) ->
from_string(Backend, binary_to_list(ContractBin), Options);
from_string(Backend, ContractString, Options) ->
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) ->
try
from_string1(Backend, ContractString, Options)
from_string1(ContractString, Options)
catch
throw:{error, Errors} -> {error, Errors}
end.
from_string1(aevm, ContractString, Options) ->
#{ icode := Icode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
TypeInfo = extract_type_info(Icode),
Assembler = assemble(Icode, Options),
pp_assembler(aevm, Assembler, Options),
ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>,
pp_bytecode(ByteCode, Options),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
compiler_version => Version,
contract_source => ContractString,
type_info => TypeInfo,
abi_version => aeb_aevm_abi:abi_version(),
payable => maps:get(payable, Icode)
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)};
from_string1(fate, ContractString, Options) ->
from_string1(ContractString, Options) ->
#{ fcode := FCode
, folded_typed_ast := FoldedTypedAst } = string_to_code(ContractString, Options),
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
pp_assembler(fate, FateCode, Options),
, fcode_env := FCodeEnv
, folded_typed_ast := FoldedTypedAst
, warnings := Warnings } = string_to_code(ContractString, Options),
#{ child_con_env := ChildContracts } = FCodeEnv,
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
{FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
pp_assembler(FateCode, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = version(),
Res = #{byte_code => ByteCode,
@@ -148,9 +127,16 @@ from_string1(fate, ContractString, Options) ->
type_info => [],
fate_code => FateCode,
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
payable => maps:get(payable, FCode),
warnings => Warnings
},
{ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}.
ResDbg = Res#{variables_registers => VarsRegs},
FinalRes =
case proplists:get_value(debug_info, Options, false) of
true -> ResDbg;
false -> Res
end,
{ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}.
maybe_generate_aci(Result, FoldedTypedAst, Options) ->
case proplists:get_value(aci, Options) of
@@ -166,28 +152,18 @@ string_to_code(ContractString, Options) ->
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
{TypeEnv, FoldedTypedAst, UnfoldedTypedAst, Warnings} = aeso_ast_infer_types:infer(Ast, [return_env | Options]),
pp_typed_ast(UnfoldedTypedAst, Options),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = ast_to_icode(UnfoldedTypedAst, Options),
pp_icode(Icode, Options),
#{ icode => Icode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast };
fate ->
Fcode = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, Options),
#{ fcode => Fcode
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast }
end.
{Env, Fcode} = aeso_ast_to_fcode:ast_to_fcode(UnfoldedTypedAst, [{original_src, ContractString}|Options]),
#{ fcode => Fcode
, fcode_env => Env
, unfolded_typed_ast => UnfoldedTypedAst
, folded_typed_ast => FoldedTypedAst
, type_env => TypeEnv
, ast => Ast
, warnings => Warnings }.
-define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
%% Takes a string containing a contract with a declaration/prototype of a
%% function (foo, say) and adds function __call() = foo(args) calling this
@@ -195,10 +171,8 @@ string_to_code(ContractString, Options) ->
%% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]}
| {ok, string(), [term()]}
| {error, [aeso_errors:error()]}
when Type :: term().
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), [term()]}
| {error, [aeso_errors:error()]}.
check_call(Source, "init" = FunName, Args, Options) ->
case check_call1(Source, FunName, Args, Options) of
Err = {error, _} when Args == [] ->
@@ -215,43 +189,20 @@ check_call(Source, FunName, Args, Options) ->
check_call1(ContractString0, FunName, Args, Options) ->
try
case proplists:get_value(backend, Options, aevm) of
aevm ->
%% First check the contract without the __call function
#{ast := Ast} = string_to_code(ContractString0, Options),
ContractString = insert_call_function(Ast, ContractString0, ?CALL_NAME, FunName, Args),
#{unfolded_typed_ast := TypedAst,
icode := Icode} = string_to_code(ContractString, Options),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
RetVMType1 =
case FunName of
"init" -> {tuple, [typerep, RetVMType]};
_ -> RetVMType
end,
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
fate ->
%% First check the contract without the __call function
#{ fcode := OrgFcode
, ast := Ast } = string_to_code(ContractString0, Options),
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
end
%% First check the contract without the __call function
#{fcode := OrgFcode
, fcode_env := #{child_con_env := ChildContracts}
, ast := Ast} = string_to_code(ContractString0, Options),
{FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
%% collect all hashes and compute the first name without hash collision to
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
CallName = first_none_match(?CALL_NAME, SymbolHashes,
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
#{fcode := Fcode} = string_to_code(ContractString, Options),
CallArgs = arguments_of_body(CallName, FunName, Fcode),
{ok, FunName, CallArgs}
catch
throw:{error, Errors} -> {error, Errors}
end.
@@ -295,36 +246,25 @@ insert_init_function(Code, Options) ->
last_contract_indent(Decls) ->
case lists:last(Decls) of
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
{_, _, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
-spec to_sophia_value(string(), string(), ok | error | revert, binary()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, [aeso_errors:error()]}.
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, Options) ->
case proplists:get_value(backend, Options, aevm) of
aevm ->
case aeb_heap:from_binary(string, Data) of
{ok, Err} ->
{ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} ->
Msg = "Could not interpret the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
try aeb_fate_encoding:deserialize(Data) of
Err -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}}
catch _:_ ->
Msg = "Could not deserialize the revert message\n",
{error, [aeso_errors:new(data_error, Msg)]}
end
to_sophia_value(_, _, revert, Data, _Options) ->
try aeso_vm_decode:from_fate({id, [], "string"}, aeb_fate_encoding:deserialize(Data)) of
Err ->
{ok, {app, [], {id, [], "abort"}, [Err]}}
catch _:_ ->
Msg = "Could not deserialize the revert message",
{error, [aeso_errors:new(data_error, Msg)]}
end;
to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Options = [no_code | Options0],
@@ -332,76 +272,46 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
Code = string_to_code(ContractString, Options),
#{ unfolded_typed_ast := TypedAst, type_env := TypeEnv} = Code,
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Type = aeso_tc_type_unfolding:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary(VmType, Data) of
{ok, VmValue} ->
try
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[Data, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode binary as type ~p\n", [VmType]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
try
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
catch throw:cannot_translate_to_sophia ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
[aeb_fate_encoding:deserialize(Data), Type1]),
{error, [aeso_errors:new(data_error, Msg)]};
_:_ ->
Type1 = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
{error, [aeso_errors:new(data_error, Msg)]}
end
catch
throw:{error, Errors} -> {error, Errors}
end.
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
| {error, [aeso_errors:error()]}.
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args) ->
create_calldata(Code, Fun, Args, [{backend, aevm}]).
create_calldata(Code, Fun, Args, []).
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
{ok, binary()} | {error, [aeso_errors:error()]}.
create_calldata(Code, Fun, Args, Options0) ->
Options = [no_code | Options0],
case proplists:get_value(backend, Options, aevm) of
aevm ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end;
fate ->
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end
case check_call(Code, Fun, Args, Options) of
{ok, FunName, FateArgs} ->
aeb_fate_abi:create_calldata(FunName, FateArgs);
{error, _} = Err -> Err
end.
-spec decode_calldata(string(), string(), binary()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata) ->
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
decode_calldata(ContractString, FunName, Calldata, []).
-spec decode_calldata(string(), string(), binary(), options()) ->
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
| {error, [aeso_errors:error()]}.
decode_calldata(ContractString, FunName, Calldata, Options0) ->
Options = [no_code | Options0],
try
@@ -413,76 +323,30 @@ decode_calldata(ContractString, FunName, Calldata, Options0) ->
ArgTypes = lists:map(GetType, Args),
Type0 = {tuple_t, [], ArgTypes},
%% user defined data types such as variants needed to match against
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case proplists:get_value(backend, Options, aevm) of
aevm ->
Icode = maps:get(icode, Code),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
{ok, {_, VmValue}} ->
try
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
%% Values are Sophia expressions in AST format
{ok, ArgTypes, Values}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
[VmValue, VmType, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _Err} ->
Msg = io_lib:format("Failed to decode calldata as type ~p\n", [VmType]),
Type = aeso_tc_type_unfolding:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s",
[FateArgs, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
fate ->
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
{ok, FateArgs} ->
try
{tuple_t, [], ArgTypes1} = Type,
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
{ok, ArgTypes, AstArgs}
catch throw:cannot_translate_to_sophia ->
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
Msg = io_lib:format("Cannot translate FATE value ~p\n to Sophia type ~s\n",
[FateArgs, Type0Str]),
{error, [aeso_errors:new(data_error, Msg)]}
end;
{error, _} ->
Msg = io_lib:format("Failed to decode calldata binary\n", []),
{error, [aeso_errors:new(data_error, Msg)]}
end
{error, _} ->
Msg = io_lib:format("Failed to decode calldata binary", []),
{error, [aeso_errors:new(data_error, Msg)]}
end
catch
throw:{error, Errors} -> {error, Errors}
end.
get_arg_icode(Funs) ->
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
[Args] -> Args;
[] -> error_missing_call_function()
end.
-dialyzer({nowarn_function, error_missing_call_function/0}).
error_missing_call_function() ->
Msg = "Internal error: missing '__call'-function",
aeso_errors:throw(aeso_errors:new(internal_error, Msg)).
get_call_type([{contract, _, _, Defs}]) ->
case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _,
{app, _,
{typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call};
[] -> error_missing_call_function()
end;
get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract
get_call_type(Contracts).
-dialyzer({nowarn_function, get_decode_type/2}).
get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
get_decode_type(FunName, [{Contract, Ann, _, _, Defs}]) when ?IS_CONTRACT_HEAD(Contract) ->
GetType = fun({letfun, _, {id, _, Name}, Args, Ret, _}) when Name == FunName -> [{Args, Ret}];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, Args, Ret}}) when Name == FunName -> [{Args, Ret}];
(_) -> [] end,
@@ -492,8 +356,8 @@ get_decode_type(FunName, [{contract, Ann, _, Defs}]) ->
case FunName of
"init" -> {ok, [], {tuple_t, [], []}};
_ ->
Msg = io_lib:format("Function '~s' is missing in contract\n", [FunName]),
Pos = aeso_code_errors:pos(Ann),
Msg = io_lib:format("Function '~s' is missing in contract", [FunName]),
Pos = aeso_errors:pos(Ann),
aeso_errors:throw(aeso_errors:new(data_error, Pos, Msg))
end
end;
@@ -501,87 +365,17 @@ get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
get_decode_type(FunName, Contracts).
%% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeb_heap:to_binary().
icode_to_term(word, {integer, N}) -> N;
icode_to_term(word, {unop, '-', {integer, N}}) -> -N;
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
Str;
icode_to_term({list, T}, {list, Vs}) ->
[ icode_to_term(T, V) || V <- Vs ];
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
list_to_tuple(icodes_to_terms(Ts, Vs));
icode_to_term({variant, Cs}, {tuple, [{integer, Tag} | Args]}) ->
Ts = lists:nth(Tag + 1, Cs),
{variant, Tag, icodes_to_terms(Ts, Args)};
icode_to_term(T = {map, KT, VT}, M) ->
%% Maps are compiled to builtin and primop calls, so this gets a little hairy
case M of
{funcall, {var_ref, {builtin, map_put}}, [M1, K, V]} ->
Map = icode_to_term(T, M1),
Key = icode_to_term(KT, K),
Val = icode_to_term(VT, V),
Map#{ Key => Val };
#prim_call_contract{ address = {integer, 0},
arg = {tuple, [{integer, ?PRIM_CALL_MAP_EMPTY}, _, _]} } ->
#{};
_ -> throw({todo, M})
end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) ->
throw({todo, typerep});
icode_to_term(T, V) ->
throw({not_a_value, T, V}).
icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
ast_to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) ->
aeso_icode_to_asm:convert(Icode, Options).
to_bytecode(['COMMENT',_|Rest],_Options) ->
to_bytecode(Rest,_Options);
to_bytecode([Op|Rest], Options) ->
[aeb_opcodes:m_to_op(Op)|to_bytecode(Rest, Options)];
to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) ->
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
Payable = fun(Attrs) -> proplists:get_value(payable, Attrs, false) end,
TypeInfo = [aeb_aevm_abi:function_type_info(list_to_binary(lists:last(Name)),
Payable(Attrs), ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name),
not lists:member(private, Attrs)
],
lists:sort(TypeInfo).
pp_sophia_code(C, Opts)-> pp(C, Opts, pp_sophia_code, fun(Code) ->
io:format("~s\n", [prettypr:format(aeso_pretty:decls(Code))])
end).
pp_ast(C, Opts) -> pp(C, Opts, pp_ast, fun aeso_ast:pp/1).
pp_typed_ast(C, Opts)-> pp(C, Opts, pp_typed_ast, fun aeso_ast:pp_typed/1).
pp_icode(C, Opts) -> pp(C, Opts, pp_icode, fun aeso_icode:pp/1).
pp_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_assembler(C, Opts) -> pp(C, Opts, pp_assembler, fun(Asm) -> io:format("~s", [aeb_fate_asm:pp(Asm)]) end).
pp(Code, Options, Option, PPFun) ->
case proplists:lookup(Option, Options) of
{Option, true} ->
{Option1, true} when Option1 =:= Option ->
PPFun(Code);
none ->
ok
@@ -594,31 +388,27 @@ pp(Code, Options, Option, PPFun) ->
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
case proplists:get_value(backend, Options, aevm) of
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
fate ->
try
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
{FCode2, SrcPayable} =
?protect(compile,
begin
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
from_string1(fate, Source, Options),
FCode = aeb_fate_code:deserialize(SrcByteCode),
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
end),
case compare_fate_code(FCode1, FCode2) of
ok when SrcPayable /= Payable ->
Not = fun(true) -> ""; (false) -> " not" end,
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
[Not(Payable), Not(SrcPayable)]));
ok -> ok;
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
end
catch
throw:{deserialize, _} -> Fail("Invalid byte code");
throw:{compile, {error, Errs}} -> {error, Errs}
end
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(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.
compare_fate_code(FCode1, FCode2) ->
@@ -670,14 +460,6 @@ pp_fate_type(T) -> io_lib:format("~w", [T]).
%% -------------------------------------------------------------------
-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}.
sophia_type_to_typerep(String) ->
Ast = aeso_parser:run_parser(aeso_parser:type(), String),
try aeso_ast_to_icode:ast_typerep(Ast) of
Type -> {ok, Type}
catch _:_ -> {error, bad_type}
end.
-spec parse(string(), aeso_compiler:options()) -> none() | aeso_syntax:ast().
parse(Text, Options) ->
parse(Text, sets:new(), Options).
+21 -3
View File
@@ -30,12 +30,16 @@
-export([ err_msg/1
, msg/1
, msg_oneline/1
, new/2
, new/3
, new/4
, pos/1
, pos/2
, pos/3
, pp/1
, pp_oneline/1
, pp_pos/1
, to_json/1
, throw/1
, type/1
@@ -50,6 +54,12 @@ new(Type, Pos, Msg) ->
new(Type, Pos, Msg, Ctxt) ->
#err{ type = Type, pos = Pos, message = Msg, context = Ctxt }.
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),
pos(File, Line, Col).
pos(Line, Col) ->
#pos{ line = Line, col = Col }.
@@ -65,10 +75,13 @@ throw(#err{} = Err) ->
erlang:throw({error, [Err]}).
msg(#err{ message = Msg, context = none }) -> Msg;
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ Ctxt.
msg(#err{ message = Msg, context = Ctxt }) -> Msg ++ "\n" ++ Ctxt.
msg_oneline(#err{ message = Msg, context = none }) -> Msg;
msg_oneline(#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)])).
lists:flatten(io_lib:format("~s~s\n", [str_pos(Pos), msg(Err)])).
str_pos(#pos{file = no_file, line = L, col = C}) ->
io_lib:format("~p:~p:", [L, C]);
@@ -78,7 +91,12 @@ str_pos(#pos{file = F, line = L, col = 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)])).
lists:flatten(io_lib:format("~s~s:\n~s\n", [pp_kind(Kind), pp_pos(Pos), msg(Err)])).
pp_oneline(#err{ type = Kind, pos = Pos } = Err) ->
Msg = msg_oneline(Err),
OneLineMsg = re:replace(Msg, "[\s\\n]+", " ", [global]),
lists:flatten(io_lib:format("~s~s: ~s", [pp_kind(Kind), pp_pos(Pos), OneLineMsg])).
pp_kind(type_error) -> "Type error";
pp_kind(parse_error) -> "Parse error";
+320 -119
View File
@@ -9,7 +9,7 @@
%%%-------------------------------------------------------------------
-module(aeso_fcode_to_fate).
-export([compile/2, term_to_fate/1]).
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
-ifdef(TEST).
-export([optimize_fun/4, to_basic_blocks/1]).
@@ -45,7 +45,14 @@
-define(s(N), {store, N}).
-define(void, {var, 9999}).
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
-record(env, { contract,
vars = [],
locals = [],
current_function,
tailpos = true,
child_contracts = #{},
saved_fresh_names = #{},
options = [] }).
%% -- Debugging --------------------------------------------------------------
@@ -64,19 +71,34 @@ debug(Tag, Options, Fun) ->
-dialyzer({nowarn_function, [code_error/1]}).
code_error(Err) ->
aeso_errors:throw(aeso_code_errors:format(Err)).
Pos = aeso_errors:pos(0, 0),
Msg = lists:flatten(io_lib:format("Unknown error: ~p\n", [Err])),
aeso_errors:throw(aeso_errors:new(code_error, Pos, Msg)).
%% -- Main -------------------------------------------------------------------
%% @doc Main entry point.
compile(FCode, Options) ->
compile(FCode, SavedFreshNames, Options) ->
compile(#{}, FCode, SavedFreshNames, Options).
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
try
compile1(ChildContracts, FCode, SavedFreshNames, Options)
after
put(variables_registers, undefined)
end.
compile1(ChildContracts, FCode, SavedFreshNames, Options) ->
#{ contract_name := ContractName,
functions := Functions } = FCode,
SFuns = functions_to_scode(ContractName, Functions, Options),
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
SFuns1 = optimize_scode(SFuns, Options),
FateCode = to_basic_blocks(SFuns1),
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
FateCode.
FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of
false -> FateCode;
true -> add_child_symbols(ChildContracts, FateCode)
end,
{FateCode1, get_variables_registers()}.
make_function_id(X) ->
aeb_fate_code:symbol_identifier(make_function_name(X)).
@@ -85,21 +107,48 @@ make_function_name(event) -> <<"Chain.event">>;
make_function_name({entrypoint, Name}) -> Name;
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
functions_to_scode(ContractName, Functions, Options) ->
add_child_symbols(ChildContracts, FateCode) ->
Funs = lists:flatten([ maps:keys(ChildFuns) || {_, #{functions := ChildFuns}} <- maps:to_list(ChildContracts) ]),
Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]),
aeb_fate_code:update_symbols(FateCode, Symbols).
functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options) ->
FunNames = maps:keys(Functions),
maps:from_list(
[ {make_function_name(Name), function_to_scode(ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, SavedFreshNames, Options)}
|| {Name, #{args := Args,
body := Body,
attrs := Attrs,
return := Type}} <- maps:to_list(Functions)]).
function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) ->
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) ->
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body),
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options),
[ add_variables_register(Env, Arg, Register) ||
proplists:get_value(debug_info, Options, false),
{Arg, Register} <- Env#env.vars ],
SCode = to_scode(Env, Body),
{Attrs, {ArgTypes, ResType1}, SCode}.
get_variables_registers() ->
case get(variables_registers) of
undefined -> #{};
Vs -> Vs
end.
add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) ->
Olds = get_variables_registers(),
RealName = maps:get(Name, SavedFreshNames, Name),
FunName =
case Env#env.current_function of
event -> "Chain.event";
{entrypoint, BinName} -> binary_to_list(BinName);
{local_fun, QualName} -> lists:last(QualName)
end,
New = {Env#env.contract, FunName, RealName},
put(variables_registers, Olds#{New => Register}).
-define(tvars, '$tvars').
typesig_to_scode(Args, Res) ->
@@ -133,7 +182,9 @@ type_to_scode({tvar, X}) ->
put(?tvars, {I + 1, Vars#{ X => I }}),
{tvar, I};
J -> {tvar, J}
end.
end;
type_to_scode(L) when is_list(L) -> {tuple, types_to_scode(L)}.
types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
@@ -142,17 +193,21 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
%% -- Environment functions --
init_env(ContractName, FunNames, Name, Args) ->
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) ->
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
contract = ContractName,
child_contracts = ChildContracts,
locals = FunNames,
current_function = Name,
tailpos = true }.
options = Options,
tailpos = true,
saved_fresh_names = SavedFreshNames }.
next_var(#env{ vars = Vars }) ->
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
bind_var(Name, Var, Env = #env{ vars = Vars }) ->
proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var),
Env#env{ vars = [{Name, Var} | Vars] }.
bind_local(Name, Env) ->
@@ -169,7 +224,34 @@ lookup_var(#env{vars = Vars}, X) ->
%% -- The compiler --
lit_to_fate(L) ->
serialize_contract_code(Env, C) ->
Cache = case get(contract_code_cache) of
undefined -> put(contract_code_cache, #{}), #{};
Res -> Res
end,
case maps:get(C, Cache, none) of
none ->
Options = Env#env.options,
SavedFreshNames = Env#env.saved_fresh_names,
FCode = maps:get(C, Env#env.child_contracts),
{FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options),
ByteCode = aeb_fate_code:serialize(FateCode, []),
{ok, Version} = aeso_compiler:version(),
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
Code = #{byte_code => ByteCode,
compiler_version => Version,
source_hash => crypto:hash(sha256, OriginalSourceCode ++ [0] ++ C),
type_info => [],
abi_version => aeb_fate_abi:abi_version(),
payable => maps:get(payable, FCode)
},
Serialized = aeser_contract_code:serialize(Code),
put(contract_code_cache, maps:put(C, Serialized, Cache)),
Serialized;
Serialized -> Serialized
end.
lit_to_fate(Env, L) ->
case L of
{int, N} -> aeb_fate_data:make_integer(N);
{string, S} -> aeb_fate_data:make_string(S);
@@ -179,63 +261,65 @@ lit_to_fate(L) ->
{contract_pubkey, K} -> aeb_fate_data:make_contract(K);
{oracle_pubkey, K} -> aeb_fate_data:make_oracle(K);
{oracle_query_id, H} -> aeb_fate_data:make_oracle_query(H);
{contract_code, C} -> aeb_fate_data:make_contract_bytearray(serialize_contract_code(Env, C));
{typerep, T} -> aeb_fate_data:make_typerep(type_to_scode(T))
end.
term_to_fate(E) -> term_to_fate(#{}, E).
term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
term_to_fate(_Env, {lit, L}) ->
lit_to_fate(L);
term_to_fate(GlobEnv, _Env, {lit, L}) ->
lit_to_fate(GlobEnv, L);
%% negative literals are parsed as 0 - N
term_to_fate(_Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
aeb_fate_data:make_integer(-N);
term_to_fate(_Env, nil) ->
term_to_fate(_GlobEnv, _Env, nil) ->
aeb_fate_data:make_list([]);
term_to_fate(Env, {op, '::', [Hd, Tl]}) ->
term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) ->
%% The Tl will translate into a list, because FATE lists are just lists
[term_to_fate(Env, Hd) | term_to_fate(Env, Tl)];
term_to_fate(Env, {tuple, As}) ->
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(Env, A) || A<-As]));
term_to_fate(Env, {con, Ar, I, As}) ->
FateAs = [ term_to_fate(Env, A) || A <- As ],
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
term_to_fate(GlobEnv, Env, {tuple, As}) ->
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
term_to_fate(_Env, {builtin, bits_all, []}) ->
term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) ->
aeb_fate_data:make_bits(-1);
term_to_fate(_Env, {builtin, bits_none, []}) ->
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
aeb_fate_data:make_bits(0);
term_to_fate(_Env, {op, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(GlobEnv, I),
{bits, N bor (1 bsl J)};
term_to_fate(_Env, {op, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(GlobEnv, B),
J = term_to_fate(GlobEnv, I),
{bits, N band bnot (1 bsl J)};
term_to_fate(Env, {'let', X, E, Body}) ->
Env1 = Env#{ X => term_to_fate(Env, E) },
term_to_fate(Env1, Body);
term_to_fate(Env, {var, X}) ->
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
term_to_fate(GlobEnv, Env1, Body);
term_to_fate(_GlobEnv, Env, {var, X}) ->
case maps:get(X, Env, undefined) of
undefined -> throw(not_a_fate_value);
V -> V
end;
term_to_fate(_Env, {builtin, map_empty, []}) ->
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
aeb_fate_data:make_map(#{});
term_to_fate(Env, {op, map_set, [M, K, V]}) ->
Map = term_to_fate(Env, M),
Map#{term_to_fate(Env, K) => term_to_fate(Env, V)};
term_to_fate(_Env, _) ->
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
Map = term_to_fate(GlobEnv, Env, M),
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
term_to_fate(_GlobEnv, _Env, _) ->
throw(not_a_fate_value).
to_scode(Env, T) ->
try term_to_fate(T) of
try term_to_fate(Env, T) of
V -> [push(?i(V))]
catch throw:not_a_fate_value ->
to_scode1(Env, T)
end.
to_scode1(_Env, {lit, L}) ->
[push(?i(lit_to_fate(L)))];
to_scode1(Env, {lit, L}) ->
[push(?i(lit_to_fate(Env, L)))];
to_scode1(_Env, nil) ->
[aeb_fate_ops:nil(?a)];
@@ -302,18 +386,27 @@ to_scode1(Env, {funcall, Fun, Args}) ->
to_scode1(Env, {builtin, B, Args}) ->
builtin_to_scode(Env, B, Args);
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) ->
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
Lbl = make_function_id(Fun),
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
case Gas of
{builtin, call_gas_left, _} ->
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
call_to_scode(Env, Call, [Ct, Value | Args]);
case Protected of
{lit, {bool, false}} ->
case Gas of
{builtin, call_gas_left, _} ->
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
call_to_scode(Env, Call, [Ct, Value | Args]);
_ ->
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
end;
{lit, {bool, true}} ->
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)),
call_to_scode(Env, Call, [Ct, Value, Gas | Args]);
_ ->
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
end;
to_scode1(_Env, {get_state, Reg}) ->
@@ -463,6 +556,8 @@ builtin_to_scode(Env, bytes_split, [_, _] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytes_split(?a, ?a, ?a), Args);
builtin_to_scode(Env, abort, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:abort(?a), Args);
builtin_to_scode(Env, exit, [_] = Args) ->
call_to_scode(Env, aeb_fate_ops:exit(?a), Args);
builtin_to_scode(Env, chain_spend, [_, _] = Args) ->
call_to_scode(Env, [aeb_fate_ops:spend(?a, ?a),
tuple(0)], Args);
@@ -494,10 +589,14 @@ builtin_to_scode(_Env, call_value, []) ->
[aeb_fate_ops:call_value(?a)];
builtin_to_scode(_Env, call_gas_price, []) ->
[aeb_fate_ops:gasprice(?a)];
builtin_to_scode(_Env, call_fee, []) ->
[aeb_fate_ops:fee(?a)];
builtin_to_scode(_Env, call_gas_left, []) ->
[aeb_fate_ops:gas(?a)];
builtin_to_scode(Env, oracle_register, [_Sign,_Account,_QFee,_TTL,_QType,_RType] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_register(?a, ?a, ?a, ?a, ?a, ?a, ?a), Args);
builtin_to_scode(Env, oracle_expiry, [_Oracle] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_expiry(?a, ?a), Args);
builtin_to_scode(Env, oracle_query_fee, [_Oracle] = Args) ->
call_to_scode(Env, aeb_fate_ops:oracle_query_fee(?a, ?a), Args);
builtin_to_scode(Env, oracle_query, [_Oracle, _Question, _QFee, _QTTL, _RTTL, _QType, _RType] = Args) ->
@@ -536,8 +635,35 @@ builtin_to_scode(Env, aens_transfer, [_Sign, _From, _To, _Name] = Args) ->
builtin_to_scode(Env, aens_revoke, [_Sign, _Account, _Name] = Args) ->
call_to_scode(Env, [aeb_fate_ops:aens_revoke(?a, ?a, ?a),
tuple(0)], Args);
builtin_to_scode(Env, aens_update, [_Sign, _Account, _NameString, _TTL, _ClientTTL, _Pointers] = Args) ->
call_to_scode(Env, [aeb_fate_ops:aens_update(?a, ?a, ?a, ?a, ?a, ?a),
tuple(0)], Args);
builtin_to_scode(Env, aens_lookup, [_Name] = Args) ->
call_to_scode(Env, aeb_fate_ops:aens_lookup(?a, ?a), Args);
builtin_to_scode(_Env, auth_tx_hash, []) ->
[aeb_fate_ops:auth_tx_hash(?a)].
[aeb_fate_ops:auth_tx_hash(?a)];
builtin_to_scode(_Env, auth_tx, []) ->
[aeb_fate_ops:auth_tx(?a)];
builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
call_to_scode(Env, aeb_fate_ops:bytecode_hash(?a, ?a), Args);
builtin_to_scode(Env, chain_clone,
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
case GasCap of
{builtin, call_gas_left, _} ->
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, Prot | InitArgs]
);
_ ->
call_to_scode(Env, aeb_fate_ops:clone_g(?a, ?a, ?a, ?a, ?a),
[Contract, InitArgsT, Value, GasCap, Prot | InitArgs]
)
end;
builtin_to_scode(Env, chain_create,
[ Code, InitArgsT, Value | InitArgs]) ->
call_to_scode(Env, aeb_fate_ops:create(?a, ?a, ?a),
[Code, InitArgsT, Value | InitArgs]
).
%% -- Operators --
@@ -564,8 +690,14 @@ op_to_scode(map_to_list) -> aeb_fate_ops:map_to_list(?a, ?a);
op_to_scode(map_delete) -> aeb_fate_ops:map_delete(?a, ?a, ?a);
op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a);
op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a);
op_to_scode(string_length) -> aeb_fate_ops:str_length(?a, ?a);
op_to_scode(string_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a);
op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a);
op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a);
op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a);
op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a);
op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a);
op_to_scode(stringinternal_to_upper) -> aeb_fate_ops:str_to_upper(?a, ?a);
op_to_scode(char_to_int) -> aeb_fate_ops:char_to_int(?a, ?a);
op_to_scode(char_from_int) -> aeb_fate_ops:char_from_int(?a, ?a);
op_to_scode(bits_set) -> aeb_fate_ops:bits_set(?a, ?a, ?a);
op_to_scode(bits_clear) -> aeb_fate_ops:bits_clear(?a, ?a, ?a);
op_to_scode(bits_test) -> aeb_fate_ops:bits_test(?a, ?a, ?a);
@@ -584,9 +716,33 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a,
op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a);
op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a);
op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a);
op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a);
op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a);
op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a).
op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a);
op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a);
op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a);
op_to_scode(mcl_bls12_381_g1_neg) -> aeb_fate_ops:bls12_381_g1_neg(?a, ?a);
op_to_scode(mcl_bls12_381_g1_norm) -> aeb_fate_ops:bls12_381_g1_norm(?a, ?a);
op_to_scode(mcl_bls12_381_g1_valid) -> aeb_fate_ops:bls12_381_g1_valid(?a, ?a);
op_to_scode(mcl_bls12_381_g1_is_zero) -> aeb_fate_ops:bls12_381_g1_is_zero(?a, ?a);
op_to_scode(mcl_bls12_381_g1_add) -> aeb_fate_ops:bls12_381_g1_add(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_g1_mul) -> aeb_fate_ops:bls12_381_g1_mul(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_g2_neg) -> aeb_fate_ops:bls12_381_g2_neg(?a, ?a);
op_to_scode(mcl_bls12_381_g2_norm) -> aeb_fate_ops:bls12_381_g2_norm(?a, ?a);
op_to_scode(mcl_bls12_381_g2_valid) -> aeb_fate_ops:bls12_381_g2_valid(?a, ?a);
op_to_scode(mcl_bls12_381_g2_is_zero) -> aeb_fate_ops:bls12_381_g2_is_zero(?a, ?a);
op_to_scode(mcl_bls12_381_g2_add) -> aeb_fate_ops:bls12_381_g2_add(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_g2_mul) -> aeb_fate_ops:bls12_381_g2_mul(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_gt_inv) -> aeb_fate_ops:bls12_381_gt_inv(?a, ?a);
op_to_scode(mcl_bls12_381_gt_add) -> aeb_fate_ops:bls12_381_gt_add(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_gt_mul) -> aeb_fate_ops:bls12_381_gt_mul(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_gt_pow) -> aeb_fate_ops:bls12_381_gt_pow(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_gt_is_one) -> aeb_fate_ops:bls12_381_gt_is_one(?a, ?a);
op_to_scode(mcl_bls12_381_pairing) -> aeb_fate_ops:bls12_381_pairing(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_miller_loop) -> aeb_fate_ops:bls12_381_miller_loop(?a, ?a, ?a);
op_to_scode(mcl_bls12_381_final_exp) -> aeb_fate_ops:bls12_381_final_exp(?a, ?a);
op_to_scode(mcl_bls12_381_int_to_fr) -> aeb_fate_ops:bls12_381_int_to_fr(?a, ?a);
op_to_scode(mcl_bls12_381_int_to_fp) -> aeb_fate_ops:bls12_381_int_to_fp(?a, ?a);
op_to_scode(mcl_bls12_381_fr_to_int) -> aeb_fate_ops:bls12_381_fr_to_int(?a, ?a);
op_to_scode(mcl_bls12_381_fp_to_int) -> aeb_fate_ops:bls12_381_fp_to_int(?a, ?a).
%% PUSH and STORE ?a are the same, so we use STORE to make optimizations
%% easier, and specialize to PUSH (which is cheaper) at the end.
@@ -600,7 +756,7 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
optimize_scode(Funs, Options) ->
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs).
Funs).
flatten(missing) -> missing;
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
@@ -734,6 +890,7 @@ attributes(I) ->
{'CALL', A} -> Impure(?a, [A]);
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
{'CALL_PGR', A, _, B, C, D, E, F} -> Impure(?a, [A, B, C, D, E, F]);
{'CALL_T', A} -> Impure(pc, [A]);
{'CALL_VALUE', A} -> Pure(A, []);
{'JUMP', _} -> Impure(pc, []);
@@ -815,6 +972,7 @@ attributes(I) ->
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
{'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]);
{'AUTH_TX_HASH', A} -> Pure(A, []);
{'AUTH_TX', A} -> Pure(A, []);
{'BYTES_TO_INT', A, B} -> Pure(A, [B]);
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
{'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
@@ -831,6 +989,7 @@ attributes(I) ->
{'ORIGIN', A} -> Pure(A, []);
{'CALLER', A} -> Pure(A, []);
{'GASPRICE', A} -> Pure(A, []);
{'FEE', A} -> Pure(A, []);
{'BLOCKHASH', A, B} -> Pure(A, [B]);
{'BENEFICIARY', A} -> Pure(A, []);
{'TIMESTAMP', A} -> Pure(A, []);
@@ -853,12 +1012,48 @@ attributes(I) ->
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]);
{'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]);
{'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]);
{'ORACLE_EXPIRY', A, B} -> Impure(A, [B]);
{'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]);
{'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]);
{'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
'AENS_UPDATE' -> Impure(none, []);%% TODO
{'AENS_UPDATE', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
{'AENS_TRANSFER', A, B, C, D} -> Impure(none, [A, B, C, D]);
{'AENS_REVOKE', A, B, C} -> Impure(none, [A, B, C]);
{'AENS_LOOKUP', A, B} -> Impure(A, [B]);
{'BLS12_381_G1_NEG', A, B} -> Pure(A, [B]);
{'BLS12_381_G1_NORM', A, B} -> Pure(A, [B]);
{'BLS12_381_G1_VALID', A, B} -> Pure(A, [B]);
{'BLS12_381_G1_IS_ZERO', A, B} -> Pure(A, [B]);
{'BLS12_381_G1_ADD', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_G1_MUL', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_G2_NEG', A, B} -> Pure(A, [B]);
{'BLS12_381_G2_NORM', A, B} -> Pure(A, [B]);
{'BLS12_381_G2_VALID', A, B} -> Pure(A, [B]);
{'BLS12_381_G2_IS_ZERO', A, B} -> Pure(A, [B]);
{'BLS12_381_G2_ADD', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_G2_MUL', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_GT_INV', A, B} -> Pure(A, [B]);
{'BLS12_381_GT_ADD', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_GT_MUL', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_GT_POW', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_GT_IS_ONE', A, B} -> Pure(A, [B]);
{'BLS12_381_PAIRING', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_MILLER_LOOP', A, B, C} -> Pure(A, [B, C]);
{'BLS12_381_FINAL_EXP', A, B} -> Pure(A, [B]);
{'BLS12_381_INT_TO_FR', A, B} -> Pure(A, [B]);
{'BLS12_381_INT_TO_FP', A, B} -> Pure(A, [B]);
{'BLS12_381_FR_TO_INT', A, B} -> Pure(A, [B]);
{'BLS12_381_FP_TO_INT', A, B} -> Pure(A, [B]);
{'STR_TO_LIST', A, B} -> Pure(A, [B]);
{'STR_FROM_LIST', A, B} -> Pure(A, [B]);
{'STR_TO_UPPER', A, B} -> Pure(A, [B]);
{'STR_TO_LOWER', A, B} -> Pure(A, [B]);
{'CHAR_TO_INT', A, B} -> Pure(A, [B]);
{'CHAR_FROM_INT', A, B} -> Pure(A, [B]);
{'CREATE', A, B, C} -> Impure(?a, [A, B, C]);
{'CLONE', A, B, C, D} -> Impure(?a, [A, B, C, D]);
{'CLONE_G', A, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
{'BYTECODE_HASH', A, B} -> Impure(A, [B]);
{'ABORT', A} -> Impure(pc, A);
{'EXIT', A} -> Impure(pc, A);
'NOP' -> Pure(none, [])
@@ -944,7 +1139,8 @@ simpl_top(I, Code, Options) ->
simpl_top(0, I, Code, _Options) ->
code_error({optimizer_out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) ->
apply_rules(Fuel, rules(), I, Code, Options).
Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
apply_rules(Fuel, Rules, I, Code, Options).
apply_rules(Fuel, Rules, I, Code, Options) ->
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
@@ -971,29 +1167,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
-define(RULE(Name), {Name, fun Name/2}).
merge_rules() ->
[?RULE(r_push_consume),
?RULE(r_one_shot_var),
?RULE(r_write_to_dead_var),
?RULE(r_inline_switch_target)
[?RULE(optimize_push_consume),
?RULE(optimize_one_shot_var),
?RULE(optimize_write_to_dead_var),
?RULE(optimize_inline_switch_target)
].
rules() ->
merge_rules() ++
[?RULE(r_swap_push),
?RULE(r_swap_pop),
?RULE(r_swap_write),
?RULE(r_constant_propagation),
?RULE(r_prune_impossible_branches),
?RULE(r_single_successful_branch),
?RULE(r_inline_store),
?RULE(r_float_switch_body)
[?RULE(optimize_swap_push),
?RULE(optimize_swap_pop),
?RULE(optimize_swap_write),
?RULE(optimize_constant_propagation),
?RULE(optimize_prune_impossible_branches),
?RULE(optimize_single_successful_branch),
?RULE(optimize_inline_store),
?RULE(optimize_float_switch_body)
].
%% Removing pushes that are immediately consumed.
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
inline_push(Ann1, A, 0, Code, []);
%% Writing directly to memory instead of going through the accumulator.
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
IsPush =
case op_view(I) of
{_, ?a, _} -> true;
@@ -1005,7 +1201,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
end,
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
true -> false end;
r_push_consume(_, _) -> false.
optimize_push_consume(_, _) -> false.
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
@@ -1038,7 +1234,7 @@ split_stack_arg(N, [A | As], Acc) ->
split_stack_arg(N1, As, [A | Acc]).
%% Move PUSHes past non-stack instructions.
r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
case op_view(PushI) of
{_, ?a, _} ->
case independent(Push, I) of
@@ -1049,10 +1245,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
end;
_ -> false
end;
r_swap_push(_, _) -> false.
optimize_swap_push(_, _) -> false.
%% Move non-stack instruction past POPs.
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
case independent(IA, JA) of
true ->
case {op_view(I), op_view(J)} of
@@ -1060,7 +1256,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
{_, false} -> false;
{{_, IR, IAs}, {_, RJ, JAs}} ->
NonStackI = not lists:member(?a, [IR | IAs]),
%% RJ /= ?a to not conflict with r_swap_push
%% RJ /= ?a to not conflict with optimize_swap_push
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
case NonStackI andalso PopJ of
false -> false;
@@ -1071,22 +1267,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
end;
false -> false
end;
r_swap_pop(_, _) -> false.
optimize_swap_pop(_, _) -> false.
%% Match up writes to variables with instructions further down.
r_swap_write(I = {i, _, _}, [J | Code]) ->
optimize_swap_write(I = {i, _, _}, [J | Code]) ->
case {var_writes(I), independent(I, J)} of
{[_], true} ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1], I1, Code);
optimize_swap_write([J1], I1, Code);
_ -> false
end;
r_swap_write(_, _) -> false.
optimize_swap_write(_, _) -> false.
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code);
r_swap_write(Pre, I, Code0 = [J | Code]) ->
optimize_swap_write([J1 | Pre], I1, Code);
optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
case apply_rules_once(merge_rules(), I, Code0) of
{_Rule, New, Rest} ->
{lists:reverse(Pre) ++ New, Rest};
@@ -1095,27 +1291,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
false -> false;
true ->
{J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code)
optimize_swap_write([J1 | Pre], I1, Code)
end
end;
r_swap_write(_, _, _) -> false.
optimize_swap_write(_, _, _) -> false.
%% Precompute instructions with known values
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(false)}},
Cons1 = case R of
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
_ -> Cons
end,
{[Cons1, Store], Code};
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(true)}},
Nil1 = case R of
?a -> {i, Ann1, {'NIL', ?void}};
_ -> Nil
end,
{[Nil1, Store], Code};
r_constant_propagation({i, Ann, I}, Code) ->
optimize_constant_propagation({i, Ann, I}, Code) ->
case op_view(I) of
false -> false;
{Op, R, As} ->
@@ -1129,7 +1325,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
end
end
end;
r_constant_propagation(_, _) -> false.
optimize_constant_propagation(_, _) -> false.
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
@@ -1148,12 +1344,12 @@ eval_op('NOT', [false]) -> true;
eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
case pick_branch(Type, V, Alts) of
false -> false;
Alt -> {Alt, Code}
end;
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
Alts1 = [if V -> missing; true -> False end,
if V -> True; true -> missing end],
case Alts == Alts1 of
@@ -1164,7 +1360,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
end
end;
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
case {R, lists:nth(Tag + 1, Alts)} of
{_, missing} ->
@@ -1180,7 +1376,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
false -> {Alt, Code}
end
end;
r_prune_impossible_branches(_, _) -> false.
optimize_prune_impossible_branches(_, _) -> false.
pick_branch(boolean, V, [False, True]) when V == true; V == false ->
Alt = if V -> True; true -> False end,
@@ -1193,7 +1389,7 @@ pick_branch(_Type, _V, _Alts) ->
%% If there's a single branch that doesn't abort we can push the code for that
%% out of the switch.
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
case push_code_out_of_switch([Def | Alts]) of
{_, none} -> false;
{_, many} -> false;
@@ -1201,7 +1397,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
{[Def1 | Alts1], PushedOut} ->
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
end;
r_single_successful_branch(_, _) -> false.
optimize_single_successful_branch(_, _) -> false.
push_code_out_of_switch([]) -> {[], none};
push_code_out_of_switch([Alt | Alts]) ->
@@ -1237,7 +1433,7 @@ does_abort({switch, _, _, Alts, Def}) ->
does_abort(_) -> false.
%% STORE R A, SWITCH R --> SWITCH A
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
optimize_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
Ann1 =
case is_reg(A) of
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
@@ -1256,18 +1452,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
end;
_ -> false %% impossible
end;
r_inline_switch_target(_, _) -> false.
optimize_inline_switch_target(_, _) -> false.
%% Float switch-body to closest switch
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
{J1, I1} = swap_instrs(I, J),
{[], [J1, I1 | Code]};
r_float_switch_body(_, _) -> false.
optimize_float_switch_body(_, _) -> false.
%% Inline stores
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
{[], Code};
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
%% Not when A is var unless updating the annotations properly.
Inline = case A of
{arg, _} -> true;
@@ -1275,13 +1471,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
{store, _} -> true;
_ -> false
end,
if Inline -> r_inline_store([I], false, R, A, Code);
if Inline -> optimize_inline_store([I], false, R, A, Code);
true -> false end;
r_inline_store(_, _) -> false.
optimize_inline_store(_, _) -> false.
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
r_inline_store([I | Acc], Progress, R, A, Code);
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
optimize_inline_store([I | Acc], Progress, R, A, Code);
optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
#{ write := W } = attributes(I),
Inl = fun(X) when X == R -> A; (X) -> X end,
case live_in(R, Ann) of
@@ -1301,14 +1497,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
case lists:member(W, [R, A]) of
true when Progress1 -> {lists:reverse(Acc1), Code};
true -> false;
false -> r_inline_store(Acc1, Progress1, R, A, Code)
false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
end
end;
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
r_inline_store(_, false, _, _, _) -> false.
optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
optimize_inline_store(_, false, _, _, _) -> false.
%% Shortcut write followed by final read
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
case op_view(I) of
{Op, R = {var, _}, As} ->
Copy = case J of
@@ -1322,11 +1518,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
end;
_ -> false
end;
r_one_shot_var(_, _) -> false.
optimize_one_shot_var(_, _) -> false.
%% Remove writes to dead variables
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
r_write_to_dead_var({i, Ann, I}, Code) ->
optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
optimize_write_to_dead_var({i, Ann, I}, Code) ->
#{ pure := Pure } = attributes(I),
case op_view(I) of
{_Op, R, As} when R /= ?a, Pure ->
@@ -1339,9 +1535,10 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
end;
_ -> false
end;
r_write_to_dead_var(_, _) -> false.
optimize_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
op_view(T) when is_tuple(T) ->
[Op, R | As] = tuple_to_list(T),
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
@@ -1611,6 +1808,10 @@ split_calls(Ref, [], Acc, Blocks) ->
split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL';
element(1, I) == 'CALL_R';
element(1, I) == 'CALL_GR';
element(1, I) == 'CALL_PGR';
element(1, I) == 'CREATE';
element(1, I) == 'CLONE';
element(1, I) == 'CLONE_G';
element(1, I) == 'jumpif' ->
split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]);
split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) ->
-149
View File
@@ -1,149 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Intermediate Code for Aeterinty Sophia language.
%%% @end
%%% Created : 21 Dec 2017
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode).
-export([new/1,
pp/1,
set_name/2,
set_namespace/2,
set_payable/2,
enter_namespace/2,
get_namespace/1,
in_main_contract/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]).
-include("aeso_icode.hrl").
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
-type bindings() :: any().
-type fun_dec() :: { string()
, [modifier()]
, arg_list()
, expr()
, aeb_aevm_data:type()}.
-type modifier() :: private | stateful.
-type type_name() :: string() | [string()].
-type icode() :: #{ contract_name => string()
, functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()]
, state_type => aeb_aevm_data:type()
, event_type => aeb_aevm_data:type()
, types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeb_aevm_data:type() }
, constructors => #{ [string()] => integer() } %% name to tag
, options => [any()]
, payable => boolean()
}.
pp(Icode) ->
%% TODO: Actually do *Pretty* printing.
io:format("~p~n", [Icode]).
-spec new([any()]) -> icode().
new(Options) ->
#{ contract_name => ""
, functions => []
, env => new_env()
%% Default to unit type for state and event
, state_type => {tuple, []}
, event_type => {tuple, []}
, types => builtin_types()
, type_vars => #{}
, constructors => builtin_constructors()
, options => Options
, payable => false }.
builtin_types() ->
Word = fun([]) -> word end,
#{ "bool" => Word
, "int" => Word
, "char" => Word
, "bits" => Word
, "string" => fun([]) -> string end
, "address" => Word
, "hash" => Word
, "unit" => fun([]) -> {tuple, []} end
, "signature" => fun([]) -> {tuple, [word, word]} end
, "oracle" => fun([_, _]) -> word end
, "oracle_query" => fun([_, _]) -> word end
, "list" => fun([A]) -> {list, A} end
, "option" => fun([A]) -> {variant, [[], [A]]} end
, "map" => fun([K, V]) -> map_typerep(K, V) end
, ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end
}.
builtin_constructors() ->
#{ ["RelativeTTL"] => 0
, ["FixedTTL"] => 1
, ["None"] => 0
, ["Some"] => 1 }.
map_typerep(K, V) ->
{map, K, V}.
option_typerep(A) ->
{variant, [[], [A]]}.
new_env() ->
[].
-spec set_name(string(), icode()) -> icode().
set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode).
-spec set_payable(boolean(), icode()) -> icode().
set_payable(Payable, Icode) ->
maps:put(payable, Payable, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec in_main_contract(icode()) -> boolean().
in_main_contract(#{ namespace := {con, _, Main}, contract_name := Main }) -> true;
in_main_contract(_Icode) -> false.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag([string()], icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name});
Tag -> Tag
end.
-59
View File
@@ -1,59 +0,0 @@
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
-record(arg, {name::string(), type::?Type()}).
-type expr() :: term().
-type arg() :: #arg{name::string(), type::?Type()}.
-type arg_list() :: [arg()].
-record(fun_dec, { name :: string()
, args :: arg_list()
, body :: expr()}).
-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
-record(prim_call_contract,
{ gas :: expr()
, address :: expr()
, value :: expr()
, arg :: expr()
, type_hash:: expr()
}).
-record(prim_balance, { address :: expr() }).
-record(prim_block_hash, { height :: expr() }).
-record(prim_put, { state :: expr() }).
-record(integer, {value :: integer()}).
-record(tuple, {cpts :: [expr()]}).
-record(list, {elems :: [expr()]}).
-record(unop, { op :: term()
, rand :: expr()}).
-record(binop, { op :: term()
, left :: expr()
, right :: expr()}).
-record(ifte, { decision :: expr()
, then :: expr()
, else :: expr()}).
-record(switch, { expr :: expr()
, cases :: [{expr(),expr()}]}).
-record(funcall, { function :: expr()
, args :: [expr()]}).
-record(lambda, { args :: arg_list(),
body :: expr()}).
-record(missing_field, { format :: string()
, args :: [term()]}).
-record(seq, {exprs :: [expr()]}).
-record(event, {topics :: [expr()], payload :: expr()}).
-983
View File
@@ -1,983 +0,0 @@
%%%-------------------------------------------------------------------
%%% @author Happi (Erik Stenman)
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% Translator from Aesophia Icode to Aevm Assebly
%%% @end
%%% Created : 21 Dec 2017
%%%
%%%-------------------------------------------------------------------
-module(aeso_icode_to_asm).
-export([convert/2]).
-include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl").
i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
convert(#{ contract_name := _ContractName
, state_type := StateType
, functions := Functions
},
_Options) ->
%% Create a function dispatcher
DispatchFun = {"%main", [], [{"arg", "_"}],
{switch, {var_ref, "arg"},
[{{tuple, [fun_hash(Fun),
{tuple, make_args(Args)}]},
icode_seq([ hack_return_address(Fun, length(Args) + 1) ] ++
[ {funcall, {var_ref, FName}, make_args(Args)}]
)}
|| Fun={FName, _, Args, _,_TypeRep} <- Functions, is_public(Fun) ]},
word},
NewFunctions = Functions ++ [DispatchFun],
%% Create a function environment
Funs = [{Name, length(Args), make_ref()}
|| {Name, _Attrs, Args, _Body, _Type} <- NewFunctions],
%% Create dummy code to call the main function with one argument
%% taken from the stack
StopLabel = make_ref(),
StatefulStopLabel = make_ref(),
MainFunction = lookup_fun(Funs, "%main"),
StateTypeValue = aeso_ast_to_icode:type_value(StateType),
DispatchCode = [%% push two return addresses to stop, one for stateful
%% functions and one for non-stateful functions.
push_label(StatefulStopLabel),
push_label(StopLabel),
%% The calldata is already on the stack when we start. Put
%% it on top (also reorders StatefulStop and Stop).
swap(2),
jump(MainFunction),
jumpdest(StatefulStopLabel),
%% We need to encode the state type and put it
%% underneath the return value.
assemble_expr(Funs, [], nontail, StateTypeValue), %% StateT Ret
swap(1), %% Ret StateT
%% We should also change the state value at address 0 to a
%% pointer to the state value (to allow 0 to represent an
%% unchanged state).
i(?MSIZE), %% Ptr
push(0), i(?MLOAD), %% Val Ptr
i(?MSIZE), i(?MSTORE), %% Ptr Mem[Ptr] := Val
push(0), i(?MSTORE), %% Mem[0] := Ptr
%% The pointer to the return value is on top of
%% the stack, but the return instruction takes two
%% stack arguments.
push(0),
i(?RETURN),
jumpdest(StopLabel),
%% Set state pointer to 0 to indicate that we didn't change state
push(0), dup(1), i(?MSTORE),
%% Same as StatefulStopLabel above
push(0),
i(?RETURN)
],
%% Code is a deep list of instructions, containing labels and
%% references to them. Labels take the form {'JUMPDEST', Ref}, and
%% references take the form {push_label, Ref}, which is translated
%% into a PUSH instruction.
Code = [assemble_function(Funs, Name, Args, Body)
|| {Name, _, Args, Body, _Type} <- NewFunctions],
resolve_references(
[%% i(?COMMENT), "CONTRACT: " ++ ContractName,
DispatchCode,
Code]).
%% Generate error on correct format.
gen_error(Error) ->
error({code_errors, [Error]}).
make_args(Args) ->
[{var_ref, [I-1 + $a]} || I <- lists:seq(1, length(Args))].
fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeb_aevm_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top
%% one for stateful functions and the bottom one for non-stateful.
hack_return_address(Fun, N) ->
case is_stateful(Fun) of
true -> {inline_asm, [i(?MSIZE)]};
false ->
{inline_asm, %% X1 .. XN State NoState
[ dup(N + 2) %% NoState X1 .. XN State NoState
, swap(N + 1) %% State X1 .. XN NoState NoState
]} %% Top of the stack will be discarded.
end.
assemble_function(Funs, Name, Args, Body) ->
[jumpdest(lookup_fun(Funs, Name)),
assemble_expr(Funs, lists:reverse(Args), tail, Body),
%% swap return value and first argument
pop_args(length(Args)),
swap(1),
i(?JUMP)].
%% {seq, Es} - should be "one" operation in terms of stack content
%% i.e. after the `seq` there should be one new element on the stack.
assemble_expr(Funs, Stack, Tail, {seq, [E]}) ->
assemble_expr(Funs, Stack, Tail, E);
assemble_expr(Funs, Stack, Tail, {seq, [E | Es]}) ->
[assemble_expr(Funs, Stack, nontail, E),
assemble_expr(Funs, Stack, Tail, {seq, Es})];
assemble_expr(_Funs, _Stack, _Tail, {inline_asm, Code}) ->
Code; %% Unsafe! Code should take care to respect the stack!
assemble_expr(Funs, Stack, _TailPosition, {var_ref, Id}) ->
case lists:keymember(Id, 1, Stack) of
true ->
dup(lookup_var(Id, Stack));
false ->
%% Build a closure
%% When a top-level fun is called directly, we do not
%% reach this case.
Eta = make_ref(),
Continue = make_ref(),
[i(?MSIZE),
push_label(Eta),
dup(2),
i(?MSTORE),
jump(Continue),
%% the code of the closure
jumpdest(Eta),
%% pop the pointer to the function
pop(1),
jump(lookup_fun(Funs, Id)),
jumpdest(Continue)]
end;
assemble_expr(_, _, _, {missing_field, Format, Args}) ->
io:format(Format, Args),
gen_error(missing_field);
assemble_expr(_Funs, _Stack, _, {integer, N}) ->
push(N);
assemble_expr(Funs, Stack, _, {tuple, Cpts}) ->
%% We build tuples right-to-left, so that the first write to the
%% tuple extends the memory size. Because we use ?MSIZE as the
%% heap pointer, we must allocate the tuple AFTER computing the
%% first element.
%% We store elements into the tuple as soon as possible, to avoid
%% keeping them for a long time on the stack.
case lists:reverse(Cpts) of
[] ->
i(?MSIZE);
[Last|Rest] ->
[assemble_expr(Funs, Stack, nontail, Last),
%% allocate the tuple memory
i(?MSIZE),
%% compute address of last word
push(32 * (length(Cpts) - 1)), i(?ADD),
%% Stack: <last-value> <pointer>
%% Write value to memory (allocates the tuple)
swap(1), dup(2), i(?MSTORE),
%% Stack: pointer to last word written
[[%% Update pointer to next word to be written
push(32), swap(1), i(?SUB),
%% Compute element
assemble_expr(Funs, [pointer|Stack], nontail, A),
%% Write element to memory
dup(2), i(?MSTORE)]
%% And we leave a pointer to the last word written on
%% the stack
|| A <- Rest]]
%% The pointer to the entire tuple is on the stack
end;
assemble_expr(_Funs, _Stack, _, {list, []}) ->
%% Use Erik's value of -1 for []
[push(0), i(?NOT)];
assemble_expr(Funs, Stack, _, {list, [A|B]}) ->
assemble_expr(Funs, Stack, nontail, {tuple, [A, {list, B}]});
assemble_expr(Funs, Stack, _, {unop, '!', A}) ->
case A of
{binop, Logical, _, _} when Logical=='&&'; Logical=='||' ->
assemble_expr(Funs, Stack, nontail, {ifte, A, {integer, 0}, {integer, 1}});
_ ->
[assemble_expr(Funs, Stack, nontail, A),
i(?ISZERO)
]
end;
assemble_expr(Funs, Stack, _, {event, Topics, Payload}) ->
[assemble_exprs(Funs, Stack, Topics ++ [Payload]),
case length(Topics) of
0 -> i(?LOG0);
1 -> i(?LOG1);
2 -> i(?LOG2);
3 -> i(?LOG3);
4 -> i(?LOG4)
end, i(?MSIZE)];
assemble_expr(Funs, Stack, _, {unop, Op, A}) ->
[assemble_expr(Funs, Stack, nontail, A),
assemble_prefix(Op)];
assemble_expr(Funs, Stack, Tail, {binop, '&&', A, B}) ->
assemble_expr(Funs, Stack, Tail, {ifte, A, B, {integer, 0}});
assemble_expr(Funs, Stack, Tail, {binop, '||', A, B}) ->
assemble_expr(Funs, Stack, Tail, {ifte, A, {integer, 1}, B});
assemble_expr(Funs, Stack, Tail, {binop, '::', A, B}) ->
%% Take advantage of optimizations in tuple construction.
assemble_expr(Funs, Stack, Tail, {tuple, [A, B]});
assemble_expr(Funs, Stack, _, {binop, Op, A, B}) ->
%% EEVM binary instructions take their first argument from the top
%% of the stack, so to get operands on the stack in the right
%% order, we evaluate from right to left.
[assemble_expr(Funs, Stack, nontail, B),
assemble_expr(Funs, [dummy|Stack], nontail, A),
assemble_infix(Op)];
assemble_expr(Funs, Stack, _, {lambda, Args, Body}) ->
Function = make_ref(),
FunBody = make_ref(),
Continue = make_ref(),
NoMatch = make_ref(),
FreeVars = free_vars({lambda, Args, Body}),
{NewVars, MatchingCode} = assemble_pattern(FunBody, NoMatch, {tuple, [{var_ref, "_"}|FreeVars]}),
BodyCode = assemble_expr(Funs, NewVars ++ lists:reverse([ {Arg#arg.name, Arg#arg.type} || Arg <- Args ]), tail, Body),
[assemble_expr(Funs, Stack, nontail, {tuple, [{label, Function}|FreeVars]}),
jump(Continue), %% will be optimized away
jumpdest(Function),
%% A pointer to the closure is on the stack
MatchingCode,
jumpdest(FunBody),
BodyCode,
pop_args(length(Args)+length(NewVars)),
swap(1),
i(?JUMP),
jumpdest(NoMatch), %% dead code--raise an exception just in case
push(0),
i(?NOT),
i(?MLOAD),
i(?STOP),
jumpdest(Continue)];
assemble_expr(_, _, _, {label, Label}) ->
push_label(Label);
assemble_expr(Funs, Stack, nontail, {funcall, Fun, Args}) ->
Return = make_ref(),
%% This is the obvious code:
%% [{push_label, Return},
%% assemble_exprs(Funs, [return_address|Stack], Args++[Fun]),
%% 'JUMP',
%% {'JUMPDEST', Return}];
%% Its problem is that it stores the return address on the stack
%% while the arguments are computed, which is unnecessary. To
%% avoid that, we compute the last argument FIRST, and replace it
%% with the return address using a SWAP.
%%
%% assemble_function leaves the code pointer of the function to
%% call on top of the stack, and--if the function is not a
%% top-level name--a pointer to its tuple of free variables. In
%% either case a JUMP is the right way to call it.
case Args of
[] ->
[push_label(Return),
assemble_function(Funs, [return_address|Stack], Fun),
i(?JUMP),
jumpdest(Return)];
_ ->
{Init, [Last]} = lists:split(length(Args) - 1, Args),
[assemble_exprs(Funs, Stack, [Last|Init]),
%% Put the return address in the right place, which also
%% reorders the args correctly.
push_label(Return),
swap(length(Args)),
assemble_function(Funs, [dummy || _ <- Args] ++ [return_address|Stack], Fun),
i(?JUMP),
jumpdest(Return)]
end;
assemble_expr(Funs, Stack, tail, {funcall, Fun, Args}) ->
IsTopLevel = is_top_level_fun(Stack, Fun),
%% If the fun is not top-level, then it may refer to local
%% variables and must be computed before stack shuffling.
ArgsAndFun = Args++[Fun || not IsTopLevel],
ComputeArgsAndFun = assemble_exprs(Funs, Stack, ArgsAndFun),
%% Copy arguments back down the stack to the start of the frame
ShuffleSpec = lists:seq(length(ArgsAndFun), 1, -1) ++ [discard || _ <- Stack],
Shuffle = shuffle_stack(ShuffleSpec),
[ComputeArgsAndFun, Shuffle,
if IsTopLevel ->
%% still need to compute function
assemble_function(Funs, [], Fun);
true ->
%% need to unpack a closure
[dup(1), i(?MLOAD)]
end,
i(?JUMP)];
assemble_expr(Funs, Stack, Tail, {ifte, Decision, Then, Else}) ->
%% This compilation scheme introduces a lot of labels and
%% jumps. Unnecessary ones are removed later in
%% resolve_references.
Close = make_ref(),
ThenL = make_ref(),
ElseL = make_ref(),
[assemble_decision(Funs, Stack, Decision, ThenL, ElseL),
jumpdest(ElseL),
assemble_expr(Funs, Stack, Tail, Else),
jump(Close),
jumpdest(ThenL),
assemble_expr(Funs, Stack, Tail, Then),
jumpdest(Close)
];
assemble_expr(Funs, Stack, Tail, {switch, A, Cases}) ->
Close = make_ref(),
[assemble_expr(Funs, Stack, nontail, A),
assemble_cases(Funs, Stack, Tail, Close, Cases),
{'JUMPDEST', Close}];
%% State primitives
%% (A pointer to) the contract state is stored at address 0.
assemble_expr(_Funs, _Stack, _Tail, prim_state) ->
[push(0), i(?MLOAD)];
assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
[assemble_expr(Funs, Stack, nontail, State),
push(0), i(?MSTORE), %% We need something for the unit value on the stack,
i(?MSIZE)]; %% MSIZE is the cheapest instruction.
%% Environment primitives
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
[i(?ADDRESS)];
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
[i(?CREATOR)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
[i(?ORIGIN)];
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
[i(?CALLER)];
assemble_expr(_Funs, _Stack, _Tail, prim_call_value) ->
[i(?CALLVALUE)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_price) ->
[i(?GASPRICE)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_left) ->
[i(?GAS)];
assemble_expr(_Funs, _Stack, _Tail, prim_coinbase) ->
[i(?COINBASE)];
assemble_expr(_Funs, _Stack, _Tail, prim_timestamp) ->
[i(?TIMESTAMP)];
assemble_expr(_Funs, _Stack, _Tail, prim_block_height) ->
[i(?NUMBER)];
assemble_expr(_Funs, _Stack, _Tail, prim_difficulty) ->
[i(?DIFFICULTY)];
assemble_expr(_Funs, _Stack, _Tail, prim_gas_limit) ->
[i(?GASLIMIT)];
assemble_expr(Funs, Stack, _Tail, #prim_balance{ address = Addr }) ->
[assemble_expr(Funs, Stack, nontail, Addr),
i(?BALANCE)];
assemble_expr(Funs, Stack, _Tail, #prim_block_hash{ height = Height }) ->
[assemble_expr(Funs, Stack, nontail, Height),
i(?BLOCKHASH)];
assemble_expr(Funs, Stack, _Tail,
#prim_call_contract{ gas = Gas
, address = To
, value = Value
, arg = Arg
, type_hash= TypeHash
}) ->
%% ?CALL takes (from the top)
%% Gas, To, Value, Arg, TypeHash, _OOffset,_OSize
%% So assemble these in reverse order.
[ assemble_exprs(Funs, Stack, [ {integer, 0}, {integer, 0}, TypeHash
, Arg, Value, To, Gas ])
, i(?CALL)
].
assemble_exprs(_Funs, _Stack, []) ->
[];
assemble_exprs(Funs, Stack, [E|Es]) ->
[assemble_expr(Funs, Stack, nontail, E),
assemble_exprs(Funs, [dummy|Stack], Es)].
assemble_decision(Funs, Stack, {binop, '&&', A, B}, Then, Else) ->
Label = make_ref(),
[assemble_decision(Funs, Stack, A, Label, Else),
jumpdest(Label),
assemble_decision(Funs, Stack, B, Then, Else)];
assemble_decision(Funs, Stack, {binop, '||', A, B}, Then, Else) ->
Label = make_ref(),
[assemble_decision(Funs, Stack, A, Then, Label),
jumpdest(Label),
assemble_decision(Funs, Stack, B, Then, Else)];
assemble_decision(Funs, Stack, {unop, '!', A}, Then, Else) ->
assemble_decision(Funs, Stack, A, Else, Then);
assemble_decision(Funs, Stack, {ifte, A, B, C}, Then, Else) ->
TrueL = make_ref(),
FalseL = make_ref(),
[assemble_decision(Funs, Stack, A, TrueL, FalseL),
jumpdest(TrueL), assemble_decision(Funs, Stack, B, Then, Else),
jumpdest(FalseL), assemble_decision(Funs, Stack, C, Then, Else)];
assemble_decision(Funs, Stack, Decision, Then, Else) ->
[assemble_expr(Funs, Stack, nontail, Decision),
jump_if(Then), jump(Else)].
%% Entered with value to switch on on top of the stack
%% Evaluate selected case, then jump to Close with result on the
%% stack.
assemble_cases(_Funs, _Stack, _Tail, _Close, []) ->
%% No match! What should be do? There's no real way to raise an
%% exception, except consuming all the gas.
%% There should not be enough gas to do this:
[push(1), i(?NOT),
i(?MLOAD),
%% now stop, so that jump optimizer realizes we will not fall
%% through this code.
i(?STOP)];
assemble_cases(Funs, Stack, Tail, Close, [{Pattern, Body}|Cases]) ->
Succeed = make_ref(),
Fail = make_ref(),
{NewVars, MatchingCode} =
assemble_pattern(Succeed, Fail, Pattern),
%% In the code that follows, if this is NOT the last case, then we
%% save the value being switched on, and discard it on
%% success. The code is simpler if this IS the last case.
[[dup(1) || Cases /= []], %% save value for next case, if there is one
MatchingCode,
jumpdest(Succeed),
%% Discard saved value, if we saved one
[case NewVars of
[] ->
pop(1);
[_] ->
%% Special case for peep-hole optimization
pop_args(1);
_ ->
[swap(length(NewVars)), pop(1)]
end
|| Cases/=[]],
assemble_expr(Funs,
case Cases of
[] -> NewVars;
_ -> reorder_vars(NewVars)
end
++Stack, Tail, Body),
%% If the Body makes a tail call, then we will not return
%% here--but it doesn't matter, because
%% (a) the NewVars will be popped before the tailcall
%% (b) the code below will be deleted since it is dead
pop_args(length(NewVars)),
jump(Close),
jumpdest(Fail),
assemble_cases(Funs, Stack, Tail, Close, Cases)].
%% Entered with value to match on top of the stack.
%% Generated code removes value, and
%% - jumps to Fail if no match, or
%% - binds variables, leaves them on the stack, and jumps to Succeed
%% Result is a list of variables to add to the stack, and the matching
%% code.
assemble_pattern(Succeed, Fail, {integer, N}) ->
{[], [push(N),
i(?EQ),
jump_if(Succeed),
jump(Fail)]};
assemble_pattern(Succeed, _Fail, {var_ref, "_"}) ->
{[], [i(?POP), jump(Succeed)]};
assemble_pattern(Succeed, Fail, {missing_field, _, _}) ->
%% Missing record fields are quite ok in patterns.
assemble_pattern(Succeed, Fail, {var_ref, "_"});
assemble_pattern(Succeed, _Fail, {var_ref, Id}) ->
{[{Id, "_"}], jump(Succeed)};
assemble_pattern(Succeed, _Fail, {tuple, []}) ->
{[], [pop(1), jump(Succeed)]};
assemble_pattern(Succeed, Fail, {tuple, [A]}) ->
%% Treat this case specially, because we don't need to save the
%% pointer to the tuple.
{AVars, ACode} = assemble_pattern(Succeed, Fail, A),
{AVars, [i(?MLOAD),
ACode]};
assemble_pattern(Succeed, Fail, {tuple, [A|B]}) ->
%% Entered with the address of the tuple on the top of the
%% stack. We will duplicate the address before matching on A.
Continue = make_ref(), %% the label for matching B
Pop1Fail = make_ref(), %% pop 1 word and goto Fail
PopNFail = make_ref(), %% pop length(AVars) words and goto Fail
{AVars, ACode} =
assemble_pattern(Continue, Pop1Fail, A),
{BVars, BCode} =
assemble_pattern(Succeed, PopNFail, {tuple, B}),
{BVars ++ reorder_vars(AVars),
[%% duplicate the pointer so we don't lose it when we match on A
dup(1),
i(?MLOAD),
ACode,
jumpdest(Continue),
%% Bring the pointer to the top of the stack--this reorders AVars!
swap(length(AVars)),
push(32),
i(?ADD),
BCode,
case AVars of
[] ->
[jumpdest(Pop1Fail), pop(1),
jumpdest(PopNFail),
jump(Fail)];
_ ->
[{'JUMPDEST', PopNFail}, pop(length(AVars)-1),
{'JUMPDEST', Pop1Fail}, pop(1),
{push_label, Fail}, 'JUMP']
end]};
assemble_pattern(Succeed, Fail, {list, []}) ->
%% [] is represented by -1.
{[], [push(1),
i(?ADD),
jump_if(Fail),
jump(Succeed)]};
assemble_pattern(Succeed, Fail, {list, [A|B]}) ->
assemble_pattern(Succeed, Fail, {binop, '::', A, {list, B}});
assemble_pattern(Succeed, Fail, {binop, '::', A, B}) ->
%% Make sure it's not [], then match as tuple.
NotNil = make_ref(),
{Vars, Code} = assemble_pattern(Succeed, Fail, {tuple, [A, B]}),
{Vars, [dup(1), push(1), i(?ADD), %% Check for [] without consuming the value
jump_if(NotNil), %% so it's still there when matching the tuple.
pop(1), %% It was [] so discard the saved value.
jump(Fail),
jumpdest(NotNil),
Code]}.
%% When Vars are on the stack, with a value we want to discard
%% below them, then we swap the top variable with that value and pop.
%% This reorders the variables on the stack, as follows:
reorder_vars([]) ->
[];
reorder_vars([V|Vs]) ->
Vs ++ [V].
assemble_prefix('sha3') -> [i(?DUP1), i(?MLOAD), %% length, ptr
i(?SWAP1), push(32), i(?ADD), %% ptr+32, length
i(?SHA3)];
assemble_prefix('-') -> [push(0), i(?SUB)];
assemble_prefix('bnot') -> i(?NOT).
assemble_infix('+') -> i(?ADD);
assemble_infix('-') -> i(?SUB);
assemble_infix('*') -> i(?MUL);
assemble_infix('/') -> i(?SDIV);
assemble_infix('div') -> i(?DIV);
assemble_infix('mod') -> i(?MOD);
assemble_infix('^') -> i(?EXP);
assemble_infix('bor') -> i(?OR);
assemble_infix('band') -> i(?AND);
assemble_infix('bxor') -> i(?XOR);
assemble_infix('bsl') -> i(?SHL);
assemble_infix('bsr') -> i(?SHR);
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
assemble_infix('>') -> i(?SGT);
assemble_infix('==') -> i(?EQ);
assemble_infix('<=') -> [i(?SGT), i(?ISZERO)];
assemble_infix('=<') -> [i(?SGT), i(?ISZERO)];
assemble_infix('>=') -> [i(?SLT), i(?ISZERO)];
assemble_infix('!=') -> [i(?EQ), i(?ISZERO)];
assemble_infix('!') -> [i(?ADD), i(?MLOAD)];
assemble_infix('byte') -> i(?BYTE).
%% assemble_infix('::') -> [i(?MSIZE), write_word(0), write_word(1)].
%% a function may either refer to a top-level function, in which case
%% we fetch the code label from Funs, or it may be a lambda-expression
%% (including a top-level function passed as a parameter). In the
%% latter case, the function value is a pointer to a tuple of the code
%% pointer and the free variables: we keep the pointer and push the
%% code pointer onto the stack. In either case, we are ready to enter
%% the function with JUMP.
assemble_function(Funs, Stack, Fun) ->
case is_top_level_fun(Stack, Fun) of
true ->
{var_ref, Name} = Fun,
{push_label, lookup_fun(Funs, Name)};
false ->
[assemble_expr(Funs, Stack, nontail, Fun),
dup(1),
i(?MLOAD)]
end.
free_vars(V={var_ref, _}) ->
[V];
free_vars({switch, E, Cases}) ->
lists:umerge(free_vars(E),
lists:umerge([free_vars(Body)--free_vars(Pattern)
|| {Pattern, Body} <- Cases]));
free_vars({lambda, Args, Body}) ->
free_vars(Body) -- [{var_ref, Arg#arg.name} || Arg <- Args];
free_vars(T) when is_tuple(T) ->
free_vars(tuple_to_list(T));
free_vars([H|T]) ->
lists:umerge(free_vars(H), free_vars(T));
free_vars(_) ->
[].
%% shuffle_stack reorders the stack, for example before a tailcall. It is called
%% with a description of the current stack, and how the final stack
%% should appear. The argument is a list containing
%% a NUMBER for each element that should be kept, the number being
%% the position this element should occupy in the final stack
%% discard, for elements that can be discarded.
%% The positions start at 1, referring to the variable to be placed at
%% the bottom of the stack, and ranging up to the size of the final stack.
shuffle_stack([]) ->
[];
shuffle_stack([discard|Stack]) ->
[i(?POP) | shuffle_stack(Stack)];
shuffle_stack([N|Stack]) ->
case length(Stack) + 1 - N of
0 ->
%% the job should be finished
CorrectStack = lists:seq(N - 1, 1, -1),
CorrectStack = Stack,
[];
MoveBy ->
{Pref, [_|Suff]} = lists:split(MoveBy - 1, Stack),
[swap(MoveBy) | shuffle_stack([lists:nth(MoveBy, Stack) | Pref ++ [N|Suff]])]
end.
lookup_fun(Funs, Name) ->
case [Ref || {Name1, _, Ref} <- Funs,
Name == Name1] of
[Ref] -> Ref;
[] -> gen_error({undefined_function, Name})
end.
is_top_level_fun(Stack, {var_ref, Id}) ->
not lists:keymember(Id, 1, Stack);
is_top_level_fun(_, _) ->
false.
lookup_var(Id, Stack) ->
lookup_var(1, Id, Stack).
lookup_var(N, Id, [{Id, _Type}|_]) ->
N;
lookup_var(N, Id, [_|Stack]) ->
lookup_var(N + 1, Id, Stack);
lookup_var(_, Id, []) ->
gen_error({var_not_in_scope, Id}).
%% Smart instruction generation
%% TODO: handle references to the stack beyond depth 16. Perhaps the
%% best way is to repush variables that will be needed in
%% subexpressions before evaluating he subexpression... i.e. fix the
%% problem in assemble_expr, rather than here. A fix here would have
%% to save the top elements of the stack in memory, duplicate the
%% targetted element, and then repush the values from memory.
dup(N) when 1 =< N, N =< 16 ->
i(?DUP1 + N - 1).
push(N) ->
Bytes = binary:encode_unsigned(N),
true = size(Bytes) =< 32,
[i(?PUSH1 + size(Bytes) - 1) |
binary_to_list(Bytes)].
%% Pop N values from UNDER the top element of the stack.
%% This is a pseudo-instruction so peephole optimization can
%% combine pop_args(M), pop_args(N) to pop_args(M+N)
pop_args(0) ->
[];
pop_args(N) ->
{pop_args, N}.
%% [swap(N), pop(N)].
pop(N) ->
[i(?POP) || _ <- lists:seq(1, N)].
swap(0) ->
%% Doesn't exist, but is logically a no-op.
[];
swap(N) when 1 =< N, N =< 16 ->
i(?SWAP1 + N - 1).
jumpdest(Label) -> {i(?JUMPDEST), Label}.
push_label(Label) -> {push_label, Label}.
jump(Label) -> [push_label(Label), i(?JUMP)].
jump_if(Label) -> [push_label(Label), i(?JUMPI)].
%% ICode utilities (TODO: move to separate module)
icode_noname() -> #var_ref{name = "_"}.
icode_seq([A]) -> A;
icode_seq([A | As]) ->
icode_seq(A, icode_seq(As)).
icode_seq(A, B) ->
#switch{ expr = A, cases = [{icode_noname(), B}] }.
%% Stack: <N elements> ADDR
%% Write elements at addresses ADDR, ADDR+32, ADDR+64...
%% Stack afterwards: ADDR
% write_words(N) ->
% [write_word(I) || I <- lists:seq(N-1, 0, -1)].
%% Unused at the moment. Comment out to please dialyzer.
%% write_word(I) ->
%% [%% Stack: elements e ADDR
%% swap(1),
%% dup(2),
%% %% Stack: elements ADDR e ADDR
%% push(32*I),
%% i(?ADD),
%% %% Stack: elements ADDR e ADDR+32I
%% i(?MSTORE)].
%% Resolve references, and convert code from deep list to flat list.
%% List elements are:
%% Opcodes
%% Byte values
%% {'JUMPDEST', Ref} -- assembles to ?JUMPDEST and sets Ref
%% {push_label, Ref} -- assembles to ?PUSHN address bytes
%% For now, we assemble all code addresses as three bytes.
resolve_references(Code) ->
Peephole = peep_hole(lists:flatten(Code)),
%% WARNING: Optimizing jumps reorders the code and deletes
%% instructions. When debugging the assemble_ functions, it can be
%% useful to replace the next line by:
%% Instrs = lists:flatten(Code),
%% thus disabling the optimization.
OptimizedJumps = optimize_jumps(Peephole),
Instrs = lists:reverse(peep_hole_backwards(lists:reverse(OptimizedJumps))),
Labels = define_labels(0, Instrs),
lists:flatten([use_labels(Labels, I) || I <- Instrs]).
define_labels(Addr, [{'JUMPDEST', Lab}|More]) ->
[{Lab, Addr}|define_labels(Addr + 1, More)];
define_labels(Addr, [{push_label, _}|More]) ->
define_labels(Addr + 4, More);
define_labels(Addr, [{pop_args, N}|More]) ->
define_labels(Addr + N + 1, More);
define_labels(Addr, [_|More]) ->
define_labels(Addr + 1, More);
define_labels(_, []) ->
[].
use_labels(_, {'JUMPDEST', _}) ->
'JUMPDEST';
use_labels(Labels, {push_label, Ref}) ->
case proplists:get_value(Ref, Labels) of
undefined ->
gen_error({undefined_label, Ref});
Addr when is_integer(Addr) ->
[i(?PUSH3),
Addr div 65536, (Addr div 256) rem 256, Addr rem 256]
end;
use_labels(_, {pop_args, N}) ->
[swap(N), pop(N)];
use_labels(_, I) ->
I.
%% Peep-hole optimization.
%% The compilation of conditionals can introduce jumps depending on
%% constants 1 and 0. These are removed by peep-hole optimization.
peep_hole(['PUSH1', 0, {push_label, _}, 'JUMPI'|More]) ->
peep_hole(More);
peep_hole(['PUSH1', 1, {push_label, Lab}, 'JUMPI'|More]) ->
[{push_label, Lab}, 'JUMP'|peep_hole(More)];
peep_hole([{pop_args, M}, {pop_args, N}|More]) when M + N =< 16 ->
peep_hole([{pop_args, M + N}|More]);
peep_hole([I|More]) ->
[I|peep_hole(More)];
peep_hole([]) ->
[].
%% Peep-hole optimization on reversed instructions lists.
peep_hole_backwards(Code) ->
NewCode = peep_hole_backwards1(Code),
if Code == NewCode -> Code;
true -> peep_hole_backwards(NewCode)
end.
peep_hole_backwards1(['ADD', 0, 'PUSH1'|Code]) ->
peep_hole_backwards1(Code);
peep_hole_backwards1(['POP', UnOp|Code]) when UnOp=='MLOAD';UnOp=='ISZERO';UnOp=='NOT' ->
peep_hole_backwards1(['POP'|Code]);
peep_hole_backwards1(['POP', BinOp|Code]) when
%% TODO: more binary operators
BinOp=='ADD';BinOp=='SUB';BinOp=='MUL';BinOp=='SDIV' ->
peep_hole_backwards1(['POP', 'POP'|Code]);
peep_hole_backwards1(['POP', _, 'PUSH1'|Code]) ->
peep_hole_backwards1(Code);
peep_hole_backwards1([I|Code]) ->
[I|peep_hole_backwards1(Code)];
peep_hole_backwards1([]) ->
[].
%% Jump optimization:
%% Replaces a jump to a jump with a jump to the final destination
%% Moves basic blocks to eliminate an unconditional jump to them.
%% The compilation of conditionals generates a lot of labels and
%% jumps, some of them unnecessary. This optimization phase reorders
%% code so that as many jumps as possible can be eliminated, and
%% replaced by just falling through to the destination label. This
%% both optimizes the code generated by conditionals, and converts one
%% call of a function into falling through into its code--so it
%% reorders code quite aggressively. Function returns are indirect
%% jumps, however, and are never optimized away.
%% IMPORTANT: since execution begins at address zero, then the first
%% block of code must never be moved elsewhere. The code below has
%% this property, because it processes blocks from left to right, and
%% because the first block does not begin with a label, and so can
%% never be jumped to--hence no code can be inserted before it.
%% The optimization works by taking one block of code at a time, and
%% then prepending blocks that jump directly to it, and appending
%% blocks that it jumps directly to, resulting in a jump-free sequence
%% that is as long as possible. To do so, we store blocks in the form
%% {OptionalLabel, Body, OptionalJump} which represents the code block
%% OptionalLabel++Body++OptionalJump; the optional parts are the empty
%% list of instructions if not present. Two blocks can be merged if
%% the first ends in an OptionalJump to the OptionalLabel beginning
%% the second; the OptionalJump can then be removed (and the
%% OptionalLabel if there are no other references to it--this happens
%% during dead code elimination.
%% TODO: the present implementation is QUADRATIC, because we search
%% repeatedly for matching blocks to merge with the first one, storing
%% the blocks in a list. A near linear time implementation could use
%% two ets tables, one keyed on the labels, and the other keyed on the
%% final jumps.
optimize_jumps(Code) ->
JJs = jumps_to_jumps(Code),
ShortCircuited = [short_circuit_jumps(JJs, Instr) || Instr <- Code],
NoDeadCode = eliminate_dead_code(ShortCircuited),
MovedCode = merge_blocks(moveable_blocks(NoDeadCode)),
%% Moving code may have made some labels superfluous.
eliminate_dead_code(MovedCode).
jumps_to_jumps([{'JUMPDEST', Label}, {push_label, Target}, 'JUMP'|More]) ->
[{Label, Target}|jumps_to_jumps(More)];
jumps_to_jumps([{'JUMPDEST', Label}, {'JUMPDEST', Target}|More]) ->
[{Label, Target}|jumps_to_jumps([{'JUMPDEST', Target}|More])];
jumps_to_jumps([_|More]) ->
jumps_to_jumps(More);
jumps_to_jumps([]) ->
[].
short_circuit_jumps(JJs, {push_label, Lab}) ->
case proplists:get_value(Lab, JJs) of
undefined ->
{push_label, Lab};
Target ->
%% I wonder if this will ever loop infinitely?
short_circuit_jumps(JJs, {push_label, Target})
end;
short_circuit_jumps(_JJs, Instr) ->
Instr.
eliminate_dead_code(Code) ->
Jumps = lists:usort([Lab || {push_label, Lab} <- Code]),
NewCode = live_code(Jumps, Code),
if Code==NewCode ->
Code;
true ->
eliminate_dead_code(NewCode)
end.
live_code(Jumps, ['JUMP'|More]) ->
['JUMP'|dead_code(Jumps, More)];
live_code(Jumps, ['STOP'|More]) ->
['STOP'|dead_code(Jumps, More)];
live_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
case lists:member(Lab, Jumps) of
true ->
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
false ->
live_code(Jumps, More)
end;
live_code(Jumps, [I|More]) ->
[I|live_code(Jumps, More)];
live_code(_, []) ->
[].
dead_code(Jumps, [{'JUMPDEST', Lab}|More]) ->
case lists:member(Lab, Jumps) of
true ->
[{'JUMPDEST', Lab}|live_code(Jumps, More)];
false ->
dead_code(Jumps, More)
end;
dead_code(Jumps, [_I|More]) ->
dead_code(Jumps, More);
dead_code(_, []) ->
[].
%% Split the code into "moveable blocks" that control flow only
%% reaches via jumps.
moveable_blocks([]) ->
[];
moveable_blocks([I]) ->
[[I]];
moveable_blocks([Jump|More]) when Jump=='JUMP'; Jump=='STOP' ->
[[Jump]|moveable_blocks(More)];
moveable_blocks([I|More]) ->
[Block|MoreBlocks] = moveable_blocks(More),
[[I|Block]|MoreBlocks].
%% Merge blocks to eliminate jumps where possible.
merge_blocks(Blocks) ->
BlocksAndTargets = [label_and_jump(B) || B <- Blocks],
[I || {Pref, Body, Suff} <- merge_after(BlocksAndTargets),
I <- Pref++Body++Suff].
%% Merge the first block with other blocks that come after it
merge_after(All=[{Label, Body, [{push_label, Target}, 'JUMP']}|BlocksAndTargets]) ->
case [{B, J} || {[{'JUMPDEST', L}], B, J} <- BlocksAndTargets,
L == Target] of
[{B, J}|_] ->
merge_after([{Label, Body ++ [{'JUMPDEST', Target}] ++ B, J}|
lists:delete({[{'JUMPDEST', Target}], B, J},
BlocksAndTargets)]);
[] ->
merge_before(All)
end;
merge_after(All) ->
merge_before(All).
%% The first block cannot be merged with any blocks that it jumps
%% to... but maybe it can be merged with a block that jumps to it!
merge_before([Block={[{'JUMPDEST', Label}], Body, Jump}|BlocksAndTargets]) ->
case [{L, B, T} || {L, B, [{push_label, T}, 'JUMP']} <- BlocksAndTargets,
T == Label] of
[{L, B, T}|_] ->
merge_before([{L, B ++ [{'JUMPDEST', Label}] ++ Body, Jump}
|lists:delete({L, B, [{push_label, T}, 'JUMP']}, BlocksAndTargets)]);
_ ->
[Block | merge_after(BlocksAndTargets)]
end;
merge_before([Block|BlocksAndTargets]) ->
[Block | merge_after(BlocksAndTargets)];
merge_before([]) ->
[].
%% Convert each block to a PREFIX, which is a label or empty, a
%% middle, and a SUFFIX which is a JUMP to a label, or empty.
label_and_jump(B) ->
{Label, B1} = case B of
[{'JUMPDEST', L}|More1] ->
{[{'JUMPDEST', L}], More1};
_ ->
{[], B}
end,
{Target, B2} = case lists:reverse(B1) of
['JUMP', {push_label, T}|More2] ->
{[{push_label, T}, 'JUMP'], lists:reverse(More2)};
_ ->
{[], B1}
end,
{Label, B2, Target}.
+9 -1
View File
@@ -15,7 +15,8 @@
many/1, many1/1, sep/2, sep1/2,
infixl/2, infixr/2]).
-export([current_file/0, set_current_file/1]).
-export([current_file/0, set_current_file/1,
current_include_type/0, set_current_include_type/1]).
%% -- Types ------------------------------------------------------------------
@@ -465,6 +466,13 @@ merge_with(Fun, Map1, Map2) ->
end, Map2, maps:to_list(Map1))
end.
%% Current include type
current_include_type() ->
get('$current_include_type').
set_current_include_type(IncludeType) ->
put('$current_include_type', IncludeType).
%% Current source file
current_file() ->
get('$current_file').
+8 -6
View File
@@ -9,12 +9,14 @@
false -> fail()
end).
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F})).
-define(RULE(A, Do), map(fun(_1) -> Do end, A )).
-define(RULE(A, B, Do), map(fun({_1, _2}) -> Do end, {A, B} )).
-define(RULE(A, B, C, Do), map(fun({_1, _2, _3}) -> Do end, {A, B, C} )).
-define(RULE(A, B, C, D, Do), map(fun({_1, _2, _3, _4}) -> Do end, {A, B, C, D} )).
-define(RULE(A, B, C, D, E, Do), map(fun({_1, _2, _3, _4, _5}) -> Do end, {A, B, C, D, E} )).
-define(RULE(A, B, C, D, E, F, Do), map(fun({_1, _2, _3, _4, _5, _6}) -> Do end, {A, B, C, D, E, F} )).
-define(RULE(A, B, C, D, E, F, G, Do), map(fun({_1, _2, _3, _4, _5, _6, _7}) -> Do end, {A, B, C, D, E, F, G} )).
-define(RULE(A, B, C, D, E, F, G, H, Do), map(fun({_1, _2, _3, _4, _5, _6, _7, _8}) -> Do end, {A, B, C, D, E, F, G, H})).
-import(aeso_parse_lib,
[tok/1, tok/2, between/3, many/1, many1/1, sep/2, sep1/2,
+101 -13
View File
@@ -18,7 +18,8 @@
run_parser/3]).
-include("aeso_parse_lib.hrl").
-import(aeso_parse_lib, [current_file/0, set_current_file/1]).
-import(aeso_parse_lib, [current_file/0, set_current_file/1,
current_include_type/0, set_current_include_type/1]).
-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none().
@@ -57,6 +58,7 @@ run_parser(P, Inp, Opts) ->
parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)),
set_current_include_type(proplists:get_value(include_type, Opts, none)),
case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
{error, {{Input, Pos}, _}} ->
@@ -93,10 +95,35 @@ decl() ->
?LAZY_P(
choice(
%% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
[ ?RULE(token(main), keyword(contract),
con(), tok('='), maybe_block(decl()), {contract_main, _2, _3, [], _5})
, ?RULE(token(main), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_main, _2, _3, _5, _7})
, ?RULE(keyword(contract),
con(), tok('='), maybe_block(decl()), {contract_child, _1, _2, [], _4})
, ?RULE(keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_child, _1, _2, _4, _6})
, ?RULE(keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), {contract_interface, _1, _3, [], _5})
, ?RULE(keyword(contract), token(interface),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), {contract_interface, _1, _3, _5, _7})
, ?RULE(token(payable), token(main), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, [], _6}))
, ?RULE(token(payable), token(main), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_main, _3, _4, _6, _8}))
, ?RULE(token(payable), keyword(contract),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, [], _5}))
, ?RULE(token(payable), keyword(contract),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_child, _2, _3, _5, _7}))
, ?RULE(token(payable), keyword(contract), token(interface),
con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, [], _6}))
, ?RULE(token(payable), keyword(contract), token(interface),
con(), tok(':'), comma_sep(con()), tok('='), maybe_block(decl()), add_modifiers([_1], {contract_interface, _2, _4, _6, _8}))
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, using()
, pragma()
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
@@ -123,6 +150,21 @@ fundef_or_decl() ->
choice([?RULE(id(), tok(':'), type(), {fun_decl, get_ann(_1), _1, _3}),
fundef()]).
using() ->
Alias = {keyword(as), con()},
For = ?RULE(keyword(for), bracket_list(id()), {for, _2}),
Hiding = ?RULE(keyword(hiding), bracket_list(id()), {hiding, _2}),
?RULE(keyword(using), con(), optional(Alias), optional(choice(For, Hiding)), using(get_ann(_1), _2, _3, _4)).
using(Ann, Con, none, none) ->
{using, Ann, Con, none, none};
using(Ann, Con, {ok, {_, Alias}}, none) ->
{using, Ann, Con, Alias, none};
using(Ann, Con, none, {ok, List}) ->
{using, Ann, Con, none, List};
using(Ann, Con, {ok, {_, Alias}}, {ok, List}) ->
{using, Ann, Con, Alias, List}.
pragma() ->
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
@@ -183,10 +225,16 @@ letdef() -> choice(valdef(), fundef()).
valdef() ->
?RULE(pattern(), tok('='), body(), {letval, [], _1, _3}).
guarded_fundefs() ->
choice(
[ ?RULE(keyword('='), body(), [{guarded, _1, [], _2}])
, maybe_block(?RULE(keyword('|'), comma_sep(expr()), tok('='), body(), {guarded, _1, _2, _4}))
]).
fundef() ->
choice(
[ ?RULE(id(), args(), tok('='), body(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _4})
, ?RULE(id(), args(), tok(':'), type(), tok('='), body(), {letfun, get_ann(_1), _1, _2, _4, _6})
[ ?RULE(id(), args(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, type_wildcard(get_ann(_1)), _3})
, ?RULE(id(), args(), tok(':'), type(), guarded_fundefs(), {letfun, get_ann(_1), _1, _2, _4, _5})
]).
args() -> paren_list(pattern()).
@@ -196,6 +244,9 @@ arg() -> choice(
?RULE(id(), {arg, get_ann(_1), _1, type_wildcard(get_ann(_1))}),
?RULE(id(), tok(':'), type(), {arg, get_ann(_1), _1, _3})).
letpat() ->
?RULE(keyword('('), id(), tok('='), pattern(), tok(')'), {letpat, get_ann(_1), _2, _4}).
%% -- Types ------------------------------------------------------------------
type_vars() -> paren_list(tvar()).
@@ -242,7 +293,8 @@ body() ->
stmt() ->
?LAZY_P(choice(
[ expr()
[ using()
, expr()
, letdecl()
, {switch, keyword(switch), parens(expr()), maybe_block(branch())}
, {'if', keyword('if'), parens(expr()), body()}
@@ -251,7 +303,13 @@ stmt() ->
])).
branch() ->
?RULE(pattern(), keyword('=>'), body(), {'case', _2, _1, _3}).
?RULE(pattern(), guarded_branches(), {'case', get_ann(lists:nth(1, _2)), _1, _2}).
guarded_branches() ->
choice(
[ ?RULE(keyword('=>'), body(), [{guarded, _1, [], _2}])
, maybe_block(?RULE(tok('|'), comma_sep(expr()), keyword('=>'), body(), {guarded, _3, _2, _4}))
]).
pattern() ->
?LET_P(E, expr(), parse_pattern(E)).
@@ -262,17 +320,18 @@ expr() -> expr100().
expr100() ->
Expr100 = ?LAZY_P(expr100()),
Expr200 = ?LAZY_P(expr200()),
Expr150 = ?LAZY_P(expr150()),
choice(
[ ?RULE(lam_args(), keyword('=>'), body(), {lam, _2, _1, _3}) %% TODO: better location
, {'if', keyword('if'), parens(Expr100), Expr200, right(tok(else), Expr100)}
, ?RULE(Expr200, optional(right(tok(':'), type())),
, {'if', keyword('if'), parens(Expr100), Expr150, right(tok(else), Expr100)}
, ?RULE(Expr150, optional(right(tok(':'), type())),
case _2 of
none -> _1;
{ok, Type} -> {typed, get_ann(_1), _1, Type}
end)
]).
expr150() -> infixl(expr200(), binop('|>')).
expr200() -> infixr(expr300(), binop('||')).
expr300() -> infixr(expr400(), binop('&&')).
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
@@ -288,7 +347,7 @@ exprAtom() ->
?LAZY_P(begin
Expr = ?LAZY_P(expr()),
choice(
[ id_or_addr(), con(), token(qid), token(qcon)
[ id_or_addr(), con(), token(qid), token(qcon), binop_as_lam()
, token(bytes), token(string), token(char)
, token(int)
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
@@ -299,9 +358,13 @@ exprAtom() ->
, ?RULE(keyword('['), Expr, token('|'), comma_sep(comprehension_exp()), tok(']'), list_comp_e(_1, _2, _4))
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
, letpat()
, hole()
])
end).
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
comprehension_exp() ->
?LAZY_P(choice(
[ comprehension_bind()
@@ -424,6 +487,19 @@ id() -> token(id).
tvar() -> token(tvar).
str() -> token(string).
binop_as_lam() ->
BinOps = ['&&', '||',
'+', '-', '*', '/', '^', 'mod',
'==', '!=', '<', '>', '<=', '=<', '>=',
'::', '++', '|>'],
OpToLam = fun(Op = {_, Ann}) ->
IdL = {id, Ann, "l"},
IdR = {id, Ann, "r"},
Arg = fun(Id) -> {arg, Ann, Id, type_wildcard(Ann)} end,
{lam, Ann, [Arg(IdL), Arg(IdR)], infix(IdL, Op, IdR)}
end,
?RULE(parens(choice(lists:map(fun token/1, BinOps))), OpToLam(_1)).
token(Tag) ->
?RULE(tok(Tag),
case _1 of
@@ -478,7 +554,11 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
pos_ann(Line, Col) ->
[ {file, current_file()}
, {include_type, current_include_type()}
, {line, Line}
, {col, Col} ].
ann_pos(Ann) ->
{proplists:get_value(file, Ann),
@@ -559,6 +639,8 @@ tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}.
-spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()).
parse_pattern({letpat, Ann, Id, Pat}) ->
{letpat, Ann, Id, parse_pattern(Pat)};
parse_pattern({app, Ann, Con = {'::', _}, Es}) ->
{app, Ann, Con, lists:map(fun parse_pattern/1, Es)};
parse_pattern({app, Ann, {'-', _}, [{int, _, N}]}) ->
@@ -618,9 +700,15 @@ expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Op
Hashed = hash_include(File, Code),
case sets:is_element(Hashed, Included) of
false ->
SrcFile = proplists:get_value(src_file, Opts, no_file),
IncludeType = case proplists:get_value(file, Ann) of
SrcFile -> direct;
_ -> indirect
end,
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
Opts2 = lists:keystore(include_type, 1, Opts1, {include_type, IncludeType}),
Included1 = sets:add_element(Hashed, Included),
case parse_and_scan(file(), Code, Opts1) of
case parse_and_scan(file(), Code, Opts2) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Included1, Acc, Opts);
Err = {error, _} ->
+50 -17
View File
@@ -13,6 +13,8 @@
-export_type([options/0]).
-include("aeso_utils.hrl").
-type doc() :: prettypr:document().
-type options() :: [{indent, non_neg_integer()} | show_generated].
@@ -131,6 +133,10 @@ typed(A, Type) ->
false -> follow(hsep(A, text(":")), type(Type))
end.
contract_head(contract_main) -> text("main contract");
contract_head(contract_child) -> text("contract");
contract_head(contract_interface) -> text("contract interface").
%% -- Exports ----------------------------------------------------------------
-spec decls([aeso_syntax:decl()], options()) -> doc().
@@ -145,12 +151,16 @@ decl(D, Options) ->
with_options(Options, fun() -> decl(D) end).
-spec decl(aeso_syntax:decl()) -> doc().
decl({contract, Attrs, C, Ds}) ->
decl({Con, Attrs, C, Is, Ds}) when ?IS_CONTRACT_HEAD(Con) ->
Mod = fun({Mod, true}) when Mod == payable ->
text(atom_to_list(Mod));
(_) -> empty() end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")])
, hsep(name(C), text("="))), decls(Ds));
ImplsList = case Is of
[] -> [empty()];
_ -> [text(":"), par(punctuate(text(","), lists:map(fun name/1, Is)), 0)]
end,
block(follow( hsep(lists:map(Mod, Attrs) ++ [contract_head(Con)])
, hsep([name(C)] ++ ImplsList ++ [text("=")])), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({pragma, _, Pragma}) -> pragma(Pragma);
@@ -206,8 +216,10 @@ name({typed, _, Name, _}) -> name(Name).
-spec letdecl(string(), aeso_syntax:letbind()) -> doc().
letdecl(Let, {letval, _, P, E}) ->
block_expr(0, hsep([text(Let), expr(P), text("=")]), E);
letdecl(Let, {letfun, _, F, Args, T, E}) ->
block_expr(0, hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T), text("=")]), E).
letdecl(Let, {letfun, _, F, Args, T, [GuardedBody]}) ->
beside(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), guarded_body(GuardedBody, "="));
letdecl(Let, {letfun, _, F, Args, T, GuardedBodies}) ->
block(hsep([text(Let), typed(beside(name(F), expr({tuple, [], Args})), T)]), above(lists:map(fun(GB) -> guarded_body(GB, "=") end, GuardedBodies))).
-spec args([aeso_syntax:arg()]) -> doc().
args(Args) ->
@@ -249,6 +261,8 @@ type(Type, Options) ->
with_options(Options, fun() -> type(Type) end).
-spec type(aeso_syntax:type()) -> doc().
type(F = {fun_t, _, _, var_args, _}) ->
type(setelement(4, F, [var_args]));
type({fun_t, _, Named, Args, Ret}) ->
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
type({type_sig, _, Named, Args, Ret}) ->
@@ -264,6 +278,8 @@ type({args_t, _, Args}) ->
type({bytes_t, _, any}) -> text("bytes(_)");
type({bytes_t, _, Len}) ->
text(lists:concat(["bytes(", Len, ")"]));
type({if_t, _, Id, Then, Else}) ->
beside(text("if"), args_type([Id, Then, Else]));
type({named_arg_t, _, Name, Type, _Default}) ->
%% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
@@ -274,7 +290,9 @@ type(T = {id, _, _}) -> name(T);
type(T = {qid, _, _}) -> name(T);
type(T = {con, _, _}) -> name(T);
type(T = {qcon, _, _}) -> name(T);
type(T = {tvar, _, _}) -> name(T).
type(T = {tvar, _, _}) -> name(T);
type(var_args) -> text("var_args").
-spec args_type([aeso_syntax:type()]) -> doc().
args_type(Args) ->
@@ -290,12 +308,11 @@ tuple_type(Factors) ->
, text(")")
]).
-spec arg_expr(aeso_syntax:arg_expr()) -> doc().
arg_expr({named_arg, _, Name, E}) ->
follow(hsep(expr(Name), text("=")), expr(E));
arg_expr(E) -> expr(E).
-spec expr_p(integer(), aeso_syntax:expr()) -> doc().
-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc().
expr_p(P, {letpat, _, Id, Pat}) ->
paren(P > 100, follow(hsep(expr(Id), text("=")), expr(Pat)));
expr_p(P, {named_arg, _, Name, E}) ->
paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E)));
expr_p(P, {lam, _, Args, E}) ->
paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E)));
expr_p(P, If = {'if', Ann, Cond, Then, Else}) ->
@@ -377,8 +394,13 @@ expr_p(_, {char, _, C}) ->
case C of
$' -> text("'\\''");
$" -> text("'\"'");
_ -> S = lists:flatten(io_lib:format("~p", [[C]])),
text("'" ++ tl(lists:droplast(S)) ++ "'")
_ when C < 16#80 ->
S = lists:flatten(io_lib:format("~p", [[C]])),
text("'" ++ tl(lists:droplast(S)) ++ "'");
_ ->
S = lists:flatten(
io_lib:format("'~ts'", [list_to_binary(aeso_scan:utf8_encode([C]))])),
text(S)
end;
%% -- Names
expr_p(_, E = {id, _, _}) -> name(E);
@@ -412,6 +434,7 @@ lc_bind(Let) ->
bin_prec('..') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]'
bin_prec('@') -> { 0, 0, 0}; %% Only in error messages
bin_prec('|>') -> {150, 150, 200};
bin_prec('||') -> {200, 300, 200};
bin_prec('&&') -> {300, 400, 300};
bin_prec('<') -> {400, 500, 500};
@@ -450,7 +473,7 @@ prefix(P, Op, A) ->
app(P, F, Args) ->
paren(P > 900,
beside(expr_p(900, F),
tuple(lists:map(fun arg_expr/1, Args)))).
tuple(lists:map(fun expr/1, Args)))).
field({field, _, LV, E}) ->
follow(hsep(lvalue(LV), text("=")), expr(E));
@@ -470,8 +493,18 @@ elim1(Proj={proj, _, _}) -> beside(text("."), elim(Proj));
elim1(Get={map_get, _, _}) -> elim(Get);
elim1(Get={map_get, _, _, _}) -> elim(Get).
alt({'case', _, Pat, Body}) ->
block_expr(0, hsep(expr(Pat), text("=>")), Body).
alt({'case', _, Pat, [GuardedBody]}) ->
beside(expr(Pat), guarded_body(GuardedBody, "=>"));
alt({'case', _, Pat, GuardedBodies}) ->
block(expr(Pat), above(lists:map(fun(GB) -> guarded_body(GB, "=>") end, GuardedBodies))).
guarded_body({guarded, _, Guards, Body}, Then) ->
block_expr(0, hsep(guards(Guards), text(Then)), Body).
guards([]) ->
text("");
guards(Guards) ->
hsep([text(" |"), par(punctuate(text(","), lists:map(fun expr/1, Guards)), 0)]).
block_expr(_, Header, {block, _, Ss}) ->
block(Header, statements(Ss));
+35 -27
View File
@@ -7,7 +7,7 @@
%%%-------------------------------------------------------------------
-module(aeso_scan).
-export([scan/1]).
-export([scan/1, utf8_encode/1]).
-import(aeso_scan_lib, [token/1, token/2, symbol/0, skip/0,
override/2, push/2, pop/1]).
@@ -28,7 +28,13 @@ lexer() ->
QID = ["(", CON, "\\.)+", ID],
QCON = ["(", CON, "\\.)+", CON],
OP = "[=!<>+\\-*/:&|?~@^]+",
CHAR = "'([^'\\\\]|(\\\\.))'",
%% Five cases for a character
%% * 1 7-bit ascii, not \ or '
%% * 2-4 8-bit values (UTF8)
%% * \ followed by a known modifier [aernrtv]
%% * \xhh
%% * \x{hhh...}
CHAR = "'(([\\x00-\\x26\\x28-\\x5b\\x5d-\\x7f])|([\\x00-\\xff][\\x80-\\xff]{1,3})|(\\\\[befnrtv'\\\\])|(\\\\x[0-9a-fA-F]{2,2})|(\\\\x\\{[0-9a-fA-F]*\\}))'",
STRING = "\"([^\"\\\\]|(\\\\.))*\"",
CommentStart = {"/\\*", push(comment, skip())},
@@ -38,7 +44,9 @@ lexer() ->
, {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace"],
"stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace",
"interface", "main", "using", "as", "for", "hiding"
],
KW = string:join(Keywords, "|"),
Rules =
@@ -77,34 +85,34 @@ scan(String) ->
%% -- Helpers ----------------------------------------------------------------
parse_string([$" | Chars]) ->
unescape(Chars).
unicode:characters_to_nfc_binary(unescape(Chars)).
parse_char([$', $\\, Code, $']) ->
case Code of
$' -> $';
$\\ -> $\\;
$b -> $\b;
$e -> $\e;
$f -> $\f;
$n -> $\n;
$r -> $\r;
$t -> $\t;
$v -> $\v;
_ -> {error, "Bad control sequence: \\" ++ [Code]}
end;
parse_char([$', C, $']) -> C.
parse_char([$' | Chars]) ->
case unicode:characters_to_nfc_list(unescape($', Chars, [])) of
[Char] -> Char;
_Bad -> {error, "Bad character literal: '" ++ Chars}
end.
unescape(Str) -> unescape(Str, []).
utf8_encode(Cs) ->
binary_to_list(unicode:characters_to_binary(Cs)).
unescape([$"], Acc) ->
unescape(Str) -> unescape($", Str, []).
unescape(Delim, [Delim], Acc) ->
list_to_binary(lists:reverse(Acc));
unescape([$\\, $x, D1, D2 | Chars ], Acc) ->
unescape(Delim, [$\\, $x, ${ | Chars ], Acc) ->
{Ds, [_ | Cs]} = lists:splitwith(fun($}) -> false ; (_) -> true end, Chars),
C = list_to_integer(Ds, 16),
Utf8Cs = binary_to_list(unicode:characters_to_binary([C])),
unescape(Delim, Cs, [Utf8Cs | Acc]);
unescape(Delim, [$\\, $x, D1, D2 | Chars ], Acc) ->
C = list_to_integer([D1, D2], 16),
unescape(Chars, [C | Acc]);
unescape([$\\, Code | Chars], Acc) ->
Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
Utf8Cs = binary_to_list(unicode:characters_to_binary([C])),
unescape(Delim, Chars, [Utf8Cs | Acc]);
unescape(Delim, [$\\, Code | Chars], Acc) ->
Ok = fun(C) -> unescape(Delim, Chars, [C | Acc]) end,
case Code of
$" -> Ok($");
Delim -> Ok(Delim);
$\\ -> Ok($\\);
$b -> Ok($\b);
$e -> Ok($\e);
@@ -115,8 +123,8 @@ unescape([$\\, Code | Chars], Acc) ->
$v -> Ok($\v);
_ -> error("Bad control sequence: \\" ++ [Code]) %% TODO
end;
unescape([C | Chars], Acc) ->
unescape(Chars, [C | Acc]).
unescape(Delim, [C | Chars], Acc) ->
unescape(Delim, Chars, [C | Acc]).
strip_underscores(S) ->
lists:filter(fun(C) -> C /= $_ end, S).
+20 -7
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]).
@@ -25,7 +25,8 @@
-type ann_origin() :: system | user.
-type ann_format() :: '?:' | hex | infix | prefix | elif.
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
| stateful | private | payable | main | interface | entrypoint].
-type name() :: string().
-type id() :: {id, ann(), name()}.
@@ -34,13 +35,20 @@
-type qcon() :: {qcon, ann(), [name()]}.
-type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]}
-type namespace_alias() :: none | con().
-type namespace_parts() :: none | {for, [id()]} | {hiding, [id()]}.
-type decl() :: {contract_main, ann(), con(), [con()], [decl()]}
| {contract_child, ann(), con(), [con()], [decl()]}
| {contract_interface, ann(), con(), [con()], [decl()]}
| {namespace, ann(), con(), [decl()]}
| {include, ann(), {string, ann(), string()}}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} % Only for error msgs
| {type_def, ann(), id(), [tvar()], typedef()}
| {fun_clauses, ann(), id(), type(), [letfun() | fundecl()]}
| {block, ann(), [decl()]}
| {using, ann(), con(), namespace_alias(), namespace_parts()}
| fundecl()
| letfun()
| letval(). % Only for error msgs
@@ -49,9 +57,12 @@
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type guard() :: expr().
-type guarded_expr() :: {guarded, ann(), [guard()], expr()}.
-type letval() :: {letval, ann(), pat(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}.
-type letfun() :: {letfun, ann(), id(), [pat()], type(), [guarded_expr(),...]}.
-type letpat() :: {letpat, ann(), id(), pat()}.
-type fundecl() :: {fun_decl, ann(), id(), type()}.
-type letbind()
@@ -95,7 +106,7 @@
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '||' | '&&' | '..'.
| '||' | '&&' | '..' | '|>'.
-type un_op() :: '-' | '!'.
-type expr()
@@ -116,7 +127,8 @@
| {block, ann(), [stmt()]}
| {op(), ann()}
| id() | qid() | con() | qcon()
| constant().
| constant()
| letpat().
-type record_or_map() :: record | map | record_or_map_error.
@@ -137,7 +149,7 @@
-type stmt() :: letbind()
| expr().
-type alt() :: {'case', ann(), pat(), expr()}.
-type alt() :: {'case', ann(), pat(), [guarded_expr(),...]}.
-type lvalue() :: nonempty_list(elim()).
@@ -150,6 +162,7 @@
| {list, ann(), [pat()]}
| {typed, ann(), pat(), type()}
| {record, ann(), [field(pat())]}
| letpat()
| constant()
| con()
| id().
+15 -12
View File
@@ -31,25 +31,27 @@
| aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt().
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
ExprKind = if K == bind_expr -> bind_expr; true -> expr end,
TypeKind = if K == bind_type -> bind_type; true -> type end,
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end,
Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
[A | As] -> Scoped(Same(A), Same(As));
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ [E])]);
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, P, E} -> Scoped(BindExpr(P), Expr(E));
{letfun, _, F, Xs, T, GEs} -> Sum([BindExpr(F), Type(T), Expr(Xs ++ GEs)]);
{fun_clauses, _, _, T, Cs} -> Sum([Type(T) | [Decl(C) || C <- Cs]]);
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
@@ -88,13 +90,15 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
{map_get, _, A, B} -> Expr([A, B]);
{map_get, _, A, B, C} -> Expr([A, B, C]);
{block, _, Ss} -> Expr(Ss);
{letpat, _, X, P} -> Plus(BindExpr(X), Expr(P));
{guarded, _, Gs, E} -> Expr([E | Gs]);
%% field()
{field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]);
%% arg()
{arg, _, Y, T} -> Plus(BindExpr(Y), Type(T));
%% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
{'case', _, P, GEs} -> Scoped(BindExpr(P), Expr(GEs));
%% elim()
{proj, _, _} -> Zero;
{map_get, _, E} -> Expr(E);
@@ -153,4 +157,3 @@ used(D) ->
(_, _) -> #{}
end, decl, D)),
lists:filter(NotBound, Xs).
+17
View File
@@ -0,0 +1,17 @@
-module(aeso_tc_ann_manip).
-export([ pos/1
, pos/2
, loc/1
]).
src_file(T) -> aeso_syntax:get_ann(file, T, no_file).
include_type(T) -> aeso_syntax:get_ann(include_type, T, none).
line_number(T) -> aeso_syntax:get_ann(line, T, 0).
column_number(T) -> aeso_syntax:get_ann(col, T, 0).
pos(T) -> aeso_errors:pos(src_file(T), line_number(T), column_number(T)).
pos(L, C) -> aeso_errors:pos(L, C).
loc(T) ->
{src_file(T), include_type(T), line_number(T), column_number(T)}.
+593
View File
@@ -0,0 +1,593 @@
-module(aeso_tc_constraints).
-export([ solve_constraints/1
, solve_then_destroy_and_report_unsolved_constraints/1
, create_constraints/0
, add_is_contract_constraint/2
, add_is_contract_constraint/3
, add_aens_resolve_constraint/1
, add_oracle_type_constraint/2
, add_named_argument_constraint/3
, add_field_constraint/5
, add_dependent_type_constraint/5
, add_record_create_constraint/3
, freshen_type/2
, freshen_type_sig/2
]).
%% -- Duplicated types -------------------------------------------------------
-type uvar() :: {uvar, aeso_syntax:ann(), reference()}.
-type named_args_t() :: uvar() | [{named_arg_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), aeso_syntax:expr()}].
-type utype() :: aeso_tc_typedefs:utype().
%% -- Duplicated macros ------------------------------------------------------
-define(is_type_id(T), element(1, T) =:= id orelse
element(1, T) =:= qid orelse
element(1, T) =:= con orelse
element(1, T) =:= qcon).
%% -- Moved functions --------------------------------------------------------
unify(A, B, C, D) -> aeso_tc_unify:unify(A, B, C, D).
%% -------
unfold_types_in_type(A, B) -> aeso_tc_type_unfolding:unfold_types_in_type(A, B).
%% -------
qname(A) -> aeso_tc_name_manip:qname(A).
%% -------
type_error(A) -> aeso_tc_errors:type_error(A).
%% -------
is_monomorphic(A) -> aeso_tc_type_utils:is_monomorphic(A).
is_first_order(A) -> aeso_tc_type_utils:is_first_order(A).
app_t(A, B, C) -> aeso_tc_type_utils:app_t(A, B, C).
fresh_uvar(A) -> aeso_tc_type_utils:fresh_uvar(A).
%% ---------------------------------------------------------------------------
-type env() :: aeso_tc_env:env().
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
| {var_args, aeso_syntax:ann(), aeso_syntax:expr()}
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
-record(named_argument_constraint,
{args :: named_args_t(),
name :: aeso_syntax:id(),
type :: utype()}).
-record(dependent_type_constraint,
{ named_args_t :: named_args_t()
, named_args :: [aeso_syntax:arg_expr()]
, general_type :: utype()
, specialized_type :: utype()
, context :: term() }).
-type named_argument_constraint() :: #named_argument_constraint{} | #dependent_type_constraint{}.
-record(field_constraint,
{ record_t :: utype()
, field :: aeso_syntax:id()
, field_t :: utype()
, kind :: project | create | update %% Projection constraints can match contract
, context :: why_record() }). %% types, but field constraints only record types.
%% Constraint checking that 'record_t' has precisely 'fields'.
-record(record_create_constraint,
{ record_t :: utype()
, fields :: [aeso_syntax:id()]
, context :: why_record() }).
-record(is_contract_constraint,
{ contract_t :: utype(),
context :: {contract_literal, aeso_syntax:expr()} |
{address_to_contract, aeso_syntax:ann()} |
{bytecode_hash, aeso_syntax:ann()} |
{var_args, aeso_syntax:ann(), aeso_syntax:expr()},
force_def = false :: boolean()
}).
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
-type byte_constraint() :: {is_bytes, utype()}
| {add_bytes, aeso_syntax:ann(), concat | split, utype(), utype(), utype()}.
-type aens_resolve_constraint() :: {aens_resolve_type, utype()}.
-type oracle_type_constraint() :: {oracle_type, aeso_syntax:ann(), utype()}.
-type constraint() :: named_argument_constraint() | field_constraint() | byte_constraint()
| aens_resolve_constraint() | oracle_type_constraint().
-spec add_constraint(constraint()) -> true.
add_constraint(Constraint) ->
aeso_tc_ets_manager:ets_insert_ordered(constraints, Constraint).
add_is_contract_constraint(ContractT, Context) ->
add_constraint(
#is_contract_constraint{
contract_t = ContractT,
context = Context }).
add_is_contract_constraint(ContractT, Context, ForceDef) ->
add_constraint(
#is_contract_constraint{
contract_t = ContractT,
context = Context,
force_def = ForceDef }).
add_aens_resolve_constraint(Type) ->
add_constraint({aens_resolve_type, Type}).
add_oracle_type_constraint(Ann, Type) ->
add_constraint({oracle_type, Ann, Type}).
add_named_argument_constraint(Args, Name, Type) ->
add_constraint(
#named_argument_constraint{
args = Args,
name = Name,
type = Type }).
add_field_constraint(RecordT, Field, FieldT, Kind, Context) ->
add_constraint(#field_constraint{
record_t = RecordT,
field = Field,
field_t = FieldT,
kind = Kind,
context = Context }).
add_dependent_type_constraint(NamedArgsT, NamedArgs, GeneralType, SpecializedType, Context) ->
add_constraint(#dependent_type_constraint{
named_args_t = NamedArgsT,
named_args = NamedArgs,
general_type = GeneralType,
specialized_type = SpecializedType,
context = Context }).
add_record_create_constraint(RecordT, Fields, Context) ->
add_constraint(#record_create_constraint{
record_t = RecordT,
fields = Fields,
context = Context }).
create_constraints() ->
aeso_tc_ets_manager:ets_new(constraints, [ordered_set]).
get_constraints() ->
aeso_tc_ets_manager:ets_tab2list_ordered(constraints).
destroy_constraints() ->
aeso_tc_ets_manager:ets_delete(constraints).
-spec solve_constraints(env()) -> ok.
solve_constraints(Env) ->
%% First look for record fields that appear in only one type definition
IsAmbiguous =
fun(#field_constraint{
record_t = RecordType,
field = Field={id, _Attrs, FieldName},
field_t = FieldType,
kind = Kind,
context = When }) ->
Arity = aeso_tc_type_utils:fun_arity(aeso_tc_type_utils:dereference_deep(FieldType)),
FieldInfos = case Arity of
none -> aeso_tc_env:lookup_record_field(Env, FieldName, Kind);
_ -> aeso_tc_env:lookup_record_field_arity(Env, FieldName, Arity, Kind)
end,
case FieldInfos of
[] ->
type_error({undefined_field, Field}),
false;
[Fld] ->
FldType = aeso_tc_env:field_info_field_t(Fld),
RecType = aeso_tc_env:field_info_record_t(Fld),
create_freshen_tvars(),
FreshFldType = freshen(FldType),
FreshRecType = freshen(RecType),
destroy_freshen_tvars(),
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
unify(Env, FreshRecType, RecordType, {record_constraint, FreshRecType, RecordType, When}),
false;
_ ->
%% ambiguity--need cleverer strategy
true
end;
(_) -> true
end,
AmbiguousConstraints = lists:filter(IsAmbiguous, get_constraints()),
% The two passes on AmbiguousConstraints are needed
solve_ambiguous_constraints(Env, AmbiguousConstraints ++ AmbiguousConstraints).
-spec solve_ambiguous_constraints(env(), [constraint()]) -> ok.
solve_ambiguous_constraints(Env, Constraints) ->
Unknown = solve_known_record_types(Env, Constraints),
if Unknown == [] -> ok;
length(Unknown) < length(Constraints) ->
%% progress! Keep trying.
solve_ambiguous_constraints(Env, Unknown);
true ->
case solve_unknown_record_types(Env, Unknown) of
true -> %% Progress!
solve_ambiguous_constraints(Env, Unknown);
_ -> ok %% No progress. Report errors later.
end
end.
solve_then_destroy_and_report_unsolved_constraints(Env) ->
solve_constraints(Env),
destroy_and_report_unsolved_constraints(Env).
destroy_and_report_unsolved_constraints(Env) ->
{FieldCs, OtherCs} =
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
get_constraints()),
{CreateCs, OtherCs1} =
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
OtherCs),
{ContractCs, OtherCs2} =
lists:partition(fun(#is_contract_constraint{}) -> true; (_) -> false end, OtherCs1),
{NamedArgCs, OtherCs3} =
lists:partition(fun(#dependent_type_constraint{}) -> true;
(#named_argument_constraint{}) -> true;
(_) -> false
end, OtherCs2),
{BytesCs, OtherCs4} =
lists:partition(fun({is_bytes, _}) -> true;
({add_bytes, _, _, _, _, _}) -> true;
(_) -> false
end, OtherCs3),
{AensResolveCs, OtherCs5} =
lists:partition(fun({aens_resolve_type, _}) -> true;
(_) -> false
end, OtherCs4),
{OracleTypeCs, []} =
lists:partition(fun({oracle_type, _, _}) -> true;
(_) -> false
end, OtherCs5),
Unsolved = [ S || S <- [ solve_constraint(Env, aeso_tc_type_utils:dereference_deep(C)) || C <- NamedArgCs ],
S == unsolved ],
[ type_error({unsolved_named_argument_constraint, Name, Type})
|| #named_argument_constraint{name = Name, type = Type} <- Unsolved ],
Unknown = solve_known_record_types(Env, FieldCs),
if Unknown == [] -> ok;
true ->
case solve_unknown_record_types(Env, Unknown) of
true -> ok;
Errors -> [ type_error(Err) || Err <- Errors ]
end
end,
check_record_create_constraints(Env, CreateCs),
check_is_contract_constraints(Env, ContractCs),
check_bytes_constraints(Env, BytesCs),
check_aens_resolve_constraints(Env, AensResolveCs),
check_oracle_type_constraints(Env, OracleTypeCs),
destroy_constraints().
%% If false, a type error has been emitted, so it's safe to drop the constraint.
-spec check_named_argument_constraint(env(), named_argument_constraint()) -> true | false | unsolved.
check_named_argument_constraint(_Env, #named_argument_constraint{ args = {uvar, _, _} }) ->
unsolved;
check_named_argument_constraint(Env,
#named_argument_constraint{ args = Args,
name = Id = {id, _, Name},
type = Type }) ->
case [ T || {named_arg_t, _, {id, _, Name1}, T, _} <- Args, Name1 == Name ] of
[] ->
type_error({bad_named_argument, Args, Id}),
false;
[T] -> unify(Env, T, Type, {check_named_arg_constraint, Args, Id, Type}), true
end;
check_named_argument_constraint(Env,
#dependent_type_constraint{ named_args_t = NamedArgsT0,
named_args = NamedArgs,
general_type = GenType,
specialized_type = SpecType,
context = {check_return, App} }) ->
NamedArgsT = aeso_tc_type_utils:dereference(NamedArgsT0),
case aeso_tc_type_utils:dereference(NamedArgsT0) of
[_ | _] = NamedArgsT ->
GetVal = fun(Name, Default) ->
hd([ Val || {named_arg, _, {id, _, N}, Val} <- NamedArgs, N == Name] ++
[ Default ])
end,
ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)}
|| {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]),
GenType1 = specialize_dependent_type(ArgEnv, GenType),
unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}),
true;
_ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true
end.
specialize_dependent_type(Env, Type) ->
case aeso_tc_type_utils:dereference(Type) of
{if_t, _, {id, _, Arg}, Then, Else} ->
Val = maps:get(Arg, Env),
case Val of
{typed, _, {bool, _, true}, _} -> Then;
{typed, _, {bool, _, false}, _} -> Else;
_ ->
type_error({named_argument_must_be_literal_bool, Arg, Val}),
fresh_uvar(aeso_syntax:get_ann(Val))
end;
_ -> Type %% Currently no deep dependent types
end.
%% -- Bytes constraints --
solve_constraint(_Env, #field_constraint{record_t = {uvar, _, _}}) ->
not_solved;
solve_constraint(Env, C = #field_constraint{record_t = RecType,
field = FieldName,
field_t = FieldType,
context = When}) ->
RecId = record_type_name(RecType),
Attrs = aeso_syntax:get_ann(RecId),
case aeso_tc_env:lookup_type(Env, RecId) of
{_, {_Ann, {Formals, {What, Fields}}}} when What =:= record_t; What =:= contract_t ->
FieldTypes = [{Name, Type} || {field_t, _, {id, _, Name}, Type} <- Fields],
{id, _, FieldString} = FieldName,
case proplists:get_value(FieldString, FieldTypes) of
undefined ->
type_error({missing_field, FieldName, RecId}),
not_solved;
FldType ->
create_freshen_tvars(),
FreshFldType = freshen(FldType),
FreshRecType = freshen(app_t(Attrs, RecId, Formals)),
destroy_freshen_tvars(),
unify(Env, FreshFldType, FieldType, {field_constraint, FreshFldType, FieldType, When}),
unify(Env, FreshRecType, RecType, {record_constraint, FreshRecType, RecType, When}),
C
end;
_ ->
type_error({not_a_record_type, aeso_tc_type_utils:instantiate(RecType), When}),
not_solved
end;
solve_constraint(Env, C = #dependent_type_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(Env, C = #named_argument_constraint{}) ->
check_named_argument_constraint(Env, C);
solve_constraint(_Env, {is_bytes, _}) -> ok;
solve_constraint(Env, {add_bytes, Ann, _, A0, B0, C0}) ->
A = unfold_types_in_type(Env, aeso_tc_type_utils:dereference(A0)),
B = unfold_types_in_type(Env, aeso_tc_type_utils:dereference(B0)),
C = unfold_types_in_type(Env, aeso_tc_type_utils:dereference(C0)),
case {A, B, C} of
{{bytes_t, _, M}, {bytes_t, _, N}, _} -> unify(Env, {bytes_t, Ann, M + N}, C, {at, Ann});
{{bytes_t, _, M}, _, {bytes_t, _, R}} when R >= M -> unify(Env, {bytes_t, Ann, R - M}, B, {at, Ann});
{_, {bytes_t, _, N}, {bytes_t, _, R}} when R >= N -> unify(Env, {bytes_t, Ann, R - N}, A, {at, Ann});
_ -> ok
end;
solve_constraint(_, _) -> ok.
check_bytes_constraints(Env, Constraints) ->
InAddConstraint = [ T || {add_bytes, _, _, A, B, C} <- Constraints,
T <- [A, B, C],
element(1, T) /= bytes_t ],
%% Skip is_bytes constraints for types that occur in add_bytes constraints
%% (no need to generate error messages for both is_bytes and add_bytes).
Skip = fun({is_bytes, T}) -> lists:member(T, InAddConstraint);
(_) -> false end,
[ check_bytes_constraint(Env, C) || C <- Constraints, not Skip(C) ].
check_bytes_constraint(Env, {is_bytes, Type}) ->
Type1 = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(Type)),
case Type1 of
{bytes_t, _, _} -> ok;
_ ->
type_error({unknown_byte_length, Type})
end;
check_bytes_constraint(Env, {add_bytes, Ann, Fun, A0, B0, C0}) ->
A = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(A0)),
B = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(B0)),
C = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(C0)),
case {A, B, C} of
{{bytes_t, _, _M}, {bytes_t, _, _N}, {bytes_t, _, _R}} ->
ok; %% If all are solved we checked M + N == R in solve_constraint.
_ -> type_error({unsolved_bytes_constraint, Ann, Fun, A, B, C})
end.
check_aens_resolve_constraints(_Env, []) ->
ok;
check_aens_resolve_constraints(Env, [{aens_resolve_type, Type} | Rest]) ->
Type1 = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(Type)),
{app_t, _, {id, _, "option"}, [Type2]} = Type1,
case Type2 of
{id, _, "string"} -> ok;
{id, _, "address"} -> ok;
{con, _, _} -> ok;
{app_t, _, {id, _, "oracle"}, [_, _]} -> ok;
{app_t, _, {id, _, "oracle_query"}, [_, _]} -> ok;
_ -> type_error({invalid_aens_resolve_type, aeso_syntax:get_ann(Type), Type2})
end,
check_aens_resolve_constraints(Env, Rest).
check_oracle_type_constraints(_Env, []) ->
ok;
check_oracle_type_constraints(Env, [{oracle_type, Ann, OType} | Rest]) ->
Type = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(OType)),
{app_t, _, {id, _, "oracle"}, [QType, RType]} = Type,
is_monomorphic(QType) orelse type_error({invalid_oracle_type, polymorphic, query, Ann, Type}),
is_monomorphic(RType) orelse type_error({invalid_oracle_type, polymorphic, response, Ann, Type}),
is_first_order(QType) orelse type_error({invalid_oracle_type, higher_order, query, Ann, Type}),
is_first_order(RType) orelse type_error({invalid_oracle_type, higher_order, response, Ann, Type}),
check_oracle_type_constraints(Env, Rest).
%% -- Field constraints --
check_record_create_constraints(_, []) -> ok;
check_record_create_constraints(Env, [C | Cs]) ->
#record_create_constraint{
record_t = Type,
fields = Fields,
context = When } = C,
Type1 = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(Type)),
try aeso_tc_env:lookup_type(Env, record_type_name(Type1)) of
{_QId, {_Ann, {_Args, {record_t, RecFields}}}} ->
ActualNames = [ Fld || {field_t, _, {id, _, Fld}, _} <- RecFields ],
GivenNames = [ Fld || {id, _, Fld} <- Fields ],
case ActualNames -- GivenNames of %% We know already that we don't have too many fields
[] -> ok;
Missing -> type_error({missing_fields, When, Type1, Missing})
end;
_ -> %% We can get here if there are other type errors.
ok
catch _:_ -> %% Might be unsolved, we get a different error in that case
ok
end,
check_record_create_constraints(Env, Cs).
is_contract_defined(C) ->
aeso_tc_ets_manager:ets_lookup(defined_contracts, qname(C)) =/= [].
check_is_contract_constraints(_Env, []) -> ok;
check_is_contract_constraints(Env, [C | Cs]) ->
#is_contract_constraint{ contract_t = Type, context = Cxt, force_def = ForceDef } = C,
Type1 = unfold_types_in_type(Env, aeso_tc_type_utils:instantiate(Type)),
TypeName = record_type_name(Type1),
case aeso_tc_env:lookup_type(Env, TypeName) of
{_, {_Ann, {[], {contract_t, _}}}} ->
case not ForceDef orelse is_contract_defined(TypeName) of
true -> ok;
false -> type_error({contract_lacks_definition, Type1, Cxt})
end;
_ -> type_error({not_a_contract_type, Type1, Cxt})
end,
check_is_contract_constraints(Env, Cs).
-spec solve_unknown_record_types(env(), [field_constraint()]) -> true | [tuple()].
solve_unknown_record_types(Env, Unknown) ->
UVars = lists:usort([UVar || #field_constraint{record_t = UVar = {uvar, _, _}} <- Unknown]),
Solutions = [solve_for_uvar(Env, UVar, [{Kind, When, Field}
|| #field_constraint{record_t = U, field = Field, kind = Kind, context = When} <- Unknown,
U == UVar])
|| UVar <- UVars],
case lists:member(true, Solutions) of
true -> true;
false -> Solutions
end.
%% This will solve all kinds of constraints but will only return the
%% unsolved field constraints
-spec solve_known_record_types(env(), [constraint()]) -> [field_constraint()].
solve_known_record_types(Env, Constraints) ->
DerefConstraints = lists:map(fun(C = #field_constraint{record_t = RecordType}) ->
C#field_constraint{record_t = aeso_tc_type_utils:dereference(RecordType)};
(C) -> aeso_tc_type_utils:dereference_deep(C)
end, Constraints),
SolvedConstraints = lists:map(fun(C) -> solve_constraint(Env, aeso_tc_type_utils:dereference_deep(C)) end, DerefConstraints),
Unsolved = DerefConstraints--SolvedConstraints,
lists:filter(fun(#field_constraint{}) -> true; (_) -> false end, Unsolved).
record_type_name({app_t, _Attrs, RecId, _Args}) when ?is_type_id(RecId) ->
RecId;
record_type_name(RecId) when ?is_type_id(RecId) ->
RecId;
record_type_name(_Other) ->
%% io:format("~p is not a record type\n", [Other]),
{id, [{origin, system}], "not_a_record_type"}.
solve_for_uvar(Env, UVar = {uvar, Attrs, _}, Fields0) ->
Fields = [{Kind, Fld} || {Kind, _, Fld} <- Fields0],
[{_, When, _} | _] = Fields0, %% Get the location from the first field
%% If we have 'create' constraints they must be complete.
Covering = lists:usort([ Name || {create, {id, _, Name}} <- Fields ]),
%% Does this set of fields uniquely identify a record type?
FieldNames = [ Name || {_Kind, {id, _, Name}} <- Fields ],
UniqueFields = lists:usort(FieldNames),
Candidates = [aeso_tc_env:field_info_record_t(Fld) || Fld <- aeso_tc_env:lookup_record_field(Env, hd(FieldNames))],
TypesAndFields = [case aeso_tc_env:lookup_type(Env, record_type_name(RecType)) of
{_, {_, {_, {record_t, RecFields}}}} ->
{RecType, [Field || {field_t, _, {id, _, Field}, _} <- RecFields]};
{_, {_, {_, {contract_t, ConFields}}}} ->
%% TODO: is this right?
{RecType, [Field || {field_t, _, {id, _, Field}, _} <- ConFields]};
false -> %% impossible?
error({no_definition_for, record_type_name(RecType), in, Env})
end
|| RecType <- Candidates],
PartialSolutions =
lists:sort([{RecType, if Covering == [] -> []; true -> RecFields -- Covering end}
|| {RecType, RecFields} <- TypesAndFields,
UniqueFields -- RecFields == []]),
Solutions = [RecName || {RecName, []} <- PartialSolutions],
case {Solutions, PartialSolutions} of
{[], []} ->
{no_records_with_all_fields, Fields};
{[], _} ->
case PartialSolutions of
[{RecType, Missing} | _] -> %% TODO: better error if ambiguous
{missing_fields, When, RecType, Missing}
end;
{[RecType], _} ->
RecName = record_type_name(RecType),
{_, {_, {Formals, {_RecOrCon, _}}}} = aeso_tc_env:lookup_type(Env, RecName),
create_freshen_tvars(),
FreshRecType = freshen(app_t(Attrs, RecName, Formals)),
destroy_freshen_tvars(),
unify(Env, UVar, FreshRecType, {solve_rec_type, UVar, Fields}),
true;
{StillPossible, _} ->
{ambiguous_record, Fields, StillPossible}
end.
create_freshen_tvars() ->
aeso_tc_ets_manager:ets_new(freshen_tvars, [set]).
destroy_freshen_tvars() ->
aeso_tc_ets_manager:ets_delete(freshen_tvars).
freshen(Type) ->
freshen(aeso_syntax:get_ann(Type), Type).
freshen(Ann, {tvar, _, Name}) ->
NewT = case aeso_tc_ets_manager:ets_lookup(freshen_tvars, Name) of
[] -> fresh_uvar(Ann);
[{Name, T}] -> T
end,
aeso_tc_ets_manager:ets_insert(freshen_tvars, {Name, NewT}),
NewT;
freshen(Ann, {bytes_t, _, any}) ->
X = fresh_uvar(Ann),
add_constraint({is_bytes, X}),
X;
freshen(Ann, T) when is_tuple(T) ->
list_to_tuple(freshen(Ann, tuple_to_list(T)));
freshen(Ann, [A | B]) ->
[freshen(Ann, A) | freshen(Ann, B)];
freshen(_, X) ->
X.
freshen_type(Ann, Type) ->
create_freshen_tvars(),
Type1 = freshen(Ann, Type),
destroy_freshen_tvars(),
Type1.
freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) ->
FunT = freshen_type(Ann, aeso_tc_type_utils:typesig_to_fun_t(TypeSig)),
apply_typesig_constraint(Ann, Constr, FunT),
FunT.
apply_typesig_constraint(_Ann, none, _FunT) -> ok;
apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
aeso_tc_constraints:add_is_contract_constraint(Type, {address_to_contract, Ann});
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
add_constraint({add_bytes, Ann, concat, A, B, C});
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
add_constraint({add_bytes, Ann, split, A, B, C});
apply_typesig_constraint(Ann, bytecode_hash, {fun_t, _, _, [Con], _}) ->
aeso_tc_constraints:add_is_contract_constraint(Con, {bytecode_hash, Ann}).
+138
View File
@@ -0,0 +1,138 @@
-module(aeso_tc_desugar).
-export([ desugar/1
, desugar_clauses/4
, process_blocks/1
]).
%% -- Moved functions --------------------------------------------------------
type_error(A) -> aeso_tc_errors:type_error(A).
%% ---------------------------------------------------------------------------
%% Restructure blocks into multi-clause fundefs (`fun_clauses`).
-spec process_blocks([aeso_syntax:decl()]) -> [aeso_syntax:decl()].
process_blocks(Decls) ->
lists:flatmap(
fun({block, Ann, Ds}) -> process_block(Ann, Ds);
(Decl) -> [Decl] end, Decls).
-spec process_block(aeso_syntax:ann(), [aeso_syntax:decl()]) -> [aeso_syntax:decl()].
process_block(_, []) -> [];
process_block(_, [Decl]) -> [Decl];
process_block(_Ann, [Decl | Decls]) ->
IsThis = fun(Name) -> fun({letfun, _, {id, _, Name1}, _, _, _}) -> Name == Name1;
(_) -> false end end,
case Decl of
{fun_decl, Ann1, Id = {id, _, Name}, Type} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), Decls),
[type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
[{fun_clauses, Ann1, Id, Type, Clauses}];
{letfun, Ann1, Id = {id, _, Name}, _, _, _} ->
{Clauses, Rest} = lists:splitwith(IsThis(Name), [Decl | Decls]),
[type_error({mismatched_decl_in_funblock, Name, D1}) || D1 <- Rest],
[{fun_clauses, Ann1, Id, {id, [{origin, system} | Ann1], "_"}, Clauses}]
end.
desugar_clauses(Ann, Fun, {type_sig, _, _, _, ArgTypes, RetType}, Clauses) ->
NeedDesugar =
case Clauses of
[{letfun, _, _, As, _, [{guarded, _, [], _}]}] -> lists:any(fun({typed, _, {id, _, _}, _}) -> false; (_) -> true end, As);
_ -> true
end,
case NeedDesugar of
false -> [Clause] = Clauses, Clause;
true ->
NoAnn = [{origin, system}],
Args = [ {typed, NoAnn, {id, NoAnn, "x#" ++ integer_to_list(I)}, Type}
|| {I, Type} <- indexed(1, ArgTypes) ],
Tuple = fun([X]) -> X;
(As) -> {typed, NoAnn, {tuple, NoAnn, As}, {tuple_t, NoAnn, ArgTypes}}
end,
{letfun, Ann, Fun, Args, RetType, [{guarded, NoAnn, [], {typed, NoAnn,
{switch, NoAnn, Tuple(Args),
[ {'case', AnnC, Tuple(ArgsC), GuardedBodies}
|| {letfun, AnnC, _, ArgsC, _, GuardedBodies} <- Clauses ]}, RetType}}]}
end.
%% -- Pre-type checking desugaring -------------------------------------------
%% Desugars nested record/map updates as follows:
%% { x.y = v1, x.z @ z = f(z) } becomes { x @ __x = __x { y = v1, z @ z = f(z) } }
%% { [k1].x = v1, [k2].y = v2 } becomes { [k1] @ __x = __x { x = v1 }, [k2] @ __x = __x { y = v2 } }
%% There's no comparison of k1 and k2 to group the updates if they are equal.
desugar({record, Ann, Rec, Updates}) ->
{record, Ann, Rec, desugar_updates(Updates)};
desugar({map, Ann, Map, Updates}) ->
{map, Ann, Map, desugar_updates(Updates)};
desugar([H|T]) ->
[desugar(H) | desugar(T)];
desugar(T) when is_tuple(T) ->
list_to_tuple(desugar(tuple_to_list(T)));
desugar(X) -> X.
desugar_updates([]) -> [];
desugar_updates([Upd | Updates]) ->
{Key, MakeField, Rest} = update_key(Upd),
{More, Updates1} = updates_key(Key, Updates),
%% Check conflicts
case length([ [] || [] <- [Rest | More] ]) of
N when N > 1 -> type_error({conflicting_updates_for_field, Upd, Key});
_ -> ok
end,
[MakeField(lists:append([Rest | More])) | desugar_updates(Updates1)].
%% TODO: refactor representation to make this not horrible
update_key(Fld = {field, _, [Elim], _}) ->
{elim_key(Elim), fun(_) -> Fld end, []};
update_key(Fld = {field, _, [Elim], _, _}) ->
{elim_key(Elim), fun(_) -> Fld end, []};
update_key({field, Ann, [P = {proj, _, {id, _, Name}} | Rest], Value}) ->
{Name, fun(Flds) -> {field, Ann, [P], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Value}]};
update_key({field, Ann, [P = {proj, _, {id, _, Name}} | Rest], Id, Value}) ->
{Name, fun(Flds) -> {field, Ann, [P], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Id, Value}]};
update_key({field, Ann, [K = {map_get, _, _} | Rest], Value}) ->
{map_key, fun(Flds) -> {field, Ann, [K], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Value}]};
update_key({field, Ann, [K = {map_get, _, _, _} | Rest], Value}) ->
{map_key, fun(Flds) -> {field, Ann, [K], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Value}]};
update_key({field, Ann, [K = {map_get, _, _, _} | Rest], Id, Value}) ->
{map_key, fun(Flds) -> {field, Ann, [K], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Id, Value}]};
update_key({field, Ann, [K = {map_get, _, _} | Rest], Id, Value}) ->
{map_key, fun(Flds) -> {field, Ann, [K], {id, [], "__x"},
desugar(map_or_record(Ann, {id, [], "__x"}, Flds))}
end, [{field, Ann, Rest, Id, Value}]}.
map_or_record(Ann, Val, Flds = [Fld | _]) ->
Kind = case element(3, Fld) of
[{proj, _, _} | _] -> record;
[{map_get, _, _} | _] -> map;
[{map_get, _, _, _} | _] -> map
end,
{Kind, Ann, Val, Flds}.
elim_key({proj, _, {id, _, Name}}) -> Name;
elim_key({map_get, _, _, _}) -> map_key; %% no grouping on map keys (yet)
elim_key({map_get, _, _}) -> map_key.
updates_key(map_key, Updates) -> {[], Updates};
updates_key(Name, Updates) ->
Xs = [ {Upd, Name1 == Name, Rest}
|| Upd <- Updates,
{Name1, _, Rest} <- [update_key(Upd)] ],
Updates1 = [ Upd || {Upd, false, _} <- Xs ],
More = [ Rest || {_, true, Rest} <- Xs ],
{More, Updates1}.
indexed(I, Xs) ->
lists:zip(lists:seq(I, I + length(Xs) - 1), Xs).
+941
View File
@@ -0,0 +1,941 @@
-module(aeso_tc_env).
%% Getters
-export([ contract_parents/1
, current_function/1
, in_guard/1
, in_pattern/1
, namespace/1
, stateful/1
, typevars/1
, unify_throws/1
, used_namespaces/1
, vars/1
, what/1
]).
-export([ field_info_field_t/1
, field_info_record_t/1
]).
-export([ scope_ann/1
, scope_consts/1
, scope_funs/1
, scope_kind/1
]).
%% Setters
-export([ set_contract_parents/2
, set_current_const/2
, set_current_function/2
, set_in_guard/2
, set_in_pattern/2
, set_stateful/2
, set_used_namespaces/2
, set_what/2
]).
-export([ push_scope/3
, pop_scope/1
, get_scope/2
, get_current_scope/1
, on_scopes/2
, switch_scope/2
, bind_var/3
, bind_vars/2
, bind_contract/3
, bind_state/1
, bind_fun/3
, bind_funs/2
, bind_tvars/2
, bind_type/4
, bind_const/4
, bind_fields_append/4
]).
-export([ lookup_env/4
, lookup_type/2
, lookup_record_field/2
, lookup_record_field/3
, lookup_record_field_arity/4
]).
%% Env constructors
-export([ init_env/0
, init_env/1
, empty_env/0
]).
-export([destroy_and_report_type_errors/1]).
-export_type([env/0]).
-include("aeso_utils.hrl").
-record(field_info,
{ ann :: aeso_syntax:ann()
, field_t :: utype()
, record_t :: utype()
, kind :: contract | record }).
-type field_info() :: #field_info{}.
-type type_id() :: aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
-type typedef() :: {[aeso_syntax:tvar()], aeso_syntax:typedef() | {contract_t, [aeso_syntax:field_t()]}}
| {builtin, non_neg_integer()}.
-type namespace_alias() :: none | name().
-type namespace_parts() :: none | {for, [name()]} | {hiding, [name()]}.
-type used_namespaces() :: [{qname(), namespace_alias(), namespace_parts()}].
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}.
-type const_info() :: {aeso_syntax:ann(), type()}.
-type var_info() :: {aeso_syntax:ann(), utype()}.
-type fun_env() :: [{name(), fun_info()}].
-type type_env() :: [{name(), type_info()}].
-type const_env() :: [{name(), const_info()}].
-record(scope, { funs = [] :: fun_env()
, types = [] :: type_env()
, consts = [] :: const_env()
, kind = namespace :: namespace | contract
, ann = [{origin, system}] :: aeso_syntax:ann()
}).
-type scope() :: #scope{}.
-record(env,
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
, vars = [] :: [{name(), var_info()}]
, typevars = unrestricted :: unrestricted | [name()]
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
, contract_parents = #{} :: #{ name() => [name()] }
, namespace = [] :: qname()
, used_namespaces = [] :: used_namespaces()
, in_pattern = false :: boolean()
, in_guard = false :: boolean()
, stateful = false :: boolean()
, unify_throws = true :: boolean()
, current_const = none :: none | aeso_syntax:id()
, current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract | contract_interface
}).
-opaque env() :: #env{}.
%% -- Duplicated types -------------------------------------------------------
-type name() :: string().
-type qname() :: [string()].
-type type() :: aeso_syntax:type().
-type utype() :: aeso_tc_typedefs:utype().
-type typesig() :: aeso_tc_typedefs:typesig().
%% -- Duplicated macros ------------------------------------------------------
-define(CONSTRUCTOR_MOCK_NAME, "#__constructor__#").
%% -- Moved functions --------------------------------------------------------
name(A) -> aeso_tc_name_manip:name(A).
qname(A) -> aeso_tc_name_manip:qname(A).
qid(A, B) -> aeso_tc_name_manip:qid(A, B).
qcon(A, B) -> aeso_tc_name_manip:qcon(A, B).
%% -------
type_error(A) -> aeso_tc_errors:type_error(A).
%% -------
warn_potential_shadowing(A, B, C) -> aeso_tc_warnings:warn_potential_shadowing(A, B, C).
used_include(A) -> aeso_tc_warnings:used_include(A).
%% -------
get_option(A, B) -> aeso_tc_options:get_option(A, B).
when_warning(A, B) -> aeso_tc_options:when_warning(A, B).
%% -------
fresh_uvar(A) -> aeso_tc_type_utils:fresh_uvar(A).
%% -- Getters ------------------------------------------------------------
contract_parents(#env{contract_parents = ContractParents}) ->
ContractParents.
current_function(#env{current_function = CurrentFunction}) ->
CurrentFunction.
in_guard(#env{in_guard = InGuard}) ->
InGuard.
in_pattern(#env{in_pattern = InPattern}) ->
InPattern.
namespace(#env{namespace = Namespace}) ->
Namespace.
stateful(#env{stateful = Stateful}) ->
Stateful.
typevars(#env{typevars = Typevars}) ->
Typevars.
unify_throws(#env{unify_throws = UnifyThrows}) ->
UnifyThrows.
used_namespaces(#env{used_namespaces = UsedNamespaces}) ->
UsedNamespaces.
vars(#env{vars = Vars}) ->
Vars.
what(#env{what = What}) ->
What.
%% -- Field Info Getters -------------------------------------------------
field_info_field_t(#field_info{field_t = FieldT}) ->
FieldT.
field_info_record_t(#field_info{record_t = RecordT}) ->
RecordT.
%% -- Scope Getters ------------------------------------------------------
scope_ann(#scope{ann = Ann}) ->
Ann.
scope_consts(#scope{consts = Consts}) ->
Consts.
scope_funs(#scope{funs = Funs}) ->
Funs.
scope_kind(#scope{kind = Kind}) ->
Kind.
%% -- Setters ------------------------------------------------------------
set_contract_parents(ContractParents, Env) ->
Env#env{contract_parents = ContractParents}.
set_current_const(CurrentConst, Env) ->
Env#env{current_const = CurrentConst}.
set_current_function(CurrentFunction, Env) ->
Env#env{current_function = CurrentFunction}.
set_in_guard(InGuard, Env) ->
Env#env{in_guard = InGuard}.
set_in_pattern(InPattern, Env) ->
Env#env{in_pattern = InPattern}.
set_stateful(Stateful, Env) ->
Env#env{stateful = Stateful}.
set_used_namespaces(UsedNamespaces, Env) ->
Env#env{used_namespaces = UsedNamespaces}.
set_what(What, Env) ->
Env#env{what = What}.
%% -- Environment manipulation -----------------------------------------------
-spec switch_scope(qname(), env()) -> env().
switch_scope(Scope, Env) ->
Env#env{namespace = Scope}.
-spec push_scope(namespace | contract, aeso_syntax:con(), env()) -> env().
push_scope(Kind, Con, Env) ->
Ann = aeso_syntax:get_ann(Con),
Name = name(Con),
New = Env#env.namespace ++ [Name],
Env#env{ namespace = New, scopes = (Env#env.scopes)#{ New => #scope{ kind = Kind, ann = Ann } } }.
-spec pop_scope(env()) -> env().
pop_scope(Env) ->
Env#env{ namespace = lists:droplast(Env#env.namespace) }.
-spec get_scope(env(), qname()) -> false | scope().
get_scope(#env{ scopes = Scopes }, Name) ->
maps:get(Name, Scopes, false).
-spec get_current_scope(env()) -> scope().
get_current_scope(#env{ namespace = NS, scopes = Scopes }) ->
maps:get(NS, Scopes).
-spec on_current_scope(env(), fun((scope()) -> scope())) -> env().
on_current_scope(Env = #env{ namespace = NS, scopes = Scopes }, Fun) ->
Scope = get_current_scope(Env),
Env#env{ scopes = Scopes#{ NS => Fun(Scope) } }.
-spec on_scopes(env(), fun((scope()) -> scope())) -> env().
on_scopes(Env = #env{ scopes = Scopes }, Fun) ->
Env#env{ scopes = maps:map(fun(_, Scope) -> Fun(Scope) end, Scopes) }.
-spec bind_var(aeso_syntax:id(), utype(), env()) -> env().
bind_var({id, Ann, X}, T, Env) ->
when_warning(warn_shadowing, fun() -> warn_potential_shadowing(Env, Ann, X) end),
Env#env{ vars = [{X, {Ann, T}} | Env#env.vars] }.
-spec bind_vars([{aeso_syntax:id(), utype()}], env()) -> env().
bind_vars([], Env) -> Env;
bind_vars([{X, T} | Vars], Env) ->
bind_vars(Vars, bind_var(X, T, Env)).
-spec bind_tvars([aeso_syntax:tvar()], env()) -> env().
bind_tvars(Xs, Env) ->
Env#env{ typevars = [X || {tvar, _, X} <- Xs] }.
-spec bind_fun(name(), type() | typesig(), env()) -> env().
bind_fun(X, Type, Env) ->
case lookup_env(Env, term, [], [X]) of
false -> force_bind_fun(X, Type, Env);
{_QId, {Ann1, _}} ->
type_error({duplicate_definition, X, [Ann1, aeso_syntax:get_ann(Type)]}),
Env
end.
-spec force_bind_fun(name(), type() | typesig(), env()) -> env().
force_bind_fun(X, Type, Env = #env{ what = What }) ->
Ann = aeso_syntax:get_ann(Type),
NoCode = get_option(no_code, false),
Entry = if X == "init", What == contract, not NoCode ->
{reserved_init, Ann, Type};
What == contract; What == contract_interface -> {contract_fun, Ann, Type};
true -> {Ann, Type}
end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
Scope#scope{ funs = [{X, Entry} | Funs] }
end).
-spec bind_funs([{name(), type() | typesig()}], env()) -> env().
bind_funs([], Env) -> Env;
bind_funs([{Id, Type} | Rest], Env) ->
bind_funs(Rest, bind_fun(Id, Type, Env)).
-spec bind_type(name(), aeso_syntax:ann(), typedef(), env()) -> env().
bind_type(X, Ann, Def, Env) ->
on_current_scope(Env, fun(Scope = #scope{ types = Types }) ->
Scope#scope{ types = [{X, {Ann, Def}} | Types] }
end).
-spec bind_const(name(), aeso_syntax:ann(), type(), env()) -> env().
bind_const(X, Ann, Type, Env) ->
case lookup_env(Env, term, Ann, [X]) of
false ->
on_current_scope(Env, fun(Scope = #scope{ consts = Consts }) ->
Scope#scope{ consts = [{X, {Ann, Type}} | Consts] }
end);
_ ->
type_error({duplicate_definition, X, [Ann, aeso_syntax:get_ann(Type)]}),
Env
end.
%% Bind state primitives
-spec bind_state(env()) -> env().
bind_state(Env) ->
Ann = [{origin, system}],
Unit = {tuple_t, Ann, []},
State =
case lookup_type(Env, {id, Ann, "state"}) of
{S, _} -> {qid, Ann, S};
false -> Unit
end,
Env1 = bind_funs([{"state", State},
{"put", {type_sig, [stateful | Ann], none, [], [State], Unit}}], Env),
case lookup_type(Env, {id, Ann, "event"}) of
{E, _} ->
%% We bind Chain.event in a local 'Chain' namespace.
Event = {qid, Ann, E},
pop_scope(
bind_fun("event", {fun_t, Ann, [], [Event], Unit},
push_scope(namespace, {con, Ann, "Chain"}, Env1)));
false -> Env1
end.
%-spec bind_fields_append(env(), #{ name() => aeso_syntax:decl() }, type(), [aeso_syntax:field_t()]) -> env().
bind_fields_append(Env, _TypeMap, _, []) -> Env;
bind_fields_append(Env, TypeMap, RecTy, [{field_t, Ann, Id, Type} | Fields]) ->
Env1 = bind_field_append(name(Id), #field_info{ ann = Ann, kind = record, field_t = Type, record_t = RecTy }, Env),
bind_fields_append(Env1, TypeMap, RecTy, Fields).
-spec bind_field_append(name(), field_info(), env()) -> env().
bind_field_append(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun(Infos) -> [Info | Infos] end, [Info], Fields),
Env#env{ fields = Fields1 }.
-spec bind_field_update(name(), field_info(), env()) -> env().
bind_field_update(X, Info, Env = #env{ fields = Fields }) ->
Fields1 = maps:update_with(X, fun([_ | Infos]) -> [Info | Infos]; ([]) -> [Info] end, [Info], Fields),
Env#env{ fields = Fields1 }.
-spec bind_fields([{name(), field_info()}], typed | untyped, env()) -> env().
bind_fields([], _Typing, Env) -> Env;
bind_fields([{Id, Info} | Rest], Typing, Env) ->
NewEnv = case Typing of
untyped -> bind_field_append(Id, Info, Env);
typed -> bind_field_update(Id, Info, Env)
end,
bind_fields(Rest, Typing, NewEnv).
%% Contract entrypoints take three named arguments
%% gas : int = Call.gas_left()
%% value : int = 0
%% protected : bool = false
contract_call_type({fun_t, Ann, [], Args, Ret}) ->
Id = fun(X) -> {id, Ann, X} end,
Int = Id("int"),
Typed = fun(E, T) -> {typed, Ann, E, T} end,
Named = fun(Name, Default = {typed, _, _, T}) -> {named_arg_t, Ann, Id(Name), T, Default} end,
{fun_t, Ann, [Named("gas", Typed({app, Ann, Typed({qid, Ann, ["Call", "gas_left"]},
{fun_t, Ann, [], [], Int}),
[]}, Int)),
Named("value", Typed({int, Ann, 0}, Int)),
Named("protected", Typed({bool, Ann, false}, Id("bool")))],
Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}.
-spec bind_contract(typed | untyped, aeso_syntax:decl(), env()) -> env().
bind_contract(Typing, {Contract, Ann, Id, _Impls, Contents}, Env)
when ?IS_CONTRACT_HEAD(Contract) ->
Key = name(Id),
Sys = [{origin, system}],
TypeOrFresh = fun({typed, _, _, Type}) -> Type; (_) -> fresh_uvar(Sys) end,
Fields =
[ {field_t, AnnF, Entrypoint, contract_call_type(Type)}
|| {fun_decl, AnnF, Entrypoint, Type = {fun_t, _, _, _, _}} <- Contents ] ++
[ {field_t, AnnF, Entrypoint,
contract_call_type(
{fun_t, AnnF, [], [TypeOrFresh(Arg) || Arg <- Args], TypeOrFresh(Ret)})
}
|| {letfun, AnnF, Entrypoint = {id, _, Name}, Args, _Type, [{guarded, _, [], Ret}]} <- Contents,
Name =/= "init"
] ++
%% Predefined fields
[ {field_t, Sys, {id, Sys, "address"}, {id, Sys, "address"}} ] ++
[ {field_t, Sys, {id, Sys, ?CONSTRUCTOR_MOCK_NAME},
contract_call_type(
case [ [TypeOrFresh(Arg) || Arg <- Args]
|| {letfun, AnnF, {id, _, "init"}, Args, _, _} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args
|| {fun_decl, AnnF, {id, _, "init"}, {fun_t, _, _, Args, _}} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
++ [ Args
|| {fun_decl, AnnF, {id, _, "init"}, {type_sig, _, _, _, Args, _}} <- Contents,
aeso_syntax:get_ann(entrypoint, AnnF, false)]
of
[] -> {fun_t, [stateful,payable|Sys], [], [], {id, Sys, "void"}};
[Args] -> {fun_t, [stateful,payable|Sys], [], Args, {id, Sys, "void"}}
end
)
}
],
FieldInfo = [ {Entrypoint, #field_info{ ann = FieldAnn,
kind = contract,
field_t = Type,
record_t = Id }}
|| {field_t, _, {id, FieldAnn, Entrypoint}, Type} <- Fields ],
bind_type(Key, Ann, {[], {contract_t, Fields}},
bind_fields(FieldInfo, Typing, Env)).
%% What scopes could a given name come from?
-spec possible_scopes(env(), qname()) -> [qname()].
possible_scopes(#env{ namespace = Current, used_namespaces = UsedNamespaces }, Name) ->
Qual = lists:droplast(Name),
NewQuals = case lists:filter(fun(X) -> element(2, X) == Qual end, UsedNamespaces) of
[] ->
[Qual];
Namespaces ->
lists:map(fun(X) -> element(1, X) end, Namespaces)
end,
Ret1 = [ lists:sublist(Current, I) ++ Q || I <- lists:seq(0, length(Current)), Q <- NewQuals ],
Ret2 = [ Namespace ++ Q || {Namespace, none, _} <- UsedNamespaces, Q <- NewQuals ],
lists:usort(Ret1 ++ Ret2).
-spec visible_in_used_namespaces(used_namespaces(), qname()) -> boolean().
visible_in_used_namespaces(UsedNamespaces, QName) ->
Qual = lists:droplast(QName),
Name = lists:last(QName),
case lists:filter(fun({Ns, _, _}) -> Qual == Ns end, UsedNamespaces) of
[] ->
true;
Namespaces ->
IsVisible = fun(Namespace) ->
case Namespace of
{_, _, {for, Names}} ->
lists:member(Name, Names);
{_, _, {hiding, Names}} ->
not lists:member(Name, Names);
_ ->
true
end
end,
lists:any(IsVisible, Namespaces)
end.
-spec lookup_type(env(), type_id()) -> false | {qname(), type_info()}.
lookup_type(Env, Id) ->
lookup_env(Env, type, aeso_syntax:get_ann(Id), qname(Id)).
-spec lookup_env(env(), term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info()};
(env(), type, aeso_syntax:ann(), qname()) -> false | {qname(), type_info()}.
lookup_env(Env, Kind, Ann, Name) ->
Var = case Name of
[X] when Kind == term -> proplists:get_value(X, Env#env.vars, false);
_ -> false
end,
case Var of
{Ann1, Type} -> {Name, {Ann1, Type}};
false ->
Names = [ Qual ++ [lists:last(Name)] || Qual <- possible_scopes(Env, Name) ],
case [ Res || QName <- Names, Res <- [lookup_env1(Env, Kind, Ann, QName)], Res /= false] of
[] -> false;
[Res = {_, {AnnR, _}}] ->
when_warning(warn_unused_includes,
fun() ->
%% If a file is used from a different file, we
%% can then mark it as used
F1 = proplists:get_value(file, Ann, no_file),
F2 = proplists:get_value(file, AnnR, no_file),
if
F1 /= F2 ->
used_include(AnnR);
true ->
ok
end
end),
Res;
Many ->
type_error({ambiguous_name, qid(Ann, Name), [{qid, A, Q} || {Q, {A, _}} <- Many]}),
false
end
end.
-spec lookup_env1(env(), type | term, aeso_syntax:ann(), qname()) -> false | {qname(), fun_info() | type_info()}.
lookup_env1(#env{ namespace = Current, used_namespaces = UsedNamespaces, scopes = Scopes }, Kind, Ann, QName) ->
Qual = lists:droplast(QName),
Name = lists:last(QName),
QNameIsEvent = lists:suffix(["Chain", "event"], QName),
AllowPrivate = lists:prefix(Qual, Current),
%% Get the scope
case maps:get(Qual, Scopes, false) of
false -> false; %% TODO: return reason for not in scope
#scope{ funs = Funs, types = Types, consts = Consts, kind = ScopeKind } ->
Defs = case Kind of
type -> Types;
term -> Funs
end,
%% Look up the unqualified name
case proplists:get_value(Name, Defs, false) of
false ->
case proplists:get_value(Name, Consts, false) of
false ->
false;
Const when AllowPrivate; ScopeKind == namespace ->
{QName, Const};
Const ->
type_error({contract_treated_as_namespace_constant, Ann, QName}),
{QName, Const}
end;
{reserved_init, Ann1, Type} ->
type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
{contract_fun, Ann1, Type} when AllowPrivate orelse QNameIsEvent ->
{QName, {Ann1, Type}};
{contract_fun, Ann1, Type} ->
type_error({contract_treated_as_namespace_entrypoint, Ann, QName}),
{QName, {Ann1, Type}};
{Ann1, _} = E ->
%% Check that it's not private (or we can see private funs)
case not is_private(Ann1) orelse AllowPrivate of
true ->
case visible_in_used_namespaces(UsedNamespaces, QName) of
true -> {QName, E};
false -> false
end;
false -> false
end
end
end.
-spec lookup_record_field(env(), name()) -> [field_info()].
lookup_record_field(Env, FieldName) ->
maps:get(FieldName, Env#env.fields, []).
%% For 'create' or 'update' constraints we don't consider contract types.
-spec lookup_record_field(env(), name(), create | project | update) -> [field_info()].
lookup_record_field(Env, FieldName, Kind) ->
[ Fld || Fld = #field_info{ kind = K } <- lookup_record_field(Env, FieldName),
Kind == project orelse K /= contract ].
lookup_record_field_arity(Env, FieldName, Arity, Kind) ->
Fields = lookup_record_field(Env, FieldName, Kind),
[ Fld || Fld = #field_info{ field_t = FldType } <- Fields,
aeso_tc_type_utils:fun_arity(aeso_tc_type_utils:dereference_deep(FldType)) == Arity ].
is_private(Ann) -> proplists:get_value(private, Ann, false).
option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}.
init_env() -> init_env([]).
init_env(_Options) -> global_env().
-spec empty_env() -> env().
empty_env() -> #env{}.
%% Environment containing language primitives
-spec global_env() -> env().
global_env() ->
Ann = [{origin, system}],
Int = {id, Ann, "int"},
Char = {id, Ann, "char"},
Bool = {id, Ann, "bool"},
String = {id, Ann, "string"},
Address = {id, Ann, "address"},
Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
Unit = {tuple_t, Ann, []},
List = fun(T) -> {app_t, Ann, {id, Ann, "list"}, [T]} end,
Option = fun(T) -> {app_t, Ann, {id, Ann, "option"}, [T]} end,
Map = fun(A, B) -> {app_t, Ann, {id, Ann, "map"}, [A, B]} end,
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
FunC = fun(C, Ts, T) -> {type_sig, Ann, C, [], Ts, T} end,
FunC1 = fun(C, S, T) -> {type_sig, Ann, C, [], [S], T} end,
Fun = fun(Ts, T) -> FunC(none, Ts, T) end,
Fun1 = fun(S, T) -> Fun([S], T) end,
FunCN = fun(C, Named, Normal, Ret) -> {type_sig, Ann, C, Named, Normal, Ret} end,
FunN = fun(Named, Normal, Ret) -> FunCN(none, Named, Normal, Ret) end,
%% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end,
%% Lambda1 = fun(S, T) -> Lambda([S], T) end,
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [], Ts, T} end,
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
SignId = {id, Ann, "signature"},
SignDef = {bytes, Ann, <<0:64/unit:8>>},
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [Signature], Ts, T} end,
TTL = {qid, Ann, ["Chain", "ttl"]},
Pointee = {qid, Ann, ["AENS", "pointee"]},
AENSName = {qid, Ann, ["AENS", "name"]},
Fr = {qid, Ann, ["MCL_BLS12_381", "fr"]},
Fp = {qid, Ann, ["MCL_BLS12_381", "fp"]},
Fp2 = {tuple_t, Ann, [Fp, Fp]},
G1 = {tuple_t, Ann, [Fp, Fp, Fp]},
G2 = {tuple_t, Ann, [Fp2, Fp2, Fp2]},
GT = {tuple_t, Ann, lists:duplicate(12, Fp)},
Tx = {qid, Ann, ["Chain", "tx"]},
GAMetaTx = {qid, Ann, ["Chain", "ga_meta_tx"]},
BaseTx = {qid, Ann, ["Chain", "base_tx"]},
PayForTx = {qid, Ann, ["Chain", "paying_for_tx"]},
FldT = fun(Id, T) -> {field_t, Ann, {id, Ann, Id}, T} end,
TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)},
{"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}],
TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]},
Stateful = fun(T) -> setelement(2, T, [stateful|element(2, T)]) end,
Fee = Int,
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
MkDefs = fun(Defs) -> [{X, {Ann, if is_integer(T) -> {builtin, T}; true -> T end}} || {X, T} <- Defs] end,
TopScope = #scope
{ funs = MkDefs(
%% Option constructors
[{"None", Option(A)},
{"Some", Fun1(A, Option(A))},
%% TTL constructors
{"RelativeTTL", Fun1(Int, TTL)},
{"FixedTTL", Fun1(Int, TTL)},
%% Abort/exit
{"abort", Fun1(String, A)},
{"exit", Fun1(String, A)},
{"require", Fun([Bool, String], Unit)}])
, types = MkDefs(
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
{"void", 0},
{"unit", {[], {alias_t, Unit}}},
{"hash", {[], {alias_t, Bytes(32)}}},
{"signature", {[], {alias_t, Bytes(64)}}},
{"bits", 0},
{"option", 1}, {"list", 1}, {"map", 2},
{"oracle", 2}, {"oracle_query", 2}
]) },
ChainScope = #scope
{ funs = MkDefs(
%% Spend transaction.
[{"spend", StateFun([Address, Int], Unit)},
%% Chain environment
{"balance", Fun1(Address, Int)},
{"block_hash", Fun1(Int, Option(Hash))},
{"coinbase", Address},
{"timestamp", Int},
{"block_height", Int},
{"difficulty", Int},
{"gas_limit", Int},
{"bytecode_hash",FunC1(bytecode_hash, A, Option(Hash))},
{"create", Stateful(
FunN([ {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
], var_args, A))},
{"clone", Stateful(
FunN([ {named_arg_t, Ann, {id, Ann, "gas"}, Int,
{typed, Ann,
{app, Ann,
{typed, Ann, {qid, Ann, ["Call","gas_left"]},
aeso_tc_type_utils:typesig_to_fun_t(Fun([], Int))
},
[]}, Int
}}
, {named_arg_t, Ann, {id, Ann, "value"}, Int, {typed, Ann, {int, Ann, 0}, Int}}
, {named_arg_t, Ann, {id, Ann, "protected"}, Bool, {typed, Ann, {bool, Ann, false}, Bool}}
, {named_arg_t, Ann, {id, Ann, "ref"}, A, undefined}
], var_args, A))},
%% Tx constructors
{"GAMetaTx", Fun([Address, Int], GAMetaTx)},
{"PayingForTx", Fun([Address, Int], PayForTx)},
{"SpendTx", Fun([Address, Int, String], BaseTx)},
{"OracleRegisterTx", BaseTx},
{"OracleQueryTx", BaseTx},
{"OracleResponseTx", BaseTx},
{"OracleExtendTx", BaseTx},
{"NamePreclaimTx", BaseTx},
{"NameClaimTx", Fun([String], BaseTx)},
{"NameUpdateTx", Fun([Hash], BaseTx)},
{"NameRevokeTx", Fun([Hash], BaseTx)},
{"NameTransferTx", Fun([Address, Hash], BaseTx)},
{"ChannelCreateTx", Fun([Address], BaseTx)},
{"ChannelDepositTx", Fun([Address, Int], BaseTx)},
{"ChannelWithdrawTx", Fun([Address, Int], BaseTx)},
{"ChannelForceProgressTx", Fun([Address], BaseTx)},
{"ChannelCloseMutualTx", Fun([Address], BaseTx)},
{"ChannelCloseSoloTx", Fun([Address], BaseTx)},
{"ChannelSlashTx", Fun([Address], BaseTx)},
{"ChannelSettleTx", Fun([Address], BaseTx)},
{"ChannelSnapshotSoloTx", Fun([Address], BaseTx)},
{"ContractCreateTx", Fun([Int], BaseTx)},
{"ContractCallTx", Fun([Address, Int], BaseTx)},
{"GAAttachTx", BaseTx}
])
, types = MkDefs([{"ttl", 0}, {"tx", {[], TxType}},
{"base_tx", 0},
{"paying_for_tx", 0}, {"ga_meta_tx", 0}]) },
ContractScope = #scope
{ funs = MkDefs(
[{"address", Address},
{"creator", Address},
{"balance", Int}]) },
CallScope = #scope
{ funs = MkDefs(
[{"origin", Address},
{"caller", Address},
{"value", Int},
{"gas_price", Int},
{"fee", Int},
{"gas_left", Fun([], Int)}])
},
OracleScope = #scope
{ funs = MkDefs(
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
{"expiry", Fun([Oracle(Q, R)], Fee)},
{"query_fee", Fun([Oracle(Q, R)], Fee)},
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
{"check", Fun([Oracle(Q, R)], Bool)},
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
AENSScope = #scope
{ funs = MkDefs(
[{"resolve", Fun([String, String], option_t(Ann, A))},
{"preclaim", SignFun([Address, Hash], Unit)},
{"claim", SignFun([Address, String, Int, Int], Unit)},
{"transfer", SignFun([Address, Address, String], Unit)},
{"revoke", SignFun([Address, String], Unit)},
{"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, Pointee))], Unit)},
{"lookup", Fun([String], option_t(Ann, AENSName))},
%% AENS pointee constructors
{"AccountPt", Fun1(Address, Pointee)},
{"OraclePt", Fun1(Address, Pointee)},
{"ContractPt", Fun1(Address, Pointee)},
{"ChannelPt", Fun1(Address, Pointee)},
%% Name object constructor
{"Name", Fun([Address, TTL, Map(String, Pointee)], AENSName)}
])
, types = MkDefs([{"pointee", 0}, {"name", 0}]) },
MapScope = #scope
{ funs = MkDefs(
[{"from_list", Fun1(List(Pair(K, V)), Map(K, V))},
{"to_list", Fun1(Map(K, V), List(Pair(K, V)))},
{"lookup", Fun([K, Map(K, V)], Option(V))},
{"lookup_default", Fun([K, Map(K, V), V], V)},
{"delete", Fun([K, Map(K, V)], Map(K, V))},
{"member", Fun([K, Map(K, V)], Bool)},
{"size", Fun1(Map(K, V), Int)}]) },
%% Crypto/Curve operations
CryptoScope = #scope
{ funs = MkDefs(
[{"verify_sig", Fun([Hash, Address, SignId], Bool)},
{"verify_sig_secp256k1", Fun([Hash, Bytes(64), SignId], Bool)},
{"ecverify_secp256k1", Fun([Hash, Bytes(20), Bytes(65)], Bool)},
{"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))},
{"sha3", Fun1(A, Hash)},
{"sha256", Fun1(A, Hash)},
{"blake2b", Fun1(A, Hash)}]) },
%% Fancy BLS12-381 crypto operations
MCL_BLS12_381_Scope = #scope
{ funs = MkDefs(
[{"g1_neg", Fun1(G1, G1)},
{"g1_norm", Fun1(G1, G1)},
{"g1_valid", Fun1(G1, Bool)},
{"g1_is_zero", Fun1(G1, Bool)},
{"g1_add", Fun ([G1, G1], G1)},
{"g1_mul", Fun ([Fr, G1], G1)},
{"g2_neg", Fun1(G2, G2)},
{"g2_norm", Fun1(G2, G2)},
{"g2_valid", Fun1(G2, Bool)},
{"g2_is_zero", Fun1(G2, Bool)},
{"g2_add", Fun ([G2, G2], G2)},
{"g2_mul", Fun ([Fr, G2], G2)},
{"gt_inv", Fun1(GT, GT)},
{"gt_add", Fun ([GT, GT], GT)},
{"gt_mul", Fun ([GT, GT], GT)},
{"gt_pow", Fun ([GT, Fr], GT)},
{"gt_is_one", Fun1(GT, Bool)},
{"pairing", Fun ([G1, G2], GT)},
{"miller_loop", Fun ([G1, G2], GT)},
{"final_exp", Fun1(GT, GT)},
{"int_to_fr", Fun1(Int, Fr)},
{"int_to_fp", Fun1(Int, Fp)},
{"fr_to_int", Fun1(Fr, Int)},
{"fp_to_int", Fun1(Fp, Int)}
]),
types = MkDefs(
[{"fr", 0}, {"fp", 0}]) },
%% Authentication
AuthScope = #scope
{ funs = MkDefs(
[{"tx_hash", Option(Hash)},
{"tx", Option(Tx)} ]) },
%% Strings
StringScope = #scope
{ funs = MkDefs(
[{"length", Fun1(String, Int)},
{"concat", Fun([String, String], String)},
{"to_list", Fun1(String, List(Char))},
{"from_list", Fun1(List(Char), String)},
{"to_upper", Fun1(String, String)},
{"to_lower", Fun1(String, String)},
{"sha3", Fun1(String, Hash)},
{"sha256", Fun1(String, Hash)},
{"blake2b", Fun1(String, Hash)}
]) },
%% Chars
CharScope = #scope
{ funs = MkDefs(
[{"to_int", Fun1(Char, Int)},
{"from_int", Fun1(Int, Option(Char))}]) },
%% Bits
BitsScope = #scope
{ funs = MkDefs(
[{"set", Fun([Bits, Int], Bits)},
{"clear", Fun([Bits, Int], Bits)},
{"test", Fun([Bits, Int], Bool)},
{"sum", Fun1(Bits, Int)},
{"intersection", Fun([Bits, Bits], Bits)},
{"union", Fun([Bits, Bits], Bits)},
{"difference", Fun([Bits, Bits], Bits)},
{"none", Bits},
{"all", Bits}]) },
%% Bytes
BytesScope = #scope
{ funs = MkDefs(
[{"to_int", Fun1(Bytes(any), Int)},
{"to_str", Fun1(Bytes(any), String)},
{"concat", FunC(bytes_concat, [Bytes(any), Bytes(any)], Bytes(any))},
{"split", FunC(bytes_split, [Bytes(any)], Pair(Bytes(any), Bytes(any)))}
]) },
%% Conversion
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
{"to_contract", FunC(address_to_contract, [Address], A)},
{"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)},
{"is_payable", Fun1(Address, Bool)}]) },
#env{ scopes =
#{ [] => TopScope
, ["Chain"] => ChainScope
, ["Contract"] => ContractScope
, ["Call"] => CallScope
, ["Oracle"] => OracleScope
, ["AENS"] => AENSScope
, ["Map"] => MapScope
, ["Auth"] => AuthScope
, ["Crypto"] => CryptoScope
, ["MCL_BLS12_381"] => MCL_BLS12_381_Scope
, ["StringInternal"] => StringScope
, ["Char"] => CharScope
, ["Bits"] => BitsScope
, ["Bytes"] => BytesScope
, ["Int"] => IntScope
, ["Address"] => AddressScope
}
, fields =
maps:from_list([{N, [#field_info{ ann = [], field_t = T, record_t = Tx, kind = record }]}
|| {N, T} <- TxFlds ])
}.
destroy_and_report_type_errors(Env) ->
Errors0 = lists:reverse(aeso_tc_ets_manager:ets_tab2list(type_errors)),
%% io:format("Type errors now: ~p\n", [Errors0]),
aeso_tc_errors:destroy_type_errors(),
Errors = [ aeso_tc_errors:mk_error(unqualify(Env, Err)) || Err <- Errors0 ],
aeso_errors:throw(Errors). %% No-op if Errors == []
%% Strip current namespace from error message for nicer printing.
unqualify(Env, {qid, Ann, Xs}) ->
qid(Ann, unqualify1(aeso_tc_env:namespace(Env), Xs));
unqualify(Env, {qcon, Ann, Xs}) ->
qcon(Ann, unqualify1(aeso_tc_env:namespace(Env), Xs));
unqualify(Env, T) when is_tuple(T) ->
list_to_tuple(unqualify(Env, tuple_to_list(T)));
unqualify(Env, [H | T]) -> [unqualify(Env, H) | unqualify(Env, T)];
unqualify(_Env, X) -> X.
unqualify1(NS, Xs) ->
try lists:split(length(NS), Xs) of
{NS, Ys} -> Ys;
_ -> Xs
catch _:_ -> Xs
end.
+499
View File
@@ -0,0 +1,499 @@
-module(aeso_tc_errors).
-include("aeso_utils.hrl").
-export([cannot_unify/4
, type_error/1
, create_type_errors/0
, destroy_type_errors/0
, mk_error/1
]).
%% -- Moved functions --------------------------------------------------------
name(A) -> aeso_tc_name_manip:name(A).
%% -------
pos(A) -> aeso_tc_ann_manip:pos(A).
pos(A, B) -> aeso_tc_ann_manip:pos(A, B).
%% -------
pp(A) -> aeso_tc_pp:pp(A).
pp_type(A) -> aeso_tc_pp:pp_type(A).
pp_type(A, B) -> aeso_tc_pp:pp_type(A, B).
pp_typed(A, B, C) -> aeso_tc_pp:pp_typed(A, B, C).
pp_expr(A) -> aeso_tc_pp:pp_expr(A).
pp_why_record(A) -> aeso_tc_pp:pp_why_record(A).
pp_when(A) -> aeso_tc_pp:pp_when(A).
pp_loc(A) -> aeso_tc_pp:pp_loc(A).
%% ---------------------------------------------------------------------------
%% Save unification failures for error messages.
cannot_unify(A, B, Cxt, When) ->
type_error({cannot_unify, A, B, Cxt, When}).
type_error(Err) ->
aeso_tc_ets_manager:ets_insert(type_errors, Err).
create_type_errors() ->
aeso_tc_ets_manager:ets_new(type_errors, [bag]).
destroy_type_errors() ->
aeso_tc_ets_manager:ets_delete(type_errors).
mk_t_err(Pos, Msg) ->
aeso_errors:new(type_error, Pos, lists:flatten(Msg)).
mk_t_err(Pos, Msg, Ctxt) ->
aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)).
mk_error({no_decls, File}) ->
Pos = aeso_errors:pos(File, 0, 0),
mk_t_err(Pos, "Empty contract");
mk_error({mismatched_decl_in_funblock, Name, Decl}) ->
Msg = io_lib:format("Mismatch in the function block. Expected implementation/type declaration of ~s function", [Name]),
mk_t_err(pos(Decl), Msg);
mk_error({higher_kinded_typevar, T}) ->
Msg = io_lib:format("Type `~s` is a higher kinded type variable "
"(takes another type as an argument)", [pp(aeso_tc_type_utils:instantiate(T))]
),
mk_t_err(pos(T), Msg);
mk_error({wrong_type_arguments, X, ArityGiven, ArityReal}) ->
Msg = io_lib:format("Arity for ~s doesn't match. Expected ~p, got ~p"
, [pp(aeso_tc_type_utils:instantiate(X)), ArityReal, ArityGiven]
),
mk_t_err(pos(X), Msg);
mk_error({unnamed_map_update_with_default, Upd}) ->
Msg = "Invalid map update with default",
mk_t_err(pos(Upd), Msg);
mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) ->
Msg = io_lib:format("`~s` was declared with an invalid type `~s`. "
"Entrypoints and functions must have functional types"
, [pp(Id), pp(aeso_tc_type_utils:instantiate(Type))]),
mk_t_err(pos(Id), Msg);
mk_error({cannot_unify, A, B, Cxt, When}) ->
VarianceContext = case Cxt of
none -> "";
_ -> io_lib:format(" in a ~p context", [Cxt])
end,
Msg = io_lib:format("Cannot unify `~s` and `~s`" ++ VarianceContext,
[pp(aeso_tc_type_utils:instantiate(A)), pp(aeso_tc_type_utils:instantiate(B))]),
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({hole_found, Ann, Type}) ->
Msg = io_lib:format("Found a hole of type `~s`", [pp(aeso_tc_type_utils:instantiate(Type))]),
mk_t_err(pos(Ann), Msg);
mk_error({unbound_variable, Id}) ->
Msg = io_lib:format("Unbound variable `~s`", [pp(Id)]),
case Id of
{qid, _, ["Chain", "event"]} ->
Cxt = "Did you forget to define the event type?",
mk_t_err(pos(Id), Msg, Cxt);
_ -> mk_t_err(pos(Id), Msg)
end;
mk_error({undefined_field, Id}) ->
Msg = io_lib:format("Unbound field ~s", [pp(Id)]),
mk_t_err(pos(Id), Msg);
mk_error({not_a_record_type, Type, Why}) ->
Msg = io_lib:format("Not a record type: `~s`", [pp_type(Type)]),
{Pos, Ctxt} = pp_why_record(Why),
mk_t_err(Pos, Msg, Ctxt);
mk_error({not_a_contract_type, Type, Cxt}) ->
Msg =
case Type of
{tvar, _, _} ->
"Unresolved contract type";
_ ->
io_lib:format("The type `~s` is not a contract type", [pp_type(Type)])
end,
{Pos, Cxt1} =
case Cxt of
{var_args, Ann, Fun} ->
{pos(Ann),
io_lib:format("when calling variadic function `~s`", [pp_expr(Fun)])};
{contract_literal, Lit} ->
{pos(Lit),
io_lib:format("when checking that the contract literal `~s` has the type `~s`",
[pp_expr(Lit), pp_type(Type)])};
{address_to_contract, Ann} ->
{pos(Ann),
io_lib:format("when checking that the call to `Address.to_contract` has the type `~s`",
[pp_type(Type)])}
end,
mk_t_err(Pos, Msg, Cxt1);
mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
Msg = io_lib:format("Repeated name~s ~s in the pattern `~s`",
[plural("", "s", Nonlinear),
string:join(lists:map(fun(F) -> "`" ++ F ++ "`" end, Nonlinear), ", "),
pp_expr(Pattern)]),
mk_t_err(pos(Pattern), Msg);
mk_error({ambiguous_record, Fields = [{_, First} | _], Candidates}) ->
Msg = io_lib:format("Ambiguous record type with field~s ~s could be one of~s",
[plural("", "s", Fields),
string:join([ "`" ++ pp(F) ++ "`" || {_, F} <- Fields ], ", "),
[ ["\n - ", "`" ++ pp(C) ++ "`", " (at ", pp_loc(C), ")"] || C <- Candidates ]]),
mk_t_err(pos(First), Msg);
mk_error({missing_field, Field, Rec}) ->
Msg = io_lib:format("Record type `~s` does not have field `~s`",
[pp(Rec), pp(Field)]),
mk_t_err(pos(Field), Msg);
mk_error({missing_fields, Ann, RecType, Fields}) ->
Msg = io_lib:format("The field~s ~s ~s missing when constructing an element of type `~s`",
[plural("", "s", Fields),
string:join(lists:map(fun(F) -> "`" ++ F ++ "`" end, Fields), ", "),
plural("is", "are", Fields), pp(RecType)]),
mk_t_err(pos(Ann), Msg);
mk_error({no_records_with_all_fields, Fields = [{_, First} | _]}) ->
Msg = io_lib:format("No record type with field~s ~s",
[plural("", "s", Fields),
string:join([ "`" ++ pp(F) ++ "`" || {_, F} <- Fields ], ", ")]),
mk_t_err(pos(First), Msg);
mk_error({recursive_types_not_implemented, Types}) ->
S = plural(" is", "s are mutually", Types),
Msg = io_lib:format("The following type~s recursive, which is not yet supported:~s",
[S, [io_lib:format("\n - `~s` (at ~s)", [pp(T), pp_loc(T)]) || T <- Types]]),
mk_t_err(pos(hd(Types)), Msg);
mk_error({event_must_be_variant_type, Where}) ->
Msg = io_lib:format("The event type must be a variant type", []),
mk_t_err(pos(Where), Msg);
mk_error({indexed_type_must_be_word, Type, Type}) ->
Msg = io_lib:format("The indexed type `~s` is not a word type",
[pp_type(Type)]),
mk_t_err(pos(Type), Msg);
mk_error({indexed_type_must_be_word, Type, Type1}) ->
Msg = io_lib:format("The indexed type `~s` equals `~s` which is not a word type",
[pp_type(Type), pp_type(Type1)]),
mk_t_err(pos(Type), Msg);
mk_error({event_0_to_3_indexed_values, Constr}) ->
Msg = io_lib:format("The event constructor `~s` has too many indexed values (max 3)",
[name(Constr)]),
mk_t_err(pos(Constr), Msg);
mk_error({event_0_to_1_string_values, Constr}) ->
Msg = io_lib:format("The event constructor `~s` has too many non-indexed values (max 1)",
[name(Constr)]),
mk_t_err(pos(Constr), Msg);
mk_error({repeated_constructor, Cs}) ->
Msg = io_lib:format("Variant types must have distinct constructor names~s",
[[ io_lib:format("\n`~s` (at ~s)", [pp_typed(" - ", C, T), pp_loc(C)]) || {C, T} <- Cs ]]),
mk_t_err(pos(element(1, hd(Cs))), Msg);
mk_error({bad_named_argument, [], Name}) ->
Msg = io_lib:format("Named argument ~s supplied to function expecting no named arguments.",
[pp(Name)]),
mk_t_err(pos(Name), Msg);
mk_error({bad_named_argument, Args, Name}) ->
Msg = io_lib:format("Named argument `~s` is not one of the expected named arguments~s",
[pp(Name),
[ io_lib:format("\n - `~s`", [pp_typed("", Arg, Type)])
|| {named_arg_t, _, Arg, Type, _} <- Args ]]),
mk_t_err(pos(Name), Msg);
mk_error({unsolved_named_argument_constraint, Name, Type}) ->
Msg = io_lib:format("Named argument ~s supplied to function with unknown named arguments.",
[pp_typed("", Name, Type)]),
mk_t_err(pos(Name), Msg);
mk_error({reserved_entrypoint, Name, Def}) ->
Msg = io_lib:format("The name '~s' is reserved and cannot be used for a "
"top-level contract function.", [Name]),
mk_t_err(pos(Def), Msg);
mk_error({duplicate_definition, Name, Locs}) ->
Msg = io_lib:format("Duplicate definitions of `~s` at~s",
[Name, [ ["\n - ", pp_loc(L)] || L <- Locs ]]),
mk_t_err(pos(lists:last(Locs)), Msg);
mk_error({duplicate_scope, Kind, Name, OtherKind, L}) ->
Msg = io_lib:format("The ~p `~s` has the same name as a ~p at ~s",
[Kind, pp(Name), OtherKind, pp_loc(L)]),
mk_t_err(pos(Name), Msg);
mk_error({include, _, {string, Pos, Name}}) ->
Msg = io_lib:format("Include of `~s` is not allowed, include only allowed at top level.",
[binary_to_list(Name)]),
mk_t_err(pos(Pos), Msg);
mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
Msg = io_lib:format("Nested namespaces are not allowed. Namespace `~s` is not defined at top level.",
[Name]),
mk_t_err(pos(Pos), Msg);
mk_error({Contract, _Pos, {con, Pos, Name}, _Impls, _Def}) when ?IS_CONTRACT_HEAD(Contract) ->
Msg = io_lib:format("Nested contracts are not allowed. Contract `~s` is not defined at top level.",
[Name]),
mk_t_err(pos(Pos), Msg);
mk_error({type_decl, _, {id, Pos, Name}, _}) ->
Msg = io_lib:format("Empty type declarations are not supported. Type `~s` lacks a definition",
[Name]),
mk_t_err(pos(Pos), Msg);
mk_error({stateful_not_allowed, Id, Fun}) ->
Msg = io_lib:format("Cannot reference stateful function `~s` in the definition of non-stateful function `~s`.",
[pp(Id), pp(Fun)]),
mk_t_err(pos(Id), Msg);
mk_error({stateful_not_allowed_in_guards, Id}) ->
Msg = io_lib:format("Cannot reference stateful function `~s` in a pattern guard.",
[pp(Id)]),
mk_t_err(pos(Id), Msg);
mk_error({value_arg_not_allowed, Value, Fun}) ->
Msg = io_lib:format("Cannot pass non-zero value argument `~s` in the definition of non-stateful function `~s`.",
[pp_expr(Value), pp(Fun)]),
mk_t_err(pos(Value), Msg);
mk_error({init_depends_on_state, Which, [_Init | Chain]}) ->
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
Msg = io_lib:format("The `init` function should return the initial state as its result and cannot ~s the state, but it calls~s",
[if Which == put -> "write"; true -> "read" end,
[ io_lib:format("\n - `~s` (at ~s)~s", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|| {[_, Fun], Ann} <- Chain]]),
mk_t_err(pos(element(2, hd(Chain))), Msg);
mk_error({missing_body_for_let, Ann}) ->
Msg = io_lib:format("Let binding must be followed by an expression.", []),
mk_t_err(pos(Ann), Msg);
mk_error({public_modifier_in_contract, Decl}) ->
Decl1 = mk_entrypoint(Decl),
Msg = io_lib:format("Use `entrypoint` instead of `function` for public function `~s`: `~s`",
[pp_expr(element(3, Decl)),
prettypr:format(aeso_pretty:decl(Decl1))]),
mk_t_err(pos(Decl), Msg);
mk_error({init_must_be_an_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
Msg = io_lib:format("The init function must be an entrypoint: ~s",
[prettypr:format(prettypr:nest(2, aeso_pretty:decl(Decl1)))]),
mk_t_err(pos(Decl), Msg);
mk_error({init_must_not_be_payable, Decl}) ->
Msg = io_lib:format("The init function cannot be payable. "
"You don't need the 'payable' annotation to be able to attach "
"funds to the create contract transaction.",
[]),
mk_t_err(pos(Decl), Msg);
mk_error({proto_must_be_entrypoint, Decl}) ->
Decl1 = mk_entrypoint(Decl),
Msg = io_lib:format("Use `entrypoint` for declaration of `~s`: `~s`",
[pp_expr(element(3, Decl)),
prettypr:format(aeso_pretty:decl(Decl1))]),
mk_t_err(pos(Decl), Msg);
mk_error({proto_in_namespace, Decl}) ->
Msg = io_lib:format("Namespaces cannot contain function prototypes.", []),
mk_t_err(pos(Decl), Msg);
mk_error({entrypoint_in_namespace, Decl}) ->
Msg = io_lib:format("Namespaces cannot contain entrypoints. Use `function` instead.", []),
mk_t_err(pos(Decl), Msg);
mk_error({private_entrypoint, Decl}) ->
Msg = io_lib:format("The entrypoint `~s` cannot be private. Use `function` instead.",
[pp_expr(element(3, Decl))]),
mk_t_err(pos(Decl), Msg);
mk_error({private_and_public, Decl}) ->
Msg = io_lib:format("The function `~s` cannot be both public and private.",
[pp_expr(element(3, Decl))]),
mk_t_err(pos(Decl), Msg);
mk_error({contract_has_no_entrypoints, Con}) ->
Msg = io_lib:format("The contract `~s` has no entrypoints. Since Sophia version 3.2, public "
"contract functions must be declared with the `entrypoint` keyword instead of "
"`function`.", [pp_expr(Con)]),
mk_t_err(pos(Con), Msg);
mk_error({definition_in_contract_interface, Ann, {id, _, Id}}) ->
Msg = "Contract interfaces cannot contain defined functions or entrypoints.",
Cxt = io_lib:format("Fix: replace the definition of `~s` by a type signature.", [Id]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({unbound_type, Type}) ->
Msg = io_lib:format("Unbound type ~s.", [pp_type(Type)]),
mk_t_err(pos(Type), Msg);
mk_error({new_tuple_syntax, Ann, Ts}) ->
Msg = io_lib:format("Invalid type `~s`. The syntax of tuple types changed in Sophia version 4.0. Did you mean `~s`",
[pp_type({args_t, Ann, Ts}), pp_type({tuple_t, Ann, Ts})]),
mk_t_err(pos(Ann), Msg);
mk_error({map_in_map_key, Ann, KeyType}) ->
Msg = io_lib:format("Invalid key type `~s`", [pp_type(KeyType)]),
Cxt = "Map keys cannot contain other maps.",
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction "
"and cannot be called from the contract code.",
mk_t_err(pos(Ann), Msg);
mk_error({contract_treated_as_namespace_entrypoint, Ann, [Con, Fun] = QName}) ->
Msg = io_lib:format("Invalid call to contract entrypoint `~s`.", [string:join(QName, ".")]),
Cxt = io_lib:format("It must be called as `c.~s` for some `c : ~s`.", [Fun, Con]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({contract_treated_as_namespace_constant, Ann, QName}) ->
Msg = io_lib:format("Invalid use of the contract constant `~s`.", [string:join(QName, ".")]),
Cxt = "Toplevel contract constants can only be used in the contracts where they are defined.",
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({bad_top_level_decl, Decl}) ->
What = case element(1, Decl) of
letval -> "function or entrypoint";
_ -> "contract or namespace"
end,
Id = element(3, Decl),
Msg = io_lib:format("The definition of '~s' must appear inside a ~s.",
[pp_expr(Id), What]),
mk_t_err(pos(Decl), Msg);
mk_error({unknown_byte_length, Type}) ->
Msg = io_lib:format("Cannot resolve length of byte array.", []),
mk_t_err(pos(Type), Msg);
mk_error({unsolved_bytes_constraint, Ann, concat, A, B, C}) ->
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.concat with arguments of type\n"
"~s (at ~s)\n~s (at ~s)\nand result type\n~s (at ~s)",
[pp_type(" - ", A), pp_loc(A), pp_type(" - ", B),
pp_loc(B), pp_type(" - ", C), pp_loc(C)]),
mk_t_err(pos(Ann), Msg);
mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) ->
Msg = io_lib:format("Failed to resolve byte array lengths in call to Bytes.split with argument of type\n"
"~s (at ~s)\nand result types\n~s (at ~s)\n~s (at ~s)",
[ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A),
pp_type(" - ", B), pp_loc(B)]),
mk_t_err(pos(Ann), Msg);
mk_error({failed_to_get_compiler_version, Err}) ->
Msg = io_lib:format("Failed to get compiler version. Error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg);
mk_error({compiler_version_mismatch, Ann, Version, Op, Bound}) ->
PrintV = fun(V) -> string:join([integer_to_list(N) || N <- V], ".") end,
Msg = io_lib:format("Cannot compile with this version of the compiler, "
"because it does not satisfy the constraint"
" ~s ~s ~s", [PrintV(Version), Op, PrintV(Bound)]),
mk_t_err(pos(Ann), Msg);
mk_error({empty_record_or_map_update, Expr}) ->
Msg = io_lib:format("Empty record/map update `~s`", [pp_expr(Expr)]),
mk_t_err(pos(Expr), Msg);
mk_error({mixed_record_and_map, Expr}) ->
Msg = io_lib:format("Mixed record fields and map keys in `~s`", [pp_expr(Expr)]),
mk_t_err(pos(Expr), Msg);
mk_error({named_argument_must_be_literal_bool, Name, Arg}) ->
Msg = io_lib:format("Invalid `~s` argument `~s`. "
"It must be either `true` or `false`.",
[Name, pp_expr(aeso_tc_type_utils:instantiate(Arg))]),
mk_t_err(pos(Arg), Msg);
mk_error({conflicting_updates_for_field, Upd, Key}) ->
Msg = io_lib:format("Conflicting updates for field '~s'", [Key]),
mk_t_err(pos(Upd), Msg);
mk_error({ambiguous_main_contract, Ann}) ->
Msg = "Could not deduce the main contract. You can point it out manually with the `main` keyword.",
mk_t_err(pos(Ann), Msg);
mk_error({main_contract_undefined, Ann}) ->
Msg = "No contract defined.",
mk_t_err(pos(Ann), Msg);
mk_error({multiple_main_contracts, Ann}) ->
Msg = "Only one main contract can be defined.",
mk_t_err(pos(Ann), Msg);
mk_error({unify_varargs, When}) ->
Msg = "Cannot infer types for variable argument list.",
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({clone_no_contract, Ann}) ->
Msg = "Chain.clone requires `ref` named argument of contract type.",
mk_t_err(pos(Ann), Msg);
mk_error({contract_lacks_definition, Type, When}) ->
Msg = io_lib:format(
"~s is not implemented.",
[pp_type(Type)]
),
{Pos, Ctxt} = pp_when(When),
mk_t_err(Pos, Msg, Ctxt);
mk_error({ambiguous_name, Name, QIds}) ->
Msg = io_lib:format("Ambiguous name `~s` could be one of~s",
[pp(Name),
[io_lib:format("\n - `~s` (at ~s)", [pp(QId), pp_loc(QId)]) || QId <- QIds]]),
mk_t_err(pos(Name), Msg);
mk_error({using_undefined_namespace, Ann, Namespace}) ->
Msg = io_lib:format("Cannot use undefined namespace ~s", [Namespace]),
mk_t_err(pos(Ann), Msg);
mk_error({using_undefined_namespace_parts, Ann, Namespace, Parts}) ->
PartsStr = lists:concat(lists:join(", ", Parts)),
Msg = io_lib:format("The namespace ~s does not define the following names: ~s", [Namespace, PartsStr]),
mk_t_err(pos(Ann), Msg);
mk_error({empty_record_definition, Ann, Name}) ->
Msg = io_lib:format("Empty record definitions are not allowed. Cannot define the record `~s`", [Name]),
mk_t_err(pos(Ann), Msg);
mk_error({unimplemented_interface_function, ConId, InterfaceName, FunName}) ->
Msg = io_lib:format("Unimplemented entrypoint `~s` from the interface `~s` in the contract `~s`", [FunName, InterfaceName, pp(ConId)]),
mk_t_err(pos(ConId), Msg);
mk_error({referencing_undefined_interface, InterfaceId}) ->
Msg = io_lib:format("Trying to implement or extend an undefined interface `~s`", [pp(InterfaceId)]),
mk_t_err(pos(InterfaceId), Msg);
mk_error({missing_definition, Id}) ->
Msg = io_lib:format("Missing definition of function `~s`", [name(Id)]),
mk_t_err(pos(Id), Msg);
mk_error({parameterized_state, Ann}) ->
Msg = "The state type cannot be parameterized",
mk_t_err(pos(Ann), Msg);
mk_error({parameterized_event, Ann}) ->
Msg = "The event type cannot be parameterized",
mk_t_err(pos(Ann), Msg);
mk_error({missing_init_function, Con}) ->
Msg = io_lib:format("Missing `init` function for the contract `~s`.", [name(Con)]),
Cxt = "The `init` function can only be omitted if the state type is `unit`",
mk_t_err(pos(Con), Msg, Cxt);
mk_error({higher_order_entrypoint, Ann, {id, _, Name}, Thing}) ->
What = "higher-order (contains function types)",
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(" `", 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",
[ThingS, Name, Bad]),
mk_t_err(pos(Ann), Msg);
mk_error({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)",
[pp_type(" `", T)]),
mk_t_err(pos(Ann), Msg);
mk_error({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`", [pp_type(" `", Type)]),
Cxt = io_lib:format("The ~s type must not be ~s", [What, WhyS]),
mk_t_err(pos(Ann), Msg, Cxt);
mk_error({interface_implementation_conflict, Contract, I1, I2, Fun}) ->
Msg = io_lib:format("Both interfaces `~s` and `~s` implemented by "
"the contract `~s` have a function called `~s`",
[name(I1), name(I2), name(Contract), name(Fun)]),
mk_t_err(pos(Contract), Msg);
mk_error({function_should_be_entrypoint, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` must be declared as an entrypoint instead of a function "
"in order to implement the entrypoint `~s` from the interface `~s`",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({entrypoint_cannot_be_stateful, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` cannot be stateful because the entrypoint `~s` in the "
"interface `~s` is not stateful",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({entrypoint_must_be_payable, Impl, Base, Interface}) ->
Msg = io_lib:format("`~s` must be payable because the entrypoint `~s` in the "
"interface `~s` is payable",
[name(Impl), name(Base), name(Interface)]),
mk_t_err(pos(Impl), Msg);
mk_error({unpreserved_payablity, Kind, ContractCon, InterfaceCon}) ->
KindStr = case Kind of
contract -> "contract";
contract_interface -> "interface"
end,
Msg = io_lib:format("Non-payable ~s `~s` cannot implement payable interface `~s`",
[KindStr, name(ContractCon), name(InterfaceCon)]),
mk_t_err(pos(ContractCon), Msg);
mk_error({mutually_recursive_constants, Consts}) ->
Msg = [ "Mutual recursion detected between the constants",
[ io_lib:format("\n - `~s` at ~s", [name(Id), pp_loc(Ann)])
|| {letval, Ann, Id, _} <- Consts ] ],
[{letval, Ann, _, _} | _] = Consts,
mk_t_err(pos(Ann), Msg);
mk_error({invalid_const_id, Ann}) ->
Msg = "The name of the compile-time constant cannot have pattern matching",
mk_t_err(pos(Ann), Msg);
mk_error({invalid_const_expr, ConstId}) ->
Msg = io_lib:format("Invalid expression in the definition of the constant `~s`", [name(ConstId)]),
Cxt = "You can only use the following expressions as constants: "
"literals, lists, tuples, maps, and other constants",
mk_t_err(pos(aeso_syntax:get_ann(ConstId)), Msg, Cxt);
mk_error({illegal_const_in_interface, Ann}) ->
Msg = "Cannot define toplevel constants inside a contract interface",
mk_t_err(pos(Ann), Msg);
mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p", [Err]),
mk_t_err(pos(0, 0), Msg).
mk_entrypoint(Decl) ->
Ann = [entrypoint | lists:keydelete(public, 1,
lists:keydelete(private, 1,
aeso_syntax:get_ann(Decl))) -- [public, private]],
aeso_syntax:set_ann(Ann, Decl).
plural(No, _Yes, [_]) -> No;
plural(_No, Yes, _) -> Yes.
+102
View File
@@ -0,0 +1,102 @@
-module(aeso_tc_ets_manager).
-export([ ets_init/0
, ets_new/2
, ets_lookup/2
, ets_insert/2
, ets_insert_new/2
, ets_insert_ordered/2
, ets_delete/1
, ets_delete/2
, ets_match_delete/2
, ets_tab2list/1
, ets_tab2list_ordered/1
, ets_tab_exists/1
, clean_up_ets/0
]).
%% Clean up all the ets tables (in case of an exception)
ets_tables() ->
[options, type_vars, constraints, freshen_tvars, type_errors,
defined_contracts, warnings, function_calls, all_functions,
type_vars_variance, functions_to_implement].
clean_up_ets() ->
[ catch ets_delete(Tab) || Tab <- ets_tables() ],
ok.
%% Named interface to ETS tables implemented without names.
%% The interface functions behave as the standard ETS interface.
ets_init() ->
put(aeso_ast_infer_types, #{}).
ets_tab_exists(Name) ->
Tabs = get(aeso_ast_infer_types),
case maps:find(Name, Tabs) of
{ok, _} -> true;
error -> false
end.
ets_tabid(Name) ->
#{Name := TabId} = get(aeso_ast_infer_types),
TabId.
ets_new(Name, Opts) ->
%% Ensure the table is NOT named!
TabId = ets:new(Name, Opts -- [named_table]),
Tabs = get(aeso_ast_infer_types),
put(aeso_ast_infer_types, Tabs#{Name => TabId}),
Name.
ets_delete(Name) ->
Tabs = get(aeso_ast_infer_types),
#{Name := TabId} = Tabs,
put(aeso_ast_infer_types, maps:remove(Name, Tabs)),
ets:delete(TabId).
ets_delete(Name, Key) ->
TabId = ets_tabid(Name),
ets:delete(TabId, Key).
ets_insert(Name, Object) ->
TabId = ets_tabid(Name),
ets:insert(TabId, Object).
ets_insert_new(Name, Object) ->
TabId = ets_tabid(Name),
ets:insert_new(TabId, Object).
ets_lookup(Name, Key) ->
TabId = ets_tabid(Name),
ets:lookup(TabId, Key).
ets_match_delete(Name, Pattern) ->
TabId = ets_tabid(Name),
ets:match_delete(TabId, Pattern).
ets_tab2list(Name) ->
TabId = ets_tabid(Name),
ets:tab2list(TabId).
ets_insert_ordered(_, []) -> true;
ets_insert_ordered(Name, [H|T]) ->
ets_insert_ordered(Name, H),
ets_insert_ordered(Name, T);
ets_insert_ordered(Name, Object) ->
Count = next_count(),
TabId = ets_tabid(Name),
ets:insert(TabId, {Count, Object}).
ets_tab2list_ordered(Name) ->
[E || {_, E} <- ets_tab2list(Name)].
next_count() ->
V = case get(counter) of
undefined ->
0;
X -> X
end,
put(counter, V + 1),
V.
+39
View File
@@ -0,0 +1,39 @@
-module(aeso_tc_name_manip).
-export([ name/1
, qname/1
, qid/2
, qcon/2
, set_qname/2
]).
%% TODO: types are duplicated
-type name() :: string().
-type qname() :: [string()].
-type type_id() :: aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
-spec qname(type_id()) -> qname().
qname({id, _, X}) -> [X];
qname({qid, _, Xs}) -> Xs;
qname({con, _, X}) -> [X];
qname({qcon, _, Xs}) -> Xs.
-spec name(Named | {typed, _, Named, _}) -> name() when
Named :: aeso_syntax:id() | aeso_syntax:con().
name({typed, _, X, _}) -> name(X);
name({id, _, X}) -> X;
name({con, _, X}) -> X.
-spec qid(aeso_syntax:ann(), qname()) -> aeso_syntax:id() | aeso_syntax:qid().
qid(Ann, [X]) -> {id, Ann, X};
qid(Ann, Xs) -> {qid, Ann, Xs}.
-spec qcon(aeso_syntax:ann(), qname()) -> aeso_syntax:con() | aeso_syntax:qcon().
qcon(Ann, [X]) -> {con, Ann, X};
qcon(Ann, Xs) -> {qcon, Ann, Xs}.
-spec set_qname(qname(), type_id()) -> type_id().
set_qname(Xs, {id, Ann, _}) -> qid(Ann, Xs);
set_qname(Xs, {qid, Ann, _}) -> qid(Ann, Xs);
set_qname(Xs, {con, Ann, _}) -> qcon(Ann, Xs);
set_qname(Xs, {qcon, Ann, _}) -> qcon(Ann, Xs).
+48
View File
@@ -0,0 +1,48 @@
-module(aeso_tc_options).
-export([ create_options/1
, get_option/2
, when_option/2
, when_warning/2
]).
%% -- Moved functions --------------------------------------------------------
all_warnings() -> aeso_tc_warnings:all_warnings().
%% ---------------------------------------------------------------------------
create_options(Options) ->
aeso_tc_ets_manager:ets_new(options, [set]),
Tup = fun(Opt) when is_atom(Opt) -> {Opt, true};
(Opt) when is_tuple(Opt) -> Opt end,
aeso_tc_ets_manager:ets_insert(options, lists:map(Tup, Options)).
get_option(Key, Default) ->
case aeso_tc_ets_manager:ets_lookup(options, Key) of
[{_Key, Val}] -> Val;
_ -> Default
end.
when_option(Opt, Do) ->
get_option(Opt, false) andalso Do().
when_warning(Warn, Do) ->
case lists:member(Warn, all_warnings()) of
false ->
%% TODO: An error for passing invalid wanring name should be thrown here.
%% Validation of compiler options might be done at an earlier stage.
ok;
true ->
case aeso_tc_ets_manager:ets_tab_exists(warnings) of
true ->
IsEnabled = get_option(Warn, false),
IsAll = get_option(warn_all, false) andalso lists:member(Warn, all_warnings()),
if
IsEnabled orelse IsAll -> Do();
true -> ok
end;
false ->
ok
end
end.
+248
View File
@@ -0,0 +1,248 @@
-module(aeso_tc_pp).
-export([ pp/1
, pp_type/1
, pp_type/2
, pp_typed/3
, pp_expr/1
, pp_why_record/1
, pp_loc/1
, pp_when/1
]).
%% -- Duplicated types -------------------------------------------------------
-type why_record() :: aeso_syntax:field(aeso_syntax:expr())
| {var_args, aeso_syntax:ann(), aeso_syntax:expr()}
| {proj, aeso_syntax:ann(), aeso_syntax:expr(), aeso_syntax:id()}.
%% -- Moved functions --------------------------------------------------------
pos(A) -> aeso_tc_ann_manip:pos(A).
pos(A, B) -> aeso_tc_ann_manip:pos(A, B).
loc(A) -> aeso_tc_ann_manip:loc(A).
%% ---------------------------------------------------------------------------
-type pos() :: aeso_errors:pos().
if_branches(If = {'if', Ann, _, Then, Else}) ->
case proplists:get_value(format, Ann) of
elif -> [Then | if_branches(Else)];
_ -> [If]
end;
if_branches(E) -> [E].
pp_when({todo, What}) -> {pos(0, 0), io_lib:format("[TODO] ~p", [What])};
pp_when({at, Ann}) -> {pos(Ann), io_lib:format("at ~s", [pp_loc(Ann)])};
pp_when({check_typesig, Name, Inferred, Given}) ->
{pos(Given),
io_lib:format("when checking the definition of `~s`\n"
" inferred type: `~s`\n"
" given type: `~s`",
[Name, pp(aeso_tc_type_utils:instantiate(Inferred)), pp(aeso_tc_type_utils:instantiate(Given))])};
pp_when({infer_app, Fun, NamedArgs, Args, Inferred0, ArgTypes0}) ->
Inferred = aeso_tc_type_utils:instantiate(Inferred0),
ArgTypes = aeso_tc_type_utils:instantiate(ArgTypes0),
{pos(Fun),
io_lib:format("when checking the application of\n"
" `~s`\n"
"to arguments~s",
[pp_typed("", Fun, Inferred),
[ ["\n ", "`" ++ pp_expr(NamedArg) ++ "`"] || NamedArg <- NamedArgs ] ++
[ ["\n ", "`" ++ pp_typed("", Arg, ArgT) ++ "`"]
|| {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])};
pp_when({field_constraint, FieldType0, InferredType0, Fld}) ->
FieldType = aeso_tc_type_utils:instantiate(FieldType0),
InferredType = aeso_tc_type_utils:instantiate(InferredType0),
{pos(Fld),
case Fld of
{var_args, _Ann, _Fun} ->
io_lib:format("when checking contract construction of type\n~s (at ~s)\nagainst the expected type\n~s\n",
[pp_type(" ", FieldType),
pp_loc(Fld),
pp_type(" ", InferredType)
]);
{field, _Ann, LV, Id, E} ->
io_lib:format("when checking the assignment of the field `~s` to the old value `~s` and the new value `~s`",
[pp_typed("", {lvalue, [], LV}, FieldType),
pp(Id),
pp_typed("", E, InferredType)]);
{field, _Ann, LV, E} ->
io_lib:format("when checking the assignment of the field `~s` to the value `~s`",
[pp_typed("", {lvalue, [], LV}, FieldType),
pp_typed("", E, InferredType)]);
{proj, _Ann, _Rec, _Fld} ->
io_lib:format("when checking the record projection `~s` against the expected type `~s`",
[pp_typed(" ", Fld, FieldType),
pp_type(" ", InferredType)])
end};
pp_when({record_constraint, RecType0, InferredType0, Fld}) ->
RecType = aeso_tc_type_utils:instantiate(RecType0),
InferredType = aeso_tc_type_utils:instantiate(InferredType0),
{Pos, WhyRec} = pp_why_record(Fld),
case Fld of
{var_args, _Ann, _Fun} ->
{Pos,
io_lib:format("when checking that contract construction of type\n~s\n~s\n"
"matches the expected type\n~s",
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)]
)
};
{field, _Ann, _LV, _Id, _E} ->
{Pos,
io_lib:format("when checking that the record type\n~s\n~s\n"
"matches the expected type\n~s",
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
{field, _Ann, _LV, _E} ->
{Pos,
io_lib:format("when checking that the record type\n~s\n~s\n"
"matches the expected type\n~s",
[pp_type(" ", RecType), WhyRec, pp_type(" ", InferredType)])};
{proj, _Ann, Rec, _FldName} ->
{pos(Rec),
io_lib:format("when checking that the expression\n~s (at ~s)\nhas type\n~s\n~s",
[pp_typed(" ", Rec, InferredType), pp_loc(Rec),
pp_type(" ", RecType), WhyRec])}
end;
pp_when({if_branches, Then, ThenType0, Else, ElseType0}) ->
{ThenType, ElseType} = aeso_tc_type_utils:instantiate({ThenType0, ElseType0}),
Branches = [ {Then, ThenType} | [ {B, ElseType} || B <- if_branches(Else) ] ],
{pos(element(1, hd(Branches))),
io_lib:format("when comparing the types of the if-branches\n"
"~s", [ [ io_lib:format("~s (at ~s)\n", [pp_typed(" - ", B, BType), pp_loc(B)])
|| {B, BType} <- Branches ] ])};
pp_when({case_pat, Pat, PatType0, ExprType0}) ->
{PatType, ExprType} = aeso_tc_type_utils:instantiate({PatType0, ExprType0}),
{pos(Pat),
io_lib:format("when checking the type of the pattern `~s` against the expected type `~s`",
[pp_typed("", Pat, PatType),
pp_type(ExprType)])};
pp_when({check_expr, Expr, Inferred0, Expected0}) ->
{Inferred, Expected} = aeso_tc_type_utils:instantiate({Inferred0, Expected0}),
{pos(Expr),
io_lib:format("when checking the type of the expression `~s` against the expected type `~s`",
[pp_typed("", Expr, Inferred), pp_type(Expected)])};
pp_when({checking_init_type, Ann}) ->
{pos(Ann),
io_lib:format("when checking that `init` returns a value of type `state`", [])};
pp_when({list_comp, BindExpr, Inferred0, Expected0}) ->
{Inferred, Expected} = aeso_tc_type_utils:instantiate({Inferred0, Expected0}),
{pos(BindExpr),
io_lib:format("when checking rvalue of list comprehension binding `~s` against type `~s`",
[pp_typed("", BindExpr, Inferred), pp_type(Expected)])};
pp_when({check_named_arg_constraint, CArgs, CName, CType}) ->
{id, _, Name} = Arg = CName,
[Type | _] = [ Type || {named_arg_t, _, {id, _, Name1}, Type, _} <- CArgs, Name1 == Name ],
Err = io_lib:format("when checking named argument `~s` against inferred type `~s`",
[pp_typed("", Arg, Type), pp_type(CType)]),
{pos(Arg), Err};
pp_when({checking_init_args, Ann, Con0, ArgTypes0}) ->
Con = aeso_tc_type_utils:instantiate(Con0),
ArgTypes = aeso_tc_type_utils:instantiate(ArgTypes0),
{pos(Ann),
io_lib:format("when checking arguments of `~s`'s init entrypoint to match\n(~s)",
[pp_type(Con), string:join([pp_type(A) || A <- ArgTypes], ", ")])
};
pp_when({return_contract, App, Con0}) ->
Con = aeso_tc_type_utils:instantiate(Con0),
{pos(App)
, io_lib:format("when checking that expression returns contract of type `~s`", [pp_type(Con)])
};
pp_when({arg_name, Id1, Id2, When}) ->
{Pos, Ctx} = pp_when(When),
{Pos
, io_lib:format("when unifying names of named arguments: `~s` and `~s`\n~s", [pp_expr(Id1), pp_expr(Id2), Ctx])
};
pp_when({var_args, Ann, Fun}) ->
{pos(Ann)
, io_lib:format("when resolving arguments of variadic function `~s`", [pp_expr(Fun)])
};
pp_when(unknown) -> {pos(0,0), ""}.
-spec pp_why_record(why_record()) -> {pos(), iolist()}.
pp_why_record({var_args, Ann, Fun}) ->
{pos(Ann),
io_lib:format("arising from resolution of variadic function `~s`",
[pp_expr(Fun)])};
pp_why_record(Fld = {field, _Ann, LV, _E}) ->
{pos(Fld),
io_lib:format("arising from an assignment of the field `~s`",
[pp_expr({lvalue, [], LV})])};
pp_why_record(Fld = {field, _Ann, LV, _Alias, _E}) ->
{pos(Fld),
io_lib:format("arising from an assignment of the field `~s`",
[pp_expr({lvalue, [], LV})])};
pp_why_record({proj, _Ann, Rec, FldName}) ->
{pos(Rec),
io_lib:format("arising from the projection of the field `~s`",
[pp(FldName)])}.
pp_typed(Label, E, T = {type_sig, _, _, _, _, _}) -> pp_typed(Label, E, aeso_tc_type_utils:typesig_to_fun_t(T));
pp_typed(Label, {typed, _, Expr, _}, Type) ->
pp_typed(Label, Expr, Type);
pp_typed(Label, Expr, Type) ->
pp_expr(Label, {typed, [], Expr, Type}).
pp_expr(Expr) ->
pp_expr("", Expr).
pp_expr(Label, Expr) ->
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:expr(Expr, [show_generated])), 80, 80).
pp_type(Type) ->
pp_type("", Type).
pp_type(Label, Type) ->
prettypr:format(prettypr:beside(prettypr:text(Label), aeso_pretty:type(Type, [show_generated])), 80, 80).
pp_loc(T) ->
{File, IncludeType, Line, Col} = loc(T),
case {Line, Col} of
{0, 0} -> "(builtin location)";
_ -> case IncludeType of
none -> io_lib:format("line ~p, column ~p", [Line, Col]);
_ -> io_lib:format("line ~p, column ~p in ~s", [Line, Col, File])
end
end.
pp(T = {type_sig, _, _, _, _, _}) ->
pp(aeso_tc_type_utils:typesig_to_fun_t(T));
pp([]) ->
"";
pp([T]) ->
pp(T);
pp([T|Ts]) ->
[pp(T), ", "|pp(Ts)];
pp({id, _, Name}) ->
Name;
pp({qid, _, Name}) ->
string:join(Name, ".");
pp({con, _, Name}) ->
Name;
pp({qcon, _, Name}) ->
string:join(Name, ".");
pp({uvar, _, Ref}) ->
%% Show some unique representation
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
pp({tvar, _, Name}) ->
Name;
pp({if_t, _, Id, Then, Else}) ->
["if(", pp([Id, Then, Else]), ")"];
pp({tuple_t, _, []}) ->
"unit";
pp({tuple_t, _, Cpts}) ->
["(", string:join(lists:map(fun pp/1, Cpts), " * "), ")"];
pp({bytes_t, _, any}) -> "bytes(_)";
pp({bytes_t, _, Len}) ->
["bytes(", integer_to_list(Len), ")"];
pp({app_t, _, T, []}) ->
pp(T);
pp({app_t, _, Type, Args}) ->
[pp(Type), "(", pp(Args), ")"];
pp({named_arg_t, _, Name, Type, _Default}) ->
[pp(Name), " : ", pp(Type)];
pp({fun_t, _, Named = {uvar, _, _}, As, B}) ->
["(", pp(Named), " | ", pp(As), ") => ", pp(B)];
pp({fun_t, _, Named, As, B}) when is_list(Named) ->
["(", pp(Named ++ As), ") => ", pp(B)];
pp(Other) ->
io_lib:format("~p", [Other]).
+134
View File
@@ -0,0 +1,134 @@
-module(aeso_tc_type_unfolding).
-export([ unfold_types_in_type/2
, unfold_types_in_type/3
, unfold_record_types/2
]).
%% -- Duplicated macros ------------------------------------------------------
-define(is_type_id(T), element(1, T) =:= id orelse
element(1, T) =:= qid orelse
element(1, T) =:= con orelse
element(1, T) =:= qcon).
%% -- Moved functions --------------------------------------------------------
type_error(A) -> aeso_tc_errors:type_error(A).
%% -------
used_typedef(A, B) -> aeso_tc_warnings:used_typedef(A, B).
%% -------
when_warning(A, B) -> aeso_tc_options:when_warning(A, B).
%% ---------------------------------------------------------------------------
%% During type inference, record types are represented by their
%% names. But, before we pass the typed program to the code generator,
%% we replace record types annotating expressions with their
%% definition. This enables the code generator to see the fields.
unfold_record_types(Env, T) ->
unfold_types(Env, T, [unfold_record_types]).
unfold_types(Env, {typed, Attr, E, Type}, Options) ->
Options1 = [{ann, Attr} | lists:keydelete(ann, 1, Options)],
{typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options1)};
unfold_types(Env, {arg, Attr, Id, Type}, Options) ->
{arg, Attr, Id, unfold_types_in_type(Env, Type, Options)};
unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) ->
{type_sig, Ann, Constr,
unfold_types_in_type(Env, NamedArgs, Options),
unfold_types_in_type(Env, Args, Options),
unfold_types_in_type(Env, Ret, Options)};
unfold_types(Env, {type_def, Ann, Name, Args, Def}, Options) ->
{type_def, Ann, Name, Args, unfold_types_in_type(Env, Def, Options)};
unfold_types(Env, {fun_decl, Ann, Name, Type}, Options) ->
{fun_decl, Ann, Name, unfold_types(Env, Type, Options)};
unfold_types(Env, {letfun, Ann, Name, Args, Type, [{guarded, AnnG, [], Body}]}, Options) ->
{letfun, Ann, Name, unfold_types(Env, Args, Options), unfold_types_in_type(Env, Type, Options), [{guarded, AnnG, [], unfold_types(Env, Body, Options)}]};
unfold_types(Env, T, Options) when is_tuple(T) ->
list_to_tuple(unfold_types(Env, tuple_to_list(T), Options));
unfold_types(Env, [H|T], Options) ->
[unfold_types(Env, H, Options)|unfold_types(Env, T, Options)];
unfold_types(_Env, X, _Options) ->
X.
unfold_types_in_type(Env, T) ->
unfold_types_in_type(Env, T, []).
unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) ->
Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options),
Ann1 = proplists:get_value(ann, Options, aeso_syntax:get_ann(KeyType0)),
[ type_error({map_in_map_key, Ann1, KeyType0}) || has_maps(KeyType) ],
{app_t, Ann, Id, Args1};
unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) ->
when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, length(Args)) end),
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
case aeso_tc_env:lookup_type(Env, Id) of
{_, {_, {Formals, {record_t, Fields}}}} when UnfoldRecords, length(Formals) == length(Args) ->
{record_t,
unfold_types_in_type(Env,
subst_tvars(lists:zip(Formals, Args), Fields), Options)};
{_, {_, {Formals, {alias_t, Type}}}} when length(Formals) == length(Args) ->
unfold_types_in_type(Env, subst_tvars(lists:zip(Formals, Args), Type), Options);
{_, {_, {Formals, {variant_t, Constrs}}}} when UnfoldVariants, length(Formals) == length(Args) ->
%% TODO: unfolding variant types will not work well if we add recursive types!
{variant_t,
unfold_types_in_type(Env,
subst_tvars(lists:zip(Formals, Args), Constrs), Options)};
_ ->
%% Not a record type, or ill-formed record type.
{app_t, Ann, Id, unfold_types_in_type(Env, Args, Options)}
end;
unfold_types_in_type(Env, Id, Options) when ?is_type_id(Id) ->
%% Like the case above, but for types without parameters.
when_warning(warn_unused_typedefs, fun() -> used_typedef(Id, 0) end),
UnfoldRecords = proplists:get_value(unfold_record_types, Options, false),
UnfoldVariants = proplists:get_value(unfold_variant_types, Options, false),
case aeso_tc_env:lookup_type(Env, Id) of
{_, {_, {[], {record_t, Fields}}}} when UnfoldRecords ->
{record_t, unfold_types_in_type(Env, Fields, Options)};
{_, {_, {[], {variant_t, Constrs}}}} when UnfoldVariants ->
{variant_t, unfold_types_in_type(Env, Constrs, Options)};
{_, {_, {[], {alias_t, Type1}}}} ->
unfold_types_in_type(Env, Type1, Options);
_ ->
%% Not a record type, or ill-formed record type
Id
end;
unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
unfold_types_in_type(Env, [H|T], Options) ->
[unfold_types_in_type(Env, H, Options)|unfold_types_in_type(Env, T, Options)];
unfold_types_in_type(_Env, X, _Options) ->
X.
has_maps({app_t, _, {id, _, "map"}, _}) ->
true;
has_maps(L) when is_list(L) ->
lists:any(fun has_maps/1, L);
has_maps(T) when is_tuple(T) ->
has_maps(tuple_to_list(T));
has_maps(_) -> false.
subst_tvars(Env, Type) ->
subst_tvars1([{V, T} || {{tvar, _, V}, T} <- Env], Type).
subst_tvars1(Env, T={tvar, _, Name}) ->
proplists:get_value(Name, Env, T);
subst_tvars1(Env, [H|T]) ->
[subst_tvars1(Env, H)|subst_tvars1(Env, T)];
subst_tvars1(Env, Type) when is_tuple(Type) ->
list_to_tuple(subst_tvars1(Env, tuple_to_list(Type)));
subst_tvars1(_Env, X) ->
X.
+91
View File
@@ -0,0 +1,91 @@
-module(aeso_tc_type_utils).
-export([ fresh_uvar/1
, dereference/1
, dereference_deep/1
, instantiate/1
, typesig_to_fun_t/1
, fun_arity/1
, opposite_variance/1
, app_t/3
, is_first_order/1
, is_monomorphic/1
]).
%% TODO: Find a better place for this function
fresh_uvar(Attrs) ->
{uvar, Attrs, make_ref()}.
dereference(T = {uvar, _, R}) ->
case aeso_tc_ets_manager:ets_lookup(type_vars, R) of
[] ->
T;
[{R, Type}] ->
dereference(Type)
end;
dereference(T) ->
T.
dereference_deep(Type) ->
case dereference(Type) of
Tup when is_tuple(Tup) ->
list_to_tuple(dereference_deep(tuple_to_list(Tup)));
[H | T] -> [dereference_deep(H) | dereference_deep(T)];
T -> T
end.
%% Dereferences all uvars and replaces the uninstantiated ones with a
%% succession of tvars.
instantiate(E) ->
instantiate1(dereference(E)).
instantiate1({uvar, Attr, R}) ->
Next = proplists:get_value(next, aeso_tc_ets_manager:ets_lookup(type_vars, next), 0),
TVar = {tvar, Attr, "'" ++ integer_to_tvar(Next)},
aeso_tc_ets_manager:ets_insert(type_vars, [{next, Next + 1}, {R, TVar}]),
TVar;
instantiate1({fun_t, Ann, Named, Args, Ret}) ->
case dereference(Named) of
{uvar, _, R} ->
%% Uninstantiated named args map to the empty list
NoNames = [],
aeso_tc_ets_manager:ets_insert(type_vars, [{R, NoNames}]),
{fun_t, Ann, NoNames, instantiate(Args), instantiate(Ret)};
Named1 ->
{fun_t, Ann, instantiate1(Named1), instantiate(Args), instantiate(Ret)}
end;
instantiate1(T) when is_tuple(T) ->
list_to_tuple(instantiate1(tuple_to_list(T)));
instantiate1([A|B]) ->
[instantiate(A)|instantiate(B)];
instantiate1(X) ->
X.
integer_to_tvar(X) when X < 26 ->
[$a + X];
integer_to_tvar(X) ->
[integer_to_tvar(X div 26)] ++ [$a + (X rem 26)].
fun_arity({fun_t, _, _, Args, _}) -> length(Args);
fun_arity(_) -> none.
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({fun_t, _, _, _, _}) -> 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.
opposite_variance(invariant) -> invariant;
opposite_variance(covariant) -> contravariant;
opposite_variance(contravariant) -> covariant;
opposite_variance(bivariant) -> bivariant.
app_t(_Ann, Name, []) -> Name;
app_t(Ann, Name, Args) -> {app_t, Ann, Name, Args}.
typesig_to_fun_t({type_sig, Ann, _Constr, Named, Args, Res}) ->
{fun_t, Ann, Named, Args, Res}.
+20
View File
@@ -0,0 +1,20 @@
-module(aeso_tc_typedefs).
-export_type([utype/0, named_args_t/0, typesig/0]).
-type uvar() :: {uvar, aeso_syntax:ann(), reference()}.
-type named_args_t() :: uvar() | [{named_arg_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), aeso_syntax:expr()}].
-type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()] | var_args, utype()}
| {app_t, aeso_syntax:ann(), utype(), [utype()]}
| {tuple_t, aeso_syntax:ann(), [utype()]}
| aeso_syntax:id() | aeso_syntax:qid()
| aeso_syntax:con() | aeso_syntax:qcon() %% contracts
| aeso_syntax:tvar()
| {if_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), utype()} %% Can branch on named argument (protected)
| uvar().
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract | bytecode_hash.
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [aeso_syntax:type()], aeso_syntax:type()}.
+190
View File
@@ -0,0 +1,190 @@
-module(aeso_tc_unify).
-export([unify/4]).
%% -- Moved functions --------------------------------------------------------
unfold_types_in_type(A, B, C) -> aeso_tc_type_unfolding:unfold_types_in_type(A, B, C).
%% -------
type_error(A) -> aeso_tc_errors:type_error(A).
cannot_unify(A, B, C, D) -> aeso_tc_errors:cannot_unify(A, B, C, D).
%% -------
opposite_variance(A) -> aeso_tc_type_utils:opposite_variance(A).
%% ---------------------------------------------------------------------------
unify(Env, A, B, When) -> unify0(Env, A, B, covariant, When).
unify0(_, {id, _, "_"}, _, _Variance, _When) -> true;
unify0(_, _, {id, _, "_"}, _Variance, _When) -> true;
unify0(Env, A, B, Variance, When) ->
Options =
case When of %% Improve source location for map_in_map_key errors
{check_expr, E, _, _} -> [{ann, aeso_syntax:get_ann(E)}];
_ -> []
end,
A1 = aeso_tc_type_utils:dereference(unfold_types_in_type(Env, A, Options)),
B1 = aeso_tc_type_utils:dereference(unfold_types_in_type(Env, B, Options)),
unify1(Env, A1, B1, Variance, When).
unify1(_Env, {uvar, _, R}, {uvar, _, R}, _Variance, _When) ->
true;
unify1(_Env, {uvar, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(Env, {uvar, A, R}, T, _Variance, When) ->
case occurs_check(R, T) of
true ->
case aeso_tc_env:unify_throws(Env) of
true ->
cannot_unify({uvar, A, R}, T, none, When);
false ->
ok
end,
false;
false ->
aeso_tc_ets_manager:ets_insert(type_vars, {R, T}),
true
end;
unify1(Env, T, {uvar, A, R}, Variance, When) ->
unify1(Env, {uvar, A, R}, T, Variance, When);
unify1(_Env, {tvar, _, X}, {tvar, _, X}, _Variance, _When) -> true; %% Rigid type variables
unify1(Env, [A|B], [C|D], [V|Variances], When) ->
unify0(Env, A, C, V, When) andalso unify0(Env, B, D, Variances, When);
unify1(Env, [A|B], [C|D], Variance, When) ->
unify0(Env, A, C, Variance, When) andalso unify0(Env, B, D, Variance, When);
unify1(_Env, X, X, _Variance, _When) ->
true;
unify1(_Env, _A, {id, _, "void"}, Variance, _When)
when Variance == covariant orelse Variance == bivariant ->
true;
unify1(_Env, {id, _, "void"}, _B, Variance, _When)
when Variance == contravariant orelse Variance == bivariant ->
true;
unify1(_Env, {id, _, Name}, {id, _, Name}, _Variance, _When) ->
true;
unify1(Env, A = {con, _, NameA}, B = {con, _, NameB}, Variance, When) ->
case is_subtype(Env, NameA, NameB, Variance) of
true -> true;
false ->
case aeso_tc_env:unify_throws(Env) of
true ->
IsSubtype = is_subtype(Env, NameA, NameB, contravariant) orelse
is_subtype(Env, NameA, NameB, covariant),
Cxt = case IsSubtype of
true -> Variance;
false -> none
end,
cannot_unify(A, B, Cxt, When);
false ->
ok
end,
false
end;
unify1(_Env, {qid, _, Name}, {qid, _, Name}, _Variance, _When) ->
true;
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _Variance, _When) ->
true;
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _Variance, _When) ->
true;
unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, Variance, When) ->
unify0(Env, Then1, Then2, Variance, When) andalso
unify0(Env, Else1, Else2, Variance, When);
unify1(_Env, {fun_t, _, _, _, _}, {fun_t, _, _, var_args, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(_Env, {fun_t, _, _, var_args, _}, {fun_t, _, _, _, _}, _Variance, When) ->
type_error({unify_varargs, When});
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, Variance, When)
when length(Args1) == length(Args2) ->
unify0(Env, Named1, Named2, opposite_variance(Variance), When) andalso
unify0(Env, Args1, Args2, opposite_variance(Variance), When) andalso
unify0(Env, Result1, Result2, Variance, When);
unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, Variance, When)
when length(Args1) == length(Args2), Tag == id orelse Tag == qid ->
Variances = case aeso_tc_ets_manager:ets_lookup(type_vars_variance, F) of
[{_, Vs}] ->
case Variance of
contravariant -> lists:map(fun opposite_variance/1, Vs);
invariant -> invariant;
_ -> Vs
end;
_ -> invariant
end,
unify1(Env, Args1, Args2, Variances, When);
unify1(Env, {tuple_t, _, As}, {tuple_t, _, Bs}, Variance, When)
when length(As) == length(Bs) ->
unify0(Env, As, Bs, Variance, When);
unify1(Env, {named_arg_t, _, Id1, Type1, _}, {named_arg_t, _, Id2, Type2, _}, Variance, When) ->
unify1(Env, Id1, Id2, Variance, {arg_name, Id1, Id2, When}),
unify1(Env, Type1, Type2, Variance, When);
%% The grammar is a bit inconsistent about whether types without
%% arguments are represented as applications to an empty list of
%% parameters or not. We therefore allow them to unify.
unify1(Env, {app_t, _, T, []}, B, Variance, When) ->
unify0(Env, T, B, Variance, When);
unify1(Env, A, {app_t, _, T, []}, Variance, When) ->
unify0(Env, A, T, Variance, When);
unify1(Env, A, B, _Variance, When) ->
case aeso_tc_env:unify_throws(Env) of
true ->
cannot_unify(A, B, none, When);
false ->
ok
end,
false.
is_subtype(_Env, NameA, NameB, invariant) ->
NameA == NameB;
is_subtype(Env, NameA, NameB, covariant) ->
is_subtype(Env, NameA, NameB);
is_subtype(Env, NameA, NameB, contravariant) ->
is_subtype(Env, NameB, NameA);
is_subtype(Env, NameA, NameB, bivariant) ->
is_subtype(Env, NameA, NameB) orelse is_subtype(Env, NameB, NameA).
is_subtype(Env, Child, Base) ->
Parents = maps:get(Child, aeso_tc_env:contract_parents(Env), []),
if
Child == Base ->
true;
Parents == [] ->
false;
true ->
case lists:member(Base, Parents) of
true -> true;
false -> lists:any(fun(Parent) -> is_subtype(Env, Parent, Base) end, Parents)
end
end.
occurs_check(R, T) ->
occurs_check1(R, aeso_tc_type_utils:dereference(T)).
occurs_check1(R, {uvar, _, R1}) -> R == R1;
occurs_check1(_, {id, _, _}) -> false;
occurs_check1(_, {con, _, _}) -> false;
occurs_check1(_, {qid, _, _}) -> false;
occurs_check1(_, {qcon, _, _}) -> false;
occurs_check1(_, {tvar, _, _}) -> false;
occurs_check1(_, {bytes_t, _, _}) -> false;
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
occurs_check(R, [Res, Named | Args]);
occurs_check1(R, {app_t, _, T, Ts}) ->
occurs_check(R, [T | Ts]);
occurs_check1(R, {tuple_t, _, Ts}) ->
occurs_check(R, Ts);
occurs_check1(R, {named_arg_t, _, _, T, _}) ->
occurs_check(R, T);
occurs_check1(R, {record_t, Fields}) ->
occurs_check(R, Fields);
occurs_check1(R, {field_t, _, _, T}) ->
occurs_check(R, T);
occurs_check1(R, {if_t, _, _, Then, Else}) ->
occurs_check(R, [Then, Else]);
occurs_check1(R, [H | T]) ->
occurs_check(R, H) orelse occurs_check(R, T);
occurs_check1(_, []) -> false.
+231
View File
@@ -0,0 +1,231 @@
-module(aeso_tc_warnings).
-export([ warn_potential_shadowing/3
, used_include/1
, create_unused_functions/0
, destroy_and_report_unused_functions/0
, destroy_and_report_warnings_as_type_errors/0
, potential_unused_include/2
, potential_unused_typedefs/2
, potential_unused_constants/2
, potential_unused_stateful/2
, potential_unused_variables/3
, potential_unused_function/4
, mk_warning/1
, used_variable/3
, register_function_call/2
, used_constant/2
, used_stateful/1
, warn_potential_negative_spend/3
, warn_potential_division_by_zero/3
, potential_unused_return_value/1
, used_typedef/2
, all_warnings/0
]).
%% -- Moved functions --------------------------------------------------------
name(A) -> aeso_tc_name_manip:name(A).
qname(A) -> aeso_tc_name_manip:qname(A).
%% -------
pos(A) -> aeso_tc_ann_manip:pos(A).
%% -------
pp_loc(A) -> aeso_tc_pp:pp_loc(A).
%% ---------------------------------------------------------------------------
all_warnings() ->
[ warn_unused_includes
, warn_unused_stateful
, warn_unused_variables
, warn_unused_constants
, warn_unused_typedefs
, warn_unused_return_value
, warn_unused_functions
, warn_shadowing
, warn_division_by_zero
, warn_negative_spend ].
%% Warnings (Unused includes)
potential_unused_include(Ann, SrcFile) ->
IsIncluded = aeso_syntax:get_ann(include_type, Ann, none) =/= none,
case IsIncluded of
false -> ok;
true ->
case aeso_syntax:get_ann(file, Ann, no_file) of
no_file -> ok;
File -> aeso_tc_ets_manager:ets_insert(warnings, {unused_include, File, SrcFile})
end
end.
used_include(Ann) ->
case aeso_syntax:get_ann(file, Ann, no_file) of
no_file -> ok;
File -> aeso_tc_ets_manager:ets_match_delete(warnings, {unused_include, File, '_'})
end.
%% Warnings (Unused stateful)
potential_unused_stateful(Ann, Fun) ->
case aeso_syntax:get_ann(stateful, Ann, false) of
false -> ok;
true -> aeso_tc_ets_manager:ets_insert(warnings, {unused_stateful, Ann, Fun})
end.
used_stateful(Fun) ->
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_stateful, '_', Fun}).
%% Warnings (Unused type defs)
potential_unused_typedefs(Namespace, TypeDefs) ->
lists:map(fun({type_def, Ann, Id, Args, _}) ->
aeso_tc_ets_manager:ets_insert(warnings, {unused_typedef, Ann, Namespace ++ qname(Id), length(Args)}) end, TypeDefs).
used_typedef(TypeAliasId, Arity) ->
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_typedef, '_', qname(TypeAliasId), Arity}).
%% Warnings (Unused variables)
potential_unused_variables(Namespace, Fun, Vars0) ->
Vars = [ Var || Var = {id, _, VarName} <- Vars0, VarName /= "_" ],
lists:map(fun({id, Ann, VarName}) ->
aeso_tc_ets_manager:ets_insert(warnings, {unused_variable, Ann, Namespace, Fun, VarName}) end, Vars).
used_variable(Namespace, Fun, [VarName]) ->
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_variable, '_', Namespace, Fun, VarName});
used_variable(_, _, _) -> ok.
%% Warnings (Unused constants)
potential_unused_constants(Env, Consts) ->
case aeso_tc_env:what(Env) of
namespace -> [];
_ ->
[ aeso_tc_ets_manager:ets_insert(warnings, {unused_constant, Ann, aeso_tc_env:namespace(Env), Name}) || {letval, _, {id, Ann, Name}, _} <- Consts ]
end.
used_constant(Namespace = [Contract], [Contract, ConstName]) ->
aeso_tc_ets_manager:ets_match_delete(warnings, {unused_constant, '_', Namespace, ConstName});
used_constant(_, _) -> ok.
%% Warnings (Unused return value)
potential_unused_return_value({typed, Ann, {app, _, {typed, _, _, {fun_t, _, _, _, {id, _, Type}}}, _}, _}) when Type /= "unit" ->
aeso_tc_ets_manager:ets_insert(warnings, {unused_return_value, Ann});
potential_unused_return_value(_) -> ok.
%% Warnings (Unused functions)
create_unused_functions() ->
aeso_tc_ets_manager:ets_new(function_calls, [bag]),
aeso_tc_ets_manager:ets_new(all_functions, [set]).
register_function_call(Caller, Callee) ->
aeso_tc_ets_manager:ets_insert(function_calls, {Caller, Callee}).
potential_unused_function(Env, Ann, FunQName, FunId) ->
case aeso_tc_env:what(Env) of
namespace ->
aeso_tc_ets_manager:ets_insert(all_functions, {Ann, FunQName, FunId, not aeso_syntax:get_ann(private, Ann, false)});
_ ->
aeso_tc_ets_manager:ets_insert(all_functions, {Ann, FunQName, FunId, aeso_syntax:get_ann(entrypoint, Ann, false)})
end.
remove_used_funs(All) ->
{Used, Unused} = lists:partition(fun({_, _, _, IsUsed}) -> IsUsed end, All),
CallsByUsed = lists:flatmap(fun({_, F, _, _}) -> aeso_tc_ets_manager:ets_lookup(function_calls, F) end, Used),
CalledFuns = sets:from_list(lists:map(fun({_, Callee}) -> Callee end, CallsByUsed)),
MarkUsedFun = fun(Fun, Acc) ->
case lists:keyfind(Fun, 2, Acc) of
false -> Acc;
T -> lists:keyreplace(Fun, 2, Acc, setelement(4, T, true))
end
end,
NewUnused = sets:fold(MarkUsedFun, Unused, CalledFuns),
case lists:keyfind(true, 4, NewUnused) of
false -> NewUnused;
_ -> remove_used_funs(NewUnused)
end.
destroy_and_report_unused_functions() ->
AllFuns = aeso_tc_ets_manager:ets_tab2list(all_functions),
lists:map(fun({Ann, _, FunId, _}) -> aeso_tc_ets_manager:ets_insert(warnings, {unused_function, Ann, name(FunId)}) end,
remove_used_funs(AllFuns)),
aeso_tc_ets_manager:ets_delete(all_functions),
aeso_tc_ets_manager:ets_delete(function_calls).
%% Warnings (Shadowing)
warn_potential_shadowing(_, _, "_") -> ok;
warn_potential_shadowing(Env, Ann, Name) ->
Vars = aeso_tc_env:vars(Env),
Consts = aeso_tc_env:scope_consts(aeso_tc_env:get_current_scope(Env)),
case proplists:get_value(Name, Vars ++ Consts, false) of
false -> ok;
{AnnOld, _} -> aeso_tc_ets_manager:ets_insert(warnings, {shadowing, Ann, Name, AnnOld})
end.
%% Warnings (Division by zero)
warn_potential_division_by_zero(Ann, Op, Args) ->
case {Op, Args} of
{{'/', _}, [_, {int, _, 0}]} -> aeso_tc_ets_manager:ets_insert(warnings, {division_by_zero, Ann});
_ -> ok
end.
%% Warnings (Negative spends)
warn_potential_negative_spend(Ann, Fun, Args) ->
case {Fun, Args} of
{ {typed, _, {qid, _, ["Chain", "spend"]}, _}
, [_, {typed, _, {app, _, {'-', _}, [{typed, _, {int, _, X}, _}]}, _}]} when X > 0 ->
aeso_tc_ets_manager:ets_insert(warnings, {negative_spend, Ann});
_ -> ok
end.
destroy_and_report_warnings_as_type_errors() ->
Warnings = [ mk_warning(Warn) || Warn <- aeso_tc_ets_manager:ets_tab2list(warnings) ],
Errors = lists:map(fun mk_t_err_from_warn/1, Warnings),
aeso_errors:throw(Errors). %% No-op if Warnings == []
mk_t_err_from_warn(Warn) ->
aeso_warnings:warn_to_err(type_error, Warn).
mk_warning({unused_include, FileName, SrcFile}) ->
Msg = io_lib:format("The file `~s` is included but not used.", [FileName]),
aeso_warnings:new(aeso_errors:pos(SrcFile, 0, 0), Msg);
mk_warning({unused_stateful, Ann, FunName}) ->
Msg = io_lib:format("The function `~s` is unnecessarily marked as stateful.", [name(FunName)]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_variable, Ann, _Namespace, _Fun, VarName}) ->
Msg = io_lib:format("The variable `~s` is defined but never used.", [VarName]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_constant, Ann, _Namespace, ConstName}) ->
Msg = io_lib:format("The constant `~s` is defined but never used.", [ConstName]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_typedef, Ann, QName, _Arity}) ->
Msg = io_lib:format("The type `~s` is defined but never used.", [lists:last(QName)]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_return_value, Ann}) ->
Msg = io_lib:format("Unused return value.", []),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({unused_function, Ann, FunName}) ->
Msg = io_lib:format("The function `~s` is defined but never used.", [FunName]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({shadowing, Ann, VarName, AnnOld}) ->
Msg = io_lib:format("The definition of `~s` shadows an older definition at ~s.", [VarName, pp_loc(AnnOld)]),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({division_by_zero, Ann}) ->
Msg = io_lib:format("Division by zero.", []),
aeso_warnings:new(pos(Ann), Msg);
mk_warning({negative_spend, Ann}) ->
Msg = io_lib:format("Negative spend.", []),
aeso_warnings:new(pos(Ann), Msg);
mk_warning(Warn) ->
Msg = io_lib:format("Unknown warning: ~p", [Warn]),
aeso_warnings:new(Msg).
+6
View File
@@ -0,0 +1,6 @@
-define(IS_CONTRACT_HEAD(X),
(X =:= contract_main orelse
X =:= contract_interface orelse
X =:= contract_child
)
).
+97 -62
View File
@@ -1,75 +1,15 @@
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Decoding aevm and fate data to AST
%%%
%%% @doc Decoding fate data to AST
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_vm_decode).
-export([ from_aevm/3, from_fate/2 ]).
-export([ from_fate/2 ]).
-include_lib("aebytecode/include/aeb_fate_data.hrl").
address_literal(Type, N) -> {Type, [], <<N:256>>}.
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>};
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
end;
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [from_aevm(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
from_aevm(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
from_aevm(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
@@ -120,10 +60,105 @@ from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate({qid, _, QType}, Val) ->
from_fate_builtin(QType, Val);
from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia).
from_fate_builtin(QType, Val) ->
Con = fun([Name | _] = Names) when is_list(Name) -> {qcon, [], Names};
(Name) -> {con, [], Name} end,
App = fun(Name, []) -> Con(Name);
(Name, Value) -> {app, [], Con(Name), Value} end,
Chk = fun(Type, Value) -> from_fate(Type, Value) end,
Int = {id, [], "int"},
Str = {id, [], "string"},
Adr = {id, [], "address"},
Hsh = {bytes_t, [], 32},
I32 = {bytes_t, [], 32},
I48 = {bytes_t, [], 48},
Qid = fun(Name) -> {qid, [], Name} end,
Map = fun(KT, VT) -> {app_t, [], {id, [], "map"}, [KT, VT]} end,
ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
case {QType, Val} of
{["Chain", "ttl"], {variant, [1, 1], 0, {X}}} -> App("RelativeTTL", [Chk(Int, X)]);
{["Chain", "ttl"], {variant, [1, 1], 1, {X}}} -> App("FixedTTL", [Chk(Int, X)]);
{["AENS", "name"], {variant, [3], 0, {Addr, TTL, Ptrs}}} ->
App(["AENS","Name"], [Chk(Adr, Addr), Chk(Qid(["Chain", "ttl"]), TTL),
Chk(Map(Str, Qid(["AENS", "pointee"])), Ptrs)]);
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 0, {Addr}}} ->
App(["AENS","AccountPt"], [Chk(Adr, Addr)]);
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 1, {Addr}}} ->
App(["AENS","OraclePt"], [Chk(Adr, Addr)]);
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 2, {Addr}}} ->
App(["AENS","ContractPt"], [Chk(Adr, Addr)]);
{["AENS", "pointee"], {variant, [1, 1, 1, 1], 3, {Addr}}} ->
App(["AENS","ChannelPt"], [Chk(Adr, Addr)]);
{["Chain", "ga_meta_tx"], {variant, [2], 0, {Addr, X}}} ->
App(["Chain","GAMetaTx"], [Chk(Adr, Addr), Chk(Int, X)]);
{["Chain", "paying_for_tx"], {variant, [2], 0, {Addr, X}}} ->
App(["Chain","PayingForTx"], [Chk(Adr, Addr), Chk(Int, X)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 0, {Addr, Fee, Payload}}} ->
App(["Chain","SpendTx"], [Chk(Adr, Addr), Chk(Int, Fee), Chk(Str, Payload)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 1, {}}} ->
App(["Chain","OracleRegisterTx"], []);
{["Chain", "base_tx"], {variant, ChainTxArities, 2, {}}} ->
App(["Chain","OracleQueryTx"], []);
{["Chain", "base_tx"], {variant, ChainTxArities, 3, {}}} ->
App(["Chain","OracleResponseTx"], []);
{["Chain", "base_tx"], {variant, ChainTxArities, 4, {}}} ->
App(["Chain","OracleExtendTx"], []);
{["Chain", "base_tx"], {variant, ChainTxArities, 5, {}}} ->
App(["Chain","NamePreclaimTx"], []);
{["Chain", "base_tx"], {variant, ChainTxArities, 6, {Name}}} ->
App(["Chain","NameClaimTx"], [Chk(Str, Name)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 7, {NameHash}}} ->
App(["Chain","NameUpdateTx"], [Chk(Hsh, NameHash)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 8, {NameHash}}} ->
App(["Chain","NameRevokeTx"], [Chk(Hsh, NameHash)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 9, {NewOwner, NameHash}}} ->
App(["Chain","NameTransferTx"], [Chk(Adr, NewOwner), Chk(Hsh, NameHash)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 10, {Addr}}} ->
App(["Chain","ChannelCreateTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 11, {Addr, Amount}}} ->
App(["Chain","ChannelDepositTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 12, {Addr, Amount}}} ->
App(["Chain","ChannelWithdrawTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 13, {Addr}}} ->
App(["Chain","ChannelForceProgressTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 14, {Addr}}} ->
App(["Chain","ChannelCloseMutualTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 15, {Addr}}} ->
App(["Chain","ChannelCloseSoloTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 16, {Addr}}} ->
App(["Chain","ChannelSlashTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 17, {Addr}}} ->
App(["Chain","ChannelSettleTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 18, {Addr}}} ->
App(["Chain","ChannelSnapshotSoloTx"], [Chk(Adr, Addr)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 19, {Amount}}} ->
App(["Chain","ContractCreateTx"], [Chk(Int, Amount)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 20, {Addr, Amount}}} ->
App(["Chain","ContractCallTx"], [Chk(Adr, Addr), Chk(Int, Amount)]);
{["Chain", "base_tx"], {variant, ChainTxArities, 21, {}}} ->
App(["Chain","GAAttachTx"], []);
{["MCL_BLS12_381", "fp"], X} ->
App(["MCL_BLS12_381", "fp"], [Chk(I32, X)]);
{["MCL_BLS12_381", "fr"], X} ->
App(["MCL_BLS12_381", "fr"], [Chk(I48, X)]);
_ ->
throw(cannot_translate_to_sophia)
end.
make_bits(N) ->
Id = fun(F) -> {qid, [], ["Bits", F]} end,
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
+31
View File
@@ -0,0 +1,31 @@
-module(aeso_warnings).
-record(warn, { pos :: aeso_errors:pos()
, message :: iolist()
}).
-opaque warning() :: #warn{}.
-export_type([warning/0]).
-export([ new/1
, new/2
, warn_to_err/2
, sort_warnings/1
, pp/1
]).
new(Msg) ->
new(aeso_errors:pos(0, 0), Msg).
new(Pos, Msg) ->
#warn{ pos = Pos, message = Msg }.
warn_to_err(Kind, #warn{ pos = Pos, message = Msg }) ->
aeso_errors:new(Kind, Pos, lists:flatten(Msg)).
sort_warnings(Warnings) ->
lists:sort(fun(W1, W2) -> W1#warn.pos =< W2#warn.pos end, Warnings).
pp(#warn{ pos = Pos, message = Msg }) ->
lists:flatten(io_lib:format("Warning~s:\n~s", [aeso_errors:pp_pos(Pos), Msg])).
+2 -2
View File
@@ -1,6 +1,6 @@
{application, aesophia,
[{description, "Contract Language for aeternity"},
{vsn, "4.3.0"},
[{description, "Compiler for Aeternity Sophia language"},
{vsn, "7.1.0"},
{registered, []},
{applications,
[kernel,
+79 -91
View File
@@ -5,7 +5,6 @@
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
-define(DUMMY_HASH_WORD, 16#123).
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
sandbox(Code) ->
@@ -20,12 +19,6 @@ sandbox(Code) ->
{error, loop}
end.
malicious_from_binary_test() ->
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
ok.
from_words(Ws) ->
<< <<(from_word(W))/binary>> || W <- Ws >>.
@@ -37,23 +30,14 @@ from_word(S) when is_list(S) ->
<<Len:256, Bin/binary>>.
encode_decode_test() ->
encode_decode(word, 42),
42 = encode_decode(word, 42),
-1 = encode_decode(signed_word, -1),
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
{} = encode_decode({tuple, []}, {}),
{42} = encode_decode({tuple, [word]}, {42}),
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
[] = encode_decode({list, word}, []),
[32] = encode_decode({list, word}, [32]),
none = encode_decode({option, word}, none),
{some, 1} = encode_decode({option, word}, {some, 1}),
string = encode_decode(typerep, string),
word = encode_decode(typerep, word),
{list, word} = encode_decode(typerep, {list, word}),
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
1 = encode_decode(word, 1),
0 = encode_decode(word, 0),
Tests =
[42, 1, 0 -1, <<"Hello">>,
{tuple, {}}, {tuple, {42}}, {tuple, {21, 37}},
[], [42], [21, 37],
{variant, [0, 1], 0, {}}, {variant, [0, 1], 1, {42}}, {variant, [2], 0, {21, 37}},
{typerep, string}, {typerep, integer}, {typerep, {list, integer}}, {typerep, {tuple, [integer]}}
],
[?assertEqual(Test, encode_decode(Test)) || Test <- Tests],
ok.
encode_decode_sophia_test() ->
@@ -72,55 +56,67 @@ encode_decode_sophia_test() ->
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok.
to_sophia_value_mcl_bls12_381_test() ->
Code = "include \"BLS12_381.aes\"\n"
"contract C =\n"
" entrypoint test_bls12_381_fp(x : int) = BLS12_381.int_to_fp(x)\n"
" entrypoint test_bls12_381_fr(x : int) = BLS12_381.int_to_fr(x)\n"
" entrypoint test_bls12_381_g1(x : int) = BLS12_381.mk_g1(x, x, x)\n",
Opts = [{backend, fate}],
CallValue32 = aeb_fate_encoding:serialize({bytes, <<20:256>>}),
CallValue48 = aeb_fate_encoding:serialize({bytes, <<55:384>>}),
CallValueTp = aeb_fate_encoding:serialize({tuple, {{bytes, <<15:256>>}, {bytes, <<160:256>>}, {bytes, <<1234:256>>}}}),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue32, Opts),
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fp", ok, CallValue48, Opts),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue48, Opts),
{error, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_fr", ok, CallValue32, Opts),
{ok, _} = aeso_compiler:to_sophia_value(Code, "test_bls12_381_g1", ok, CallValueTp, Opts),
ok.
to_sophia_value_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
" entrypoint f(x : 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, [Err1]} = aeso_compiler:to_sophia_value(Code, "f", ok, encode(12)),
?assertEqual("Data error:\nCannot translate FATE value 12\n of Sophia type string\n", aeso_errors:pp(Err1)),
{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)),
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "f", revert, encode(12)),
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err2)),
ok.
encode_calldata_neg_test() ->
Code = [ "contract Foo =\n"
" entrypoint x(y : int) : string = \"hello\"\n" ],
" entrypoint f(x : 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"]),
ExpErr1 = "Type error at line 5, col 34:\nCannot unify `int` and `bool`\n"
"when checking the application of\n"
" `f : (int) => string`\n"
"to arguments\n"
" `true : bool`\n",
{error, [Err1]} = aeso_compiler:create_calldata(Code, "f", ["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" ],
" entrypoint f(x : int) : string = \"hello\"\n" ],
Code2 = [ "contract Foo =\n"
" entrypoint x(y : string) : int = 42\n" ],
" entrypoint f(x : string) : int = 42\n" ],
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "f", ["42"]),
{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, [Err1]} = aeso_compiler:decode_calldata(Code2, "f", <<1,2,3>>),
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err1)),
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "f", CallDataFATE),
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err2)),
{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)),
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE),
?assertEqual("Data error at line 1, col 1:\nFunction 'x' is missing in contract\n", aeso_errors:pp(Err3)),
ok.
@@ -133,8 +129,7 @@ encode_decode_sophia_string(SophiaType, String) ->
, " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]),
{ok, _, [Arg]} ->
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
{ok, Sophia} ->
@@ -150,30 +145,32 @@ encode_decode_sophia_string(SophiaType, String) ->
calldata_test() ->
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
[{variant, [0,1], 1, {#{ <<"a">> := 4 }}}, {tuple, {{tuple, {<<"b">>, 5}}, {variant, [0,1], 0, {}}}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
[{bytes, <<291:256>>}, {address, <<1110:256>>}] =
encode_decode_calldata("foo", ["bytes(32)", "address"],
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
[{bytes, <<291:256>>}, {bytes, <<291:256>>}] =
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[119, {bytes, <<0:64/unit:8>>}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
[{contract, <<1110:256>>}] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
ok.
calldata_init_test() ->
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
encode_decode_calldata("init", ["int"], ["42"]),
Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
encode_decode_calldata_(Code, "init", []),
ok.
calldata_indent_test() ->
Test = fun(Extra) ->
Code = parameterized_contract(Extra, "foo", ["int"]),
encode_decode_calldata_(Code, "foo", ["42"], word)
encode_decode_calldata_(Code, "foo", ["42"])
end,
Test(" stateful entrypoint bla() = ()"),
Test(" type x = int"),
@@ -190,7 +187,7 @@ parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Remote =\n"
" entrypoint bla : () => unit\n\n"
"contract Dummy =\n",
"main contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = string * 'a\n"
" record r = {x : an_alias(int), y : variant}\n"
@@ -202,9 +199,9 @@ oracle_test() ->
"contract OracleTest =\n"
" entrypoint question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
?assertEqual({ok, "question", [{oracle, <<291:256>>}, {oracle_query, <<1110:256>>}]},
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], [no_code])),
ok.
@@ -220,35 +217,26 @@ permissive_literals_fail_test() ->
ok.
encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word).
encode_decode_calldata(FunName, Types, Args, RetType) ->
Code = parameterized_contract(FunName, Types),
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
encode_decode_calldata_(Code, FunName, Args) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
{ok, _, _} = aeso_compiler:check_call(Code, FunName, Args, [no_code]),
case FunName of
"init" ->
ok;
[];
_ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values})
end,
tuple_to_list(ArgTuple).
{ok, FateArgs} = aeb_fate_abi:decode_calldata(FunName, Calldata),
FateArgs
end.
encode_decode(T, D) ->
?assertEqual(D, decode(T, encode(D))),
encode_decode(D) ->
?assertEqual(D, decode(encode(D))),
D.
encode(D) ->
aeb_heap:to_binary(D).
aeb_fate_encoding:serialize(D).
decode(T,B) ->
{ok, D} = aeb_heap:from_binary(T, B),
D.
decode(B) ->
aeb_fate_encoding:deserialize(B).
+57 -48
View File
@@ -21,8 +21,9 @@ test_cases(1) ->
" payable stateful entrypoint a(i : int) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>,
type_defs => [],
payable => true,
typedefs => [],
payable => true,
kind => contract_main,
functions =>
[#{name => <<"a">>,
arguments =>
@@ -31,56 +32,57 @@ test_cases(1) ->
returns => <<"int">>,
stateful => true,
payable => true}]}},
DecACI = <<"payable contract C =\n"
" payable entrypoint a : (int) => int\n">>,
DecACI = <<"payable main contract C =\n"
" payable stateful entrypoint a : (int) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(2) ->
Contract = <<"contract C =\n"
Contract = <<"main contract C =\n"
" type allan = int\n"
" entrypoint a(i : allan) = i+1\n">>,
MapACI = #{contract =>
#{name => <<"C">>, payable => false,
type_defs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
functions =>
[#{arguments =>
[#{name => <<"i">>,
type => <<"C.allan">>}],
name => <<"a">>,
returns => <<"int">>,
stateful => false,
payable => false}]}},
DecACI = <<"contract C =\n"
#{name => <<"C">>, payable => false,
kind => contract_main,
typedefs =>
[#{name => <<"allan">>,
typedef => <<"int">>,
vars => []}],
functions =>
[#{arguments =>
[#{name => <<"i">>,
type => <<"C.allan">>}],
name => <<"a">>,
returns => <<"int">>,
stateful => false,
payable => false}]}},
DecACI = <<"main contract C =\n"
" type allan = int\n"
" entrypoint a : (C.allan) => int\n">>,
{Contract,MapACI,DecACI};
test_cases(3) ->
Contract = <<"contract C =\n"
Contract = <<"main contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
" entrypoint a(i : bert(string)) = 1\n">>,
" datatype bert('a) = Bin('a)\n"
" entrypoint a(i : bert(string)) = 1\n">>,
MapACI = #{contract =>
#{functions =>
[#{arguments =>
[#{name => <<"i">>,
type =>
#{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>,
stateful => false, payable => false}],
name => <<"C">>, payable => false,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>,
type_defs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"contract C =\n"
[#{arguments =>
[#{name => <<"i">>,
type =>
#{<<"C.bert">> => [<<"string">>]}}],
name => <<"a">>,returns => <<"int">>,
stateful => false, payable => false}],
name => <<"C">>, payable => false, kind => contract_main,
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
state => <<"unit">>,
typedefs =>
[#{name => <<"bert">>,
typedef =>
#{variant =>
[#{<<"Bin">> => [<<"'a">>]}]},
vars => [#{name => <<"'a">>}]}]}},
DecACI = <<"main contract C =\n"
" type state = unit\n"
" datatype event = SingleEventDefined\n"
" datatype bert('a) = Bin('a)\n"
@@ -101,24 +103,31 @@ aci_test_contract(Name) ->
true -> [debug_mode];
false -> []
end ++ [{include, {file_system, [aeso_test_utils:contract_path()]}}],
{ok, JSON} = aeso_aci:contract_interface(json, String, Opts),
{ok, #{aci := JSON1}} = aeso_compiler:from_string(String, [{aci, json}, {backend, fate} | Opts]),
?assertEqual(JSON, JSON1),
JSON = case aeso_aci:contract_interface(json, String, Opts) of
{ok, J} -> J;
{error, ErrorStringJ} when is_binary(ErrorStringJ) -> error(ErrorStringJ);
{error, ErrorJ} -> aeso_compiler_tests:print_and_throw(ErrorJ)
end,
case aeso_compiler:from_string(String, [{aci, json} | Opts]) of
{ok, #{aci := JSON1}} ->
?assertEqual(JSON, JSON1),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("JSON:\n~p\n", [JSON]),
{ok, ContractStub} = aeso_aci:render_aci_json(JSON),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
io:format("STUB:\n~s\n", [ContractStub]),
check_stub(ContractStub, [{src_file, Name}]),
ok.
ok;
{error, ErrorString} when is_binary(ErrorString) -> error(ErrorString);
{error, Error} -> aeso_compiler_tests:print_and_throw(Error)
end.
check_stub(Stub, Options) ->
try aeso_parser:string(binary_to_list(Stub), Options) of
Ast ->
try
%% io:format("AST: ~120p\n", [Ast]),
aeso_ast_infer_types:infer(Ast, [])
aeso_ast_infer_types:infer(Ast, [no_code])
catch throw:{type_errors, TE} ->
io:format("Type error:\n~s\n", [TE]),
error(TE);
+30 -33
View File
@@ -19,19 +19,9 @@ calldata_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined
end,
FateExprs = ast_exprs(ContractString, Fun, Args),
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(ParsedExprs, FateExprs),
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
@@ -39,28 +29,18 @@ calldata_aci_test_() ->
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
fun() ->
ContractString = aeso_test_utils:read_contract(ContractName),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString),
{ok, ContractACIBin} = aeso_aci:contract_interface(string, ContractString, [no_code]),
ContractACI = binary_to_list(ContractACIBin),
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined
end,
FateExprs = ast_exprs(ContractACI, Fun, Args),
ParsedExprs = parse_args(Fun, Args),
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(ParsedExprs, FateExprs),
ok
end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
[{contract_main, _, _, _, [{letfun, _, _, _, _, [{guarded, _, [], {app, _, _, AST}}]}]}] =
aeso_parser:string("main contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST).
strip_ann(T) when is_tuple(T) ->
@@ -75,6 +55,8 @@ strip_ann1(L) when is_list(L) ->
lists:map(fun strip_ann/1, L);
strip_ann1(X) -> X.
ast_exprs(ContractString, Fun, Args) ->
ast_exprs(ContractString, Fun, Args, []).
ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
@@ -112,8 +94,28 @@ compilable_contracts() ->
{"funargs", "traffic_light", ["Green"]},
{"funargs", "traffic_light", ["Pantone(12)"]},
{"funargs", "tuples", ["()"]},
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "due", ["FixedTTL(1020)"]},
{"funargs", "singleton_rec", ["{x = 1000}"]},
{"funargs", "aens_name", ["AENS.Name(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, RelativeTTL(100), {[\"pt1\"] = AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)})"]},
{"funargs", "aens_pointee", ["AENS.AccountPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.OraclePt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.ContractPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "aens_pointee", ["AENS.ChannelPt(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR)"]},
{"funargs", "chain_ga_meta_tx", ["Chain.GAMetaTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
{"funargs", "chain_paying_for_tx", ["Chain.PayingForTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42)"]},
{"funargs", "chain_base_tx", ["Chain.SpendTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 42,\"foo\")"]},
{"funargs", "chain_base_tx", ["Chain.ContractCreateTx(12234)"]},
{"funargs", "chain_base_tx", ["Chain.ContractCallTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, 12234)"]},
{"funargs", "chain_base_tx", ["Chain.OracleRegisterTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleQueryTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleResponseTx"]},
{"funargs", "chain_base_tx", ["Chain.OracleExtendTx"]},
{"funargs", "chain_base_tx", ["Chain.NamePreclaimTx"]},
{"funargs", "chain_base_tx", ["Chain.NameClaimTx(\"acoolname.chain\")"]},
{"funargs", "chain_base_tx", ["Chain.NameUpdateTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
{"funargs", "chain_base_tx", ["Chain.NameRevokeTx(#ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
{"funargs", "chain_base_tx", ["Chain.NameTransferTx(ak_2dATVcZ9KJU5a8hdsVtTv21pYiGWiPbmVcU1Pz72FFqpk9pSRR, #ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)"]},
{"funargs", "chain_base_tx", ["Chain.GAAttachTx"]},
{"variant_types", "init", []},
{"basic_auth", "init", []},
{"address_literals", "init", []},
@@ -139,8 +141,3 @@ compilable_contracts() ->
{"stub", "foo", ["-42"]},
{"payable", "foo", ["42"]}
].
not_yet_compilable(fate) ->
[];
not_yet_compilable(aevm) ->
[].
File diff suppressed because it is too large Load Diff
+6 -5
View File
@@ -12,12 +12,12 @@ simple_contracts_test_() ->
fun(_) -> ok end,
[{"Parse a contract with an identity function.",
fun() ->
Text = "contract Identity =\n"
Text = "main contract Identity =\n"
" function id(x) = x\n",
?assertMatch(
[{contract, _, {con, _, "Identity"},
[{contract_main, _, {con, _, "Identity"}, _,
[{letfun, _, {id, _, "id"}, [{id, _, "x"}], {id, _, "_"},
{id, _, "x"}}]}], parse_string(Text)),
[{guarded, _, [], {id, _, "x"}}]}]}], parse_string(Text)),
ok
end},
{"Operator precedence test.",
@@ -63,7 +63,8 @@ simple_contracts_test_() ->
%% Parse tests of example contracts
[ {lists:concat(["Parse the ", Contract, " contract."]),
fun() -> roundtrip_contract(Contract) end}
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ]
|| Contract <- [counter, voting, all_syntax, '05_greeter', aeproof,
multi_sig, simple_storage, fundme, dutch_auction, utf8] ]
}.
parse_contract(Name) ->
@@ -85,7 +86,7 @@ parse_expr(Text) ->
round_trip(Text) ->
Contract = parse_string(Text),
Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))),
Contract1 = parse_string(Text1),
Contract1 = parse_string(aeso_scan:utf8_encode(Text1)),
NoSrcLoc = remove_line_numbers(Contract),
NoSrcLoc1 = remove_line_numbers(Contract1),
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
-15
View File
@@ -1,15 +0,0 @@
## Requires ocaml >= 4.02, < 4.06
## and reason-3.0.0 (opam install reason).
default : voting_test
%.ml : %.re
refmt -p ml $< > $@
voting_test : rte.ml voting.ml voting_test.ml
ocamlopt -o $@ $^
clean :
rm -f *.cmi *.cmx *.ml *.o voting_test
+1 -1
View File
@@ -1,5 +1,5 @@
contract Identity =
function main (x:int) = x
function main_fun (x:int) = x
function __call() = 12
-31
View File
@@ -1,31 +0,0 @@
// A simple test of the abort built-in function.
contract AbortTest =
record state = { value : int }
public function init(v : int) =
{ value = v }
// Aborting
public function do_abort(v : int, s : string) : unit =
put_value(v)
revert_abort(s)
// Accessing the value
public function get_value() = state.value
public function put_value(v : int) = put(state{value = v})
public function get_values() : list(int) = [state.value]
public function put_values(v : int) = put(state{value = v})
// Some basic statistics
public function get_stats(acct : address) =
( Contract.balance, Chain.balance(acct) )
// Abort functions.
private function revert_abort(s : string) =
abort(s)
// This is still legal but will be stripped out.
// TODO: This function confuses the type inference, so it cannot be present.
//private function abort(s : string) = 42
-27
View File
@@ -1,27 +0,0 @@
contract Interface =
function do_abort : (int, string) => unit
function get_value : () => int
function put_value : (int) => unit
function get_values : () => list(int)
function put_values : (int) => unit
contract AbortTestInt =
record state = {r : Interface, value : int}
public function init(r : Interface, value : int) =
{r = r, value = value}
// Aborting
public function do_abort(v : int, s : string) =
put_value(v)
state.r.do_abort(v + 100, s)
// Accessing the value
public function put_value(v : int) = put(state{value = v})
public function get_value() = state.value
public function get_values() : list(int) =
state.value :: state.r.get_values()
public function put_values(v : int) =
put_value(v)
state.r.put_values(v + 1000)
+2 -2
View File
@@ -1,5 +1,5 @@
contract Remote =
entrypoint main : (int) => unit
contract interface Remote =
entrypoint main_fun : (int) => unit
contract AddrChain =
type o_type = oracle(string, map(string, int))
+1 -1
View File
@@ -1,5 +1,5 @@
contract Remote =
contract interface Remote =
entrypoint foo : () => unit
contract AddressLiterals =
+29 -3
View File
@@ -1,5 +1,7 @@
contract C = entrypoint init() = ()
// AENS tests
contract AENSTest =
main contract AENSTest =
// Name resolution
@@ -9,10 +11,19 @@ contract AENSTest =
stateful entrypoint resolve_string(name : string, key : string) : option(string) =
AENS.resolve(name, key)
stateful entrypoint resolve_contract(name : string, key : string) : option(C) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle(name : string, key : string) : option(oracle(int, int)) =
AENS.resolve(name, key)
stateful entrypoint resolve_oracle_query(name : string, key : string) : option(oracle_query(int, int)) =
AENS.resolve(name, key)
// Transactions
stateful entrypoint preclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
chash : hash) : unit = // Commitment hash
chash : hash) : unit = // Commitment hash
AENS.preclaim(addr, chash)
stateful entrypoint signedPreclaim(addr : address, // Claim on behalf of this account (can be Contract.address)
@@ -33,7 +44,22 @@ contract AENSTest =
sign : signature) : unit =
AENS.claim(addr, name, salt, name_fee, signature = sign)
// TODO: update() -- how to handle pointers?
stateful entrypoint update(owner : address,
name : string,
ttl : option(Chain.ttl),
client_ttl : option(int),
pointers : option(map(string, AENS.pointee))) : unit =
AENS.update(owner, name, ttl, client_ttl, pointers)
stateful entrypoint signedUpdate(owner : address,
name : string,
ttl : option(Chain.ttl),
client_ttl : option(int),
pointers : option(map(string, AENS.pointee)),
sign : signature) : unit =
AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign)
stateful entrypoint transfer(owner : address,
new_owner : address,
+17
View File
@@ -0,0 +1,17 @@
contract AENSUpdate =
stateful entrypoint update_name(owner : address, name : string) =
let p1 : AENS.pointee = AENS.AccountPt(Call.caller)
let p2 : AENS.pointee = AENS.OraclePt(Call.caller)
let p3 : AENS.pointee = AENS.ContractPt(Call.caller)
let p4 : AENS.pointee = AENS.ChannelPt(Call.caller)
AENS.update(owner, name, None, None,
Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2,
["contract_pubkey"] = p3, ["misc"] = p4 }))
entrypoint get_ttl(name : string) =
switch(AENS.lookup(name))
Some(AENS.Name(_, FixedTTL(ttl), _)) => ttl
entrypoint expiry(o : oracle(int, int)) : int =
Oracle.expiry(o)
+9 -1
View File
@@ -6,6 +6,7 @@
namespace Ns =
datatype d('a) = D | S(int) | M('a, list('a), int)
private function fff() = 123
let const = 1
stateful entrypoint
f (1, x) = (_) => x
@@ -33,9 +34,13 @@ contract AllSyntax =
type state = shakespeare(int)
let cc = "str"
entrypoint init() = {
johann = 1000,
wolfgang = -10,
/* TODO: This does not compile because of bug in the parser tester.
von = (2 + 2, 0, List.sum([x | k <- [1,2,3]
, let l = k + 1
, if(l < 10)
@@ -43,11 +48,13 @@ contract AllSyntax =
, Adam <- [Adam, Mickiewicz]
, let x = f(l)
])),
*/
von = (2 + 2, 0, List.sum([1,2,3,4])),
goethe = () }
function f() =
let kp = "nietzsche"
let p = "Пушкин"
// let p = "Пушкин" // TODO: this also doesn't do right round_trip...
let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210)
let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
@@ -76,3 +83,4 @@ contract AllSyntax =
let sh : shakespeare(shakespeare(int)) =
{wolfgang = state}
sh{wolfgang.wolfgang = sh.wolfgang} // comment
exit("hope you had fun reading this")
+5
View File
@@ -0,0 +1,5 @@
contract C =
entrypoint f() = 123
contract D =
entrypoint f() = 123
@@ -0,0 +1,4 @@
contract AssignPatternToPattern =
entrypoint f() =
let x::(t::z = y) = [1, 2, 3]
(x + t)::y
+16
View File
@@ -0,0 +1,16 @@
include "List.aes"
contract AssignPatterns =
entrypoint test() = foo([1, 0, 2], (2, Some(3)), Some([4, 5]))
entrypoint foo(xs : list(int), p : int * option(int), some : option(list(int))) =
let x::(t = y::_) = xs
let z::_ = t
let (a, (o = Some(b))) = p
let Some((f = g::_)) = some
g + List.get(1, f)
x + y + z + a + b
+1 -1
View File
@@ -1,5 +1,5 @@
contract Remote =
contract interface Remote =
entrypoint foo : () => unit
contract AddressLiterals =
@@ -5,5 +5,5 @@ contract BadAENSresolve =
function fail() : t(int) =
AENS.resolve("foo.aet", "whatever")
entrypoint main() = ()
entrypoint main_fun() = ()
@@ -0,0 +1,9 @@
contract BadAENSresolve =
using AENS
type t('a) = option(list('a))
function fail() : t(int) =
resolve("foo.aet", "whatever")
entrypoint main_fun() = ()
+6
View File
@@ -0,0 +1,6 @@
contract interface Remote =
entrypoint id : int => int
contract ProtectedCall =
entrypoint bad(r : Remote) =
r.id(protected = 0 == 1, 18)
+1 -1
View File
@@ -1,3 +1,3 @@
function square(x) = x ^ 2
contract Main =
entrypoint main() = square(10)
entrypoint main_fun() = square(10)
+74
View File
@@ -0,0 +1,74 @@
// namespace Chain =
// record tx = { paying_for : option(Chain.paying_for_tx)
// , ga_metas : list(Chain.ga_meta_tx)
// , actor : address
// , fee : int
// , ttl : int
// , tx : Chain.base_tx }
// datatype ga_meta_tx = GAMetaTx(address, int)
// datatype paying_for_tx = PayingForTx(address, int)
// datatype base_tx = SpendTx(address, int, string)
// | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx
// | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string)
// | NameRevokeTx(hash) | NameTransferTx(address, string)
// | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) |
// | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address)
// | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address)
// | ContractCreateTx(int) | ContractCallTx(address, int)
// | GAAttachTx
// Contract replicating "normal" Aeternity authentication
contract BasicAuthTx =
record state = { nonce : int, owner : address }
datatype foo = Bar | Baz()
entrypoint init() = { nonce = 1, owner = Call.caller }
stateful entrypoint authorize(n : int, s : signature) : bool =
require(n >= state.nonce, "Nonce too low")
require(n =< state.nonce, "Nonce too high")
put(state{ nonce = n + 1 })
switch(Auth.tx_hash)
None => abort("Not in Auth context")
Some(tx_hash) =>
let Some(tx0) = Auth.tx
let x : option(Chain.paying_for_tx) = tx0.paying_for
let x : list(Chain.ga_meta_tx) = tx0.ga_metas
let x : int = tx0.fee + tx0.ttl
let x : address = tx0.actor
let x : Chain.tx = { tx = Chain.NamePreclaimTx, paying_for = None, ga_metas = [],
fee = 123, ttl = 0, actor = Call.caller }
switch(tx0.tx)
Chain.SpendTx(receiver, amount, payload) => verify(tx_hash, n, s)
Chain.OracleRegisterTx => false
Chain.OracleQueryTx => false
Chain.OracleResponseTx => false
Chain.OracleExtendTx => false
Chain.NamePreclaimTx => false
Chain.NameClaimTx(name) => false
Chain.NameUpdateTx(name) => false
Chain.NameRevokeTx(name) => false
Chain.NameTransferTx(to, name) => false
Chain.ChannelCreateTx(other_party) => false
Chain.ChannelDepositTx(channel, amount) => false
Chain.ChannelWithdrawTx(channel, amount) => false
Chain.ChannelForceProgressTx(channel) => false
Chain.ChannelCloseMutualTx(channel) => false
Chain.ChannelCloseSoloTx(channel) => false
Chain.ChannelSlashTx(channel) => false
Chain.ChannelSettleTx(channel) => false
Chain.ChannelSnapshotSoloTx(channel) => false
Chain.ContractCreateTx(amount) => false
Chain.ContractCallTx(ct_address, amount) => false
Chain.GAAttachTx => false
function verify(tx_hash, n, s) =
Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s)
entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n))
entrypoint weird_string() : string =
"\x19Weird String\x42\nMore\n"
+1 -1
View File
@@ -1,4 +1,4 @@
include "String.aes"
contract BytesToX =
entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b)
@@ -0,0 +1,5 @@
contract F =
entrypoint g() = 1
main contract C =
entrypoint f() = F.g()
-8
View File
@@ -1,8 +0,0 @@
contract ChannelEnv =
public function coinbase() : address = Chain.coinbase
public function timestamp() : int = Chain.timestamp
public function block_height() : int = Chain.block_height
public function difficulty() : int = Chain.difficulty
@@ -1,7 +0,0 @@
contract ChannelOnChainContractNameResolution =
public function can_resolve(name: string, key: string) : bool =
switch(AENS.resolve(name, key) : option(string))
None => false
Some(_address) => true
@@ -1,48 +0,0 @@
contract ChannelOnChainContractOracle =
type query_t = string
type answer_t = string
type oracle_id = oracle(query_t, answer_t)
type query_id = oracle_query(query_t, answer_t)
record state = { oracle : oracle_id,
question : string,
bets : map(string, address)
}
public function init(oracle: oracle_id, question: string) : state =
{ oracle = oracle,
question = question,
bets = {}
}
public stateful function place_bet(answer: string) =
switch(Map.lookup(answer, state.bets))
None =>
put(state{ bets = state.bets{[answer] = Call.caller}})
"ok"
Some(_value) =>
"bet_already_taken"
public function query_fee() =
Oracle.query_fee(state.oracle)
public function get_question(q: query_id) =
Oracle.get_question(state.oracle, q)
public stateful function resolve(q: query_id) =
switch(Oracle.get_answer(state.oracle, q))
None =>
"no response"
Some(result) =>
if(state.question == Oracle.get_question(state.oracle, q))
switch(Map.lookup(result, state.bets))
None =>
"no winning bet"
Some(winner) =>
Chain.spend(winner, Contract.balance)
"ok"
else
"different question"
@@ -1,9 +0,0 @@
contract Remote =
function get : () => int
function can_resolve : (string, string) => bool
contract RemoteCall =
function remote_resolve(r : Remote, name: string, key: string) : bool =
r.can_resolve(name, key)

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