Compare commits

...

94 Commits

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

To allow includes, either call through aeso_compiler:file or set the option `allow_include` (and add `include_path`(s)).
2019-02-08 14:16:06 +01:00
Ulf Norell 0a5b80668f Don't mess up on multiple namespaces in icode compiler 2019-02-08 14:16:06 +01:00
Ulf Norell 6cdba58e35 Update error messages 2019-02-08 14:16:06 +01:00
Ulf Norell 8262d7780f Fix some issues pointed out by dialyzer 2019-02-08 14:16:06 +01:00
Ulf Norell e6f01481bf Bind state and event primitives only in contracts (and with the right types) 2019-02-08 14:16:06 +01:00
Ulf Norell d9188d58a7 Proper checking of types 2019-02-08 14:16:06 +01:00
Ulf Norell dfa286d43c Deadcode elimination (icode post pass) 2019-02-08 14:16:06 +01:00
Ulf Norell 478da2af33 Don't expose namespace functions as entrypoints 2019-02-08 14:16:06 +01:00
Ulf Norell 10be09fe30 Add checks on event constructor arguments to type checker 2019-02-08 14:16:06 +01:00
Ulf Norell e6c9d0fac1 Put event index information in constructor annotation instead of in argument types 2019-02-08 14:16:06 +01:00
Ulf Norell 367f87b612 Implement namespaces
This includes a massive refactoring of the type checker, getting
rid of most of the ets tables and keeping a proper environment.
2019-02-08 14:16:06 +01:00
Hans Svensson 026ff52528 Merge pull request #24 from aeternity/merge_roma
Merge ROMA branch to master
2019-02-08 14:13:10 +01:00
Hans Svensson 3ba89d9f55 Merge ROMA into MINERVA 2019-02-08 13:38:37 +01:00
Robert Virding 0b4c2f14fe Move module documentation to separate files (#23) 2019-02-08 10:31:35 +01:00
Robert Virding b65c4edd19 Merge pull request #22 from aeternity/fix-compiler-interface
Remove specific filename extension handling
2019-02-06 15:17:53 +01:00
Robert Virding 515f444e7c Remove specific filename extension handling
And take the chance to make file handling errors ahve the same format
as other errors.
2019-02-06 14:02:46 +01:00
Dincho Todorov d3fa04483e Initial CircleCI integration (#20) 2019-01-29 15:35:41 +02:00
Ulf Norell 2edafe0adc Merge pull request #19 from aeternity/dialyzer-warnings
Fix incorrect type specs
2019-01-29 09:19:13 +01:00
Ulf Norell 3a7c8f905a Fix incorrect type specs
h/t OTP-21 dialyzer
2019-01-28 14:54:34 +01:00
Hans Svensson 9026e1fe6b Merge pull request #18 from aeternity/minor_fixing_up
Cleanup whitespace, bad typespec, and remaining enacl reference
2019-01-28 11:24:29 +01:00
Hans Svensson f1f2b09294 Cleanup whitespace, bad typespec, and remaining enacl reference 2019-01-28 10:50:32 +01:00
Ulf Norell 53299b9b17 Merge pull request #17 from aeternity/PT-163478903-builtin-bits-type
PT-163478903 builtin bits type
2019-01-28 10:05:09 +01:00
Robert Virding 64fd91197b Merge pull request #9 from aeternity/interface-to-sophia
PT-163063316 Interface to sophia
2019-01-25 16:20:38 +01:00
Robert Virding a73abf8e8e Fix testing to use new error message format 2019-01-25 16:16:20 +01:00
Robert Virding db1c0fa05a Improve pretty printing the code AST 2019-01-25 16:16:20 +01:00
Robert Virding 70ad303e16 Document the aeso_compiler module
The module doc format is loosely based on the standard erlang doc
formats.
2019-01-25 16:16:20 +01:00
Robert Virding fe1a2758c3 Improve the interface to the compiler
It is now more consistent though we can still discuss how we want the
interface to look.
2019-01-25 16:16:20 +01:00
Ulf Norell 79de25b3a5 Fix minor bugs in compilation of bit fields 2019-01-25 16:09:31 +01:00
Ulf Norell 3e1290efaf Add Bits.all and rename Bits.zero to Bits.none 2019-01-25 16:09:31 +01:00
Ulf Norell 9c77622c7c Add set operations on bit fields (union, isect, diff) 2019-01-25 16:09:31 +01:00
Ulf Norell a367d5040a Add builtin bit field type 2019-01-25 16:09:31 +01:00
Ulf Norell d8bf0bda45 Remove integer bit operations 2019-01-25 16:09:31 +01:00
Hans Svensson 922107e438 Merge pull request #16 from aeternity/git_mess_up_fix
Fix git mess up
2019-01-25 10:53:06 +01:00
Hans Svensson f133483a90 Fix git mess up 2019-01-25 10:49:36 +01:00
Hans Svensson be42ee08ab Merge pull request #15 from aeternity/fix_string_concat
Refactor String.concat to not shift 256 bits
2019-01-25 10:36:09 +01:00
Hans Svensson 86285a8ae0 Refactor String.concat to not shift 256 bits
Adding SafeMath to VM_AEVM_SOPHIA_2 will break String.concat otherwise.
2019-01-25 09:57:23 +01:00
Hans Svensson c5c73097fc Merge pull request #13 from aeternity/PT-163388694-add_native_bit_shift
Use ?SHL and ?SHR for 'bsl' and 'bsr'
2019-01-23 13:15:14 +01:00
Hans Svensson 23ccce4c22 Use ?SHL and ?SHR for 'bsl' and 'bsr' 2019-01-22 21:53:30 +01:00
Ulf Norell 387fdf5c34 Merge pull request #12 from aeternity/PT-163362624-generic-hash
PT-163362624 generic hash functions
2019-01-22 15:04:47 +01:00
Ulf Norell f0dcda27bc Update aebytecode commit 2019-01-22 14:54:38 +01:00
Ulf Norell 5fd24fec86 Add more hash primops 2019-01-22 09:09:37 +01:00
Hans Svensson 4bedbfee61 Merge pull request #11 from aeternity/PT-163083710-implement_ecverify
Add Crypto.ecverify
2019-01-22 08:51:10 +01:00
Hans Svensson e4c46ee16f Bump aebytecode dependency 2019-01-21 20:14:20 +01:00
Hans Svensson d8fff8f20f Add Crypto.ecverify 2019-01-21 14:20:15 +01:00
Luca Favatella b074aa3323 Rename description of node in readme (#10) 2019-01-18 10:58:26 +00:00
48 changed files with 2775 additions and 1026 deletions
+3
View File
@@ -16,3 +16,6 @@ _build
.idea .idea
*.iml *.iml
rebar3.crashdump rebar3.crashdump
*.erl~
*.aes~
aesophia
+17 -2
View File
@@ -5,6 +5,21 @@ This is the __sophia__ compiler for the æternity system which compiles contract
For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md). For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md).
It is an OTP application written in Erlang and is by default included in It is an OTP application written in Erlang and is by default included in
[æternity Epoch](https://github.com/aeternity/epoch). However, it can [the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other system to compile contracts coded in sophia which also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system. can then be loaded into the æternity system.
## Versioning
`aesophia` has a version that is only loosely connected to the version of the
Aeternity node - in principle they will share the major version but not
minor/patch version. The `aesophia` compiler version MUST be bumped whenever
there is a change in how byte code is generated, but it MAY also be bumped upon
API changes etc.
## Interface Modules
The basic modules for interfacing the compiler:
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)
* [aeso_aci: the ACI interface](./docs/aeso_aci.md)
+1
View File
@@ -0,0 +1 @@
2.0.0
+16
View File
@@ -0,0 +1,16 @@
# About this release
This is the `aesophia` compiler version 2.0.0. The main changes compared to version 1.2.0 are:
* Add `Crypto.ecverify` to the compiler.
* Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
`String.sha256` to the compiler.
* Add the `bits` type for working with bit fields in Sophia.
* Use native bit shift operations in builtin functions, reducing gas cost.
* Add Namespaces to Sophia in order to simplify using library contracts, etc.
* Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string.
* Add a missig type check on the `init` function - detects programmer errors earlier.
* Improve type checking of `record` fields - generates more understandable error messages.
* Improved, more coherent, error messages.
* Add the ACI (Aeternity Contract Interface) generator.
+135
View File
@@ -0,0 +1,135 @@
# aeso_aci
### Module
### aeso_aci
The ACI interface encoder and decoder.
### Description
This module provides an interface to generate and convert between
Sophia contracts and a suitable JSON encoding of contract
interface. As yet the interface is very basic.
Encoding this contract:
```
contract Answers =
record state = { a : answers }
type answers() = map(string, int)
stateful function init() = { a = {} }
private function the_answer() = 42
function new_answer(q : string, a : int) : answers() = { [q] = a }
```
generates the following JSON structure representing the contract interface:
``` json
{
"contract": {
"name": "Answers",
"type_defs": [
{
"name": "state",
"vars": [],
"typedef": "{a : map(string,int)}"
},
{
"name": "answers",
"vars": [],
"typedef": "map(string,int)"
}
],
"functions": [
{
"name": "init",
"arguments": [],
"type": "{a : map(string,int)}",
"stateful": true
},
{
"name": "new_answer",
"arguments": [
{
"name": "q",
"type": "string"
},
{
"name": "a",
"type": "int"
}
],
"type": "map(string,int)",
"stateful": false
}
]
}
}
```
When that encoding is decoded the following include definition is generated:
```
contract Answers =
function new_answer : (string, int) => map(string,int)
```
### Types
``` erlang
contract_string() = string() | binary()
json_string() = binary()
```
### Exports
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Generate the JSON encoding of the interface to a contract. The type definitions and non-private functions are included in the JSON string.
#### decode(JSONstring) -> ConstractString.
Types
``` erlang
ConstractString = contract_string()
JSONstring = json_string()
```
Take a JSON encoding of a contract interface and generate and generate a contract definition which can be included in another contract.
### Example run
This is an example of using the ACI generator from an Erlang shell. The file called `aci_test.aes` contains the contract in the description from which we want to generate files `aci_test.json` which is the JSON encoding of the contract interface and `aci_test.include` which is the contract definition to be included inside another contract.
``` erlang
1> {ok,Contract} = file:read_file("aci_test.aes").
{ok,<<"contract Answers =\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful function"...>>}
2> {ok,Encoding} = aeso_aci:encode(Contract).
<<"{\"contract\":{\"name\":\"Answers\",\"type_defs\":[{\"name\":\"state\",\"vars\":[],\"typedef\":\"{a : map(string,int)}\"},{\"name\":\"ans"...>>
3> file:write_file("aci_test.aci", Encoding).
ok
4> Decoded = aeso_aci:decode(Encoding).
<<"contract Answers =\n function new_answer : (string, int) => map(string,int)\n">>
5> file:write_file("aci_test.include", Decoded).
ok
6> jsx:prettify(Encoding).
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"type_defs\": [\n {\n \"name\": \"state\",\n \"vars\": [],\n "...>>
```
The final call to `jsx:prettify(Encoding)` returns the encoding in a
more easily readable form. This is what is shown in the description
above.
### Notes
The ACI generator currently cannot properly handle types defined using `datatype`.
+86
View File
@@ -0,0 +1,86 @@
# aeso_compiler
### Module
### aeso_compiler
The Sophia compiler
### Description
This module provides the interface to the standard Sophia compiler. It
returns the compiled module in a map which can then be loaded.
### Types
``` erlang
contract_string() = string() | binary()
contract_map() = #{bytecode => binary(),
compiler_version => binary(),
contract_souce => string(),
type_info => type_info()}
type_info()
errorstring() = binary()
```
### Exports
#### file(File)
#### file(File, Options) -> CompRet
#### from_string(ContractString, Options) -> CompRet
Types
``` erlang
ContractString = contract_string()
Options = [Option]
CompRet = {ok,ContractMap} | {error,ErrorString}
ContractMap = contract_map()
ErrorString = errorstring()
```
Compile a contract defined in a file or in a string.
The **pp_** options all print to standard output the following:
`pp_sophia_code` - print the input Sophia code.
`pp_ast` - print the AST of the code
`pp_types` - print information about the types
`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
#### check_call(ContractString, Options) -> CheckRet
Types
```
ContractString = string() | binary()
CheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}
Types = [Type]
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
``` erlang
Version = binary()
```
Get the current version of the Sophia compiler.
+5 -1
View File
@@ -1,8 +1,12 @@
%% -*- mode: erlang; indent-tabs-mode: nil -*-
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"99bf097"}}} {ref,"720510a"}}}
, {getopt, "1.0.1"} , {getopt, "1.0.1"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
{tag, "2.8.0"}}}
]}. ]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}. {escript_incl_apps, [aesophia, aebytecode, getopt]}.
+6 -2
View File
@@ -1,9 +1,13 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"99bf097759dedbe7553f87a796bc7e1c7322e64b"}}, {ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
0}, 0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}. {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
0}]}.
[ [
{pkg_hash,[ {pkg_hash,[
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
+9 -39
View File
@@ -11,7 +11,7 @@
-define(HASH_SIZE, 32). -define(HASH_SIZE, 32).
-export([ old_create_calldata/3 -export([ old_create_calldata/3
, create_calldata/5 , create_calldata/4
, check_calldata/2 , check_calldata/2
, function_type_info/3 , function_type_info/3
, function_type_hash/3 , function_type_hash/3
@@ -39,22 +39,12 @@
%%%=================================================================== %%%===================================================================
%%% Handle calldata %%% Handle calldata
create_calldata(Contract, FunName, Args, ArgTypes, RetType) -> create_calldata(FunName, Args, ArgTypes0, RetType) ->
case get_type_info_and_hash(Contract, FunName) of ArgTypes = {tuple, ArgTypes0},
{ok, TypeInfo, TypeHashInt} -> <<TypeHashInt:?HASH_SIZE/unit:8>> =
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}), function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
case check_calldata(Data, TypeInfo) of Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
{ok, CallDataType, OutType} -> {ok, Data, {tuple, [word, ArgTypes]}, RetType}.
case check_given_type(FunName, ArgTypes, RetType, CallDataType, OutType) of
ok ->
{ok, Data, CallDataType, OutType};
{error, _} = Err ->
Err
end;
{error,_What} = Err -> Err
end;
{error, _} = Err -> Err
end.
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) -> get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
FunBin = list_to_binary(FunName), FunBin = list_to_binary(FunName),
@@ -64,26 +54,6 @@ get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
{error, _} = Err -> Err {error, _} = Err -> Err
end. end.
%% Check that the given type matches the type from the metadata.
check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
{tuple, [word, {tuple, ExpectArgs}]} = CalldataType,
ReturnOk = if FunName == "init" -> true;
GivenRet == any -> true;
true -> GivenRet == ExpectRet
end,
ArgsOk = ExpectArgs == GivenArgs,
case ReturnOk andalso ArgsOk of
true -> ok;
false when FunName == "init" ->
{error, {init_args_mismatch,
{given, GivenArgs},
{expected, ExpectArgs}}};
false ->
{error, {call_type_mismatch,
{given, GivenArgs, '=>', GivenRet},
{expected, ExpectArgs, '=>', ExpectRet}}}
end.
-spec check_calldata(binary(), type_info()) -> -spec check_calldata(binary(), type_info()) ->
{'ok', typerep(), typerep()} | {'error', atom()}. {'ok', typerep(), typerep()} | {'error', atom()}.
check_calldata(CallData, TypeInfo) -> check_calldata(CallData, TypeInfo) ->
@@ -121,8 +91,8 @@ get_function_hash_from_calldata(CallData) ->
-spec function_type_info(function_name(), [typerep()], typerep()) -> -spec function_type_info(function_name(), [typerep()], typerep()) ->
function_type_info(). function_type_info().
function_type_info(Name, Args, OutType) -> function_type_info(Name, ArgTypes, OutType) ->
ArgType = {tuple, [T || {_, T} <- Args]}, ArgType = {tuple, ArgTypes},
{ function_type_hash(Name, ArgType, OutType) { function_type_hash(Name, ArgType, OutType)
, Name , Name
, aeso_heap:to_binary(ArgType) , aeso_heap:to_binary(ArgType)
+276
View File
@@ -0,0 +1,276 @@
%%%-------------------------------------------------------------------
%%% @author Robert Virding
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc
%%% ACI interface
%%% @end
%%% Created : 12 Dec 2017
%%%-------------------------------------------------------------------
-module(aeso_aci).
-export([encode/1,encode/2,decode/1]).
%% Define records for the various typed syntactic forms. These make
%% the code easier but don't seem to exist elsewhere.
-record(contract, {ann,con,decls}).
-record(namespace, {ann,con,decls}).
-record(letfun, {ann,id,args,type,body}).
-record(type_def, {ann,id,vars,typedef}).
-record(app_t, {ann,id,fields}).
-record(tuple_t, {ann,args}).
-record(record_t, {fields}).
-record(field_t, {ann,id,type}).
-record(alias_t, {type}).
-record(variant_t, {cons}).
-record(constr_t, {ann,con,args}).
-record(fun_t, {ann,named,args,type}).
-record(arg, {ann,id,type}).
-record(id, {ann,name}).
-record(con, {ann,name}).
-record(qid, {ann,names}).
-record(qcon, {ann,names}).
-record(tvar, {ann,name}).
%% encode(ContractString) -> {ok,JSON} | {error,String}.
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
%% Build a JSON structure with lists and tuples, not maps, as this
%% allows us to order the fields in the contructed JSON string.
encode(ContractString) -> encode(ContractString, []).
encode(ContractString, Options) when is_binary(ContractString) ->
encode(binary_to_list(ContractString), Options);
encode(ContractString, Options) ->
try
Ast = parse(ContractString, Options),
%%io:format("~p\n", [Ast]),
%% aeso_ast:pp(Ast),
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
%% io:format("~p\n", [TypedAst]),
%% aeso_ast:pp_typed(TypedAst),
%% We find and look at the last contract.
Contract = lists:last(TypedAst),
Cname = contract_name(Contract),
Tdefs = [ encode_typedef(T) ||
T <- sort_decls(contract_types(Contract)) ],
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
not is_private_func(F) ],
Jmap = [{<<"contract">>, [{<<"name">>, list_to_binary(Cname)},
{<<"type_defs">>, Tdefs},
{<<"functions">>, Fdefs}]}],
%% io:format("~p\n", [Jmap]),
{ok,jsx:encode(Jmap)}
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
encode_func(Fdef) ->
Name = function_name(Fdef),
Args = function_args(Fdef),
Type = function_type(Fdef),
[{<<"name">>, list_to_binary(Name)},
{<<"arguments">>, encode_args(Args)},
{<<"type">>, list_to_binary(encode_type(Type))},
{<<"stateful">>, is_stateful_func(Fdef)}].
encode_args(Args) ->
[ encode_arg(A) || A <- Args ].
encode_arg(#arg{id=Id,type=T}) ->
[{<<"name">>,list_to_binary(encode_type(Id))},
{<<"type">>,list_to_binary(encode_type(T))}].
encode_types(Types) ->
[ encode_type(T) || T <- Types ].
encode_type(#tvar{name=N}) -> N;
encode_type(#id{name=N}) -> N;
encode_type(#con{name=N}) -> N;
encode_type(#qid{names=Ns}) ->
lists:join(".", Ns);
encode_type(#qcon{names=Ns}) ->
lists:join(".", Ns); %?
encode_type(#tuple_t{args=As}) ->
Eas = encode_types(As),
[$(,lists:join(",", Eas),$)];
encode_type(#record_t{fields=Fs}) ->
Efs = encode_types(Fs),
[${,lists:join(",", Efs),$}];
encode_type(#app_t{id=Id,fields=Fs}) ->
Name = encode_type(Id),
Efs = encode_types(Fs),
[Name,"(",lists:join(",", Efs),")"];
encode_type(#field_t{id=Id,type=T}) ->
[encode_type(Id)," : ",encode_type(T)];
encode_type(#variant_t{cons=Cs}) ->
Ecs = encode_types(Cs),
lists:join(" | ", Ecs);
encode_type(#constr_t{con=C,args=As}) ->
Ec = encode_type(C),
Eas = encode_types(As),
[Ec,$(,lists:join(", ", Eas),$)];
encode_type(#fun_t{args=As,type=T}) ->
Eas = encode_types(As),
Et = encode_type(T),
[$(,lists:join(", ", Eas),") => ",Et].
encode_typedef(Type) ->
Name = typedef_name(Type),
Vars = typedef_vars(Type),
Def = typedef_def(Type),
[{<<"name">>, list_to_binary(Name)},
{<<"vars">>, encode_tvars(Vars)},
{<<"typedef">>, list_to_binary(encode_alias(Def))}].
encode_tvars(Vars) ->
[ encode_tvar(V) || V <- Vars ].
encode_tvar(#tvar{name=N}) ->
[{<<"name">>, list_to_binary(N)}].
encode_alias(#alias_t{type=T}) ->
encode_type(T);
encode_alias(A) -> encode_type(A).
%% decode(JSON) -> ContractString.
%% Decode a JSON string and generate a suitable contract string which
%% can be included in a contract definition. We decode into a map
%% here as this is easier to work with and order is not important.
decode(Json) ->
Map = jsx:decode(Json, [return_maps]),
%% io:format("~p\n", [Map]),
#{<<"contract">> := C} = Map,
list_to_binary(decode_contract(C)).
decode_contract(#{<<"name">> := Name,
<<"type_defs">> := _Ts,
<<"functions">> := Fs}) ->
["contract"," ",io_lib:format("~s", [Name])," =\n",
[], %Don't include types yet.
%% decode_tdefs(Ts),
decode_funcs(Fs)].
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
decode_func(#{<<"name">> := <<"init">>}) -> [];
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"type">> := T}) ->
[" function"," ",io_lib:format("~s", [Name])," : ",
decode_args(As)," => ",decode_type(T),$\n].
decode_type(T) -> io_lib:format("~s", [T]).
decode_args(As) ->
Das = [ decode_arg(A) || A <- As ],
[$(,lists:join(", ", Das),$)].
decode_arg(#{<<"type">> := T}) -> decode_type(T).
%% To keep dialyzer happy and quiet.
%% decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
%%
%% decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
%% [" type"," ",io_lib:format("~s", [Name]),decode_tvars(Vs),
%% " = ",decode_type(T),$\n].
%%
%% decode_tvars([]) -> []; %No tvars, no parentheses
%% decode_tvars(Vs) ->
%% Dvs = [ decode_tvar(V) || V <- Vs ],
%% [$(,lists:join(", ", Dvs),$)].
%%
%% decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
%%
%% #contract{Ann, Con, [Declarations]}.
contract_name(#contract{con=#con{name=N}}) -> N.
contract_funcs(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, letfun) ].
contract_types(#contract{decls=Decls}) ->
[ D || D <- Decls, is_record(D, type_def) ].
%% To keep dialyzer happy and quiet.
%% namespace_name(#namespace{con=#con{name=N}}) -> N.
%%
%% namespace_funcs(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, letfun) ].
%%
%% namespace_types(#namespace{decls=Decls}) ->
%% [ D || D <- Decls, is_record(D, type_def) ].
sort_decls(Ds) ->
Sort = fun (D1, D2) ->
aeso_syntax:get_ann(line, D1, 0) =<
aeso_syntax:get_ann(line, D2, 0)
end,
lists:sort(Sort, Ds).
%% #letfun{Ann, Id, [Arg], Type, Typedef}.
function_name(#letfun{id=#id{name=N}}) -> N.
function_args(#letfun{args=Args}) -> Args.
function_type(#letfun{type=Type}) -> Type.
is_private_func(#letfun{ann=A}) -> aeso_syntax:get_ann(private, A, false).
is_stateful_func(#letfun{ann=A}) -> aeso_syntax:get_ann(stateful, A, false).
%% #type_def{Ann, Id, [Var], Typedef}.
typedef_name(#type_def{id=#id{name=N}}) -> N.
typedef_vars(#type_def{vars=Vars}) -> Vars.
typedef_def(#type_def{typedef=Def}) -> Def.
parse(Text, Options) ->
%% Try and return something sensible here!
case aeso_parser:string(Text, Options) of
%% Yay, it worked!
{ok, Contract} -> Contract;
%% Scan errors.
{error, {Pos, scan_error}} ->
parse_error(Pos, "scan error");
{error, {Pos, scan_error_no_state}} ->
parse_error(Pos, "scan error");
%% Parse errors.
{error, {Pos, parse_error, Error}} ->
parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end.
parse_error(Pos, ErrorString) ->
io:format("Error ~p ~p\n", [Pos,ErrorString]),
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors, [Error]}).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
+4 -4
View File
@@ -17,11 +17,11 @@ line({symbol, Line, _}) -> Line.
symbol_name({symbol, _, Name}) -> Name. symbol_name({symbol, _, Name}) -> Name.
pp(Ast) -> pp(Ast) ->
%% TODO: Actually do *Pretty* printing. String = prettypr:format(aeso_pretty:decls(Ast, [])),
io:format("~p~n", [Ast]). io:format("Ast:\n~s\n", [String]).
pp_typed(TypedAst) -> pp_typed(TypedAst) ->
%% io:format("Typed tree:\n~p\n",[TypedAst]),
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])), String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
%%io:format("Typed tree:\n~p\n",[TypedAst]), io:format("Type ast:\n~s\n",[String]).
io:format("Type info:\n~s\n",[String]).
File diff suppressed because it is too large Load Diff
+148 -29
View File
@@ -17,11 +17,19 @@
-spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode(). -spec convert_typed(aeso_syntax:ast(), list()) -> aeso_icode:icode().
convert_typed(TypedTree, Options) -> convert_typed(TypedTree, Options) ->
code(TypedTree, aeso_icode:new(Options)). Name = case lists:last(TypedTree) of
{contract, _, {con, _, Con}, _} -> Con;
_ -> gen_error(last_declaration_must_be_contract)
end,
Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))),
deadcode_elimination(Icode).
code([{contract, _Attribs, {con, _, Name}, Code}|Rest], Icode) -> code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)),
aeso_icode:set_name(Name, Icode)), code(Rest, NewIcode);
code([{namespace, _Ann, Name, Code}|Rest], Icode) ->
%% TODO: nested namespaces
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Name, Icode)),
code(Rest, NewIcode); code(Rest, NewIcode);
code([], Icode) -> code([], Icode) ->
add_default_init_function(add_builtins(Icode)). add_default_init_function(add_builtins(Icode)).
@@ -33,30 +41,38 @@ gen_error(Error) ->
%% Create default init function (only if state is unit). %% Create default init function (only if state is unit).
add_default_init_function(Icode = #{functions := Funs, state_type := State}) -> add_default_init_function(Icode = #{functions := Funs, state_type := State}) ->
case lists:keymember("init", 1, Funs) of {_, _, QInit} = aeso_icode:qualify({id, [], "init"}, Icode),
case lists:keymember(QInit, 1, Funs) of
true -> Icode; true -> Icode;
false when State /= {tuple, []} -> gen_error(missing_init_function); false when State /= {tuple, []} ->
gen_error(missing_init_function);
false -> false ->
Type = {tuple, [typerep, {tuple, []}]}, Type = {tuple, [typerep, {tuple, []}]},
Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] }, Value = #tuple{ cpts = [type_value({tuple, []}), {tuple, []}] },
DefaultInit = {"init", [], [], Value, Type}, DefaultInit = {QInit, [], [], Value, Type},
Icode#{ functions => [DefaultInit | Funs] } Icode#{ functions => [DefaultInit | Funs] }
end. end.
-spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) -> -spec contract_to_icode(aeso_syntax:ast(), aeso_icode:icode()) ->
aeso_icode:icode(). aeso_icode:icode().
contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest], contract_to_icode([{namespace, _, Name, Defs} | Rest], Icode) ->
NS = aeso_icode:get_namespace(Icode),
Icode1 = contract_to_icode(Defs, aeso_icode:enter_namespace(Name, Icode)),
contract_to_icode(Rest, aeso_icode:set_namespace(NS, Icode1));
contract_to_icode([{type_def, _Attrib, Id = {id, _, Name}, Args, Def} | Rest],
Icode = #{ types := Types, constructors := Constructors }) -> Icode = #{ types := Types, constructors := Constructors }) ->
TypeDef = make_type_def(Args, Def, Icode), TypeDef = make_type_def(Args, Def, Icode),
NewConstructors = NewConstructors =
case Def of case Def of
{variant_t, Cons} -> {variant_t, Cons} ->
Tags = lists:seq(0, length(Cons) - 1), Tags = lists:seq(0, length(Cons) - 1),
GetName = fun({constr_t, _, {con, _, C}, _}) -> C end, GetName = fun({constr_t, _, C, _}) -> C end,
maps:from_list([ {GetName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]); QName = fun(Con) -> {_, _, Xs} = aeso_icode:qualify(GetName(Con), Icode), Xs end,
maps:from_list([ {QName(Con), Tag} || {Tag, Con} <- lists:zip(Tags, Cons) ]);
_ -> #{} _ -> #{}
end, end,
Icode1 = Icode#{ types := Types#{ Name => TypeDef }, {_, _, TName} = aeso_icode:qualify(Id, Icode),
Icode1 = Icode#{ types := Types#{ TName => TypeDef },
constructors := maps:merge(Constructors, NewConstructors) }, constructors := maps:merge(Constructors, NewConstructors) },
Icode2 = case Name of Icode2 = case Name of
"state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) }; "state" when Args == [] -> Icode1#{ state_type => ast_typerep(Def, Icode) };
@@ -68,8 +84,7 @@ contract_to_icode([{type_def, _Attrib, {id, _, Name}, Args, Def} | Rest],
contract_to_icode(Rest, Icode2); contract_to_icode(Rest, Icode2);
contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) -> contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest], Icode) ->
FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++ FunAttrs = [ stateful || proplists:get_value(stateful, Attrib, false) ] ++
[ private || proplists:get_value(private, Attrib, false) orelse [ private || is_private(Attrib, Icode) ],
proplists:get_value(internal, Attrib, false) ],
%% TODO: Handle types %% TODO: Handle types
FunName = ast_id(Name), FunName = ast_id(Name),
%% TODO: push funname to env %% TODO: push funname to env
@@ -84,7 +99,8 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
{tuple, [typerep, ast_typerep(T, Icode)]}}; {tuple, [typerep, ast_typerep(T, Icode)]}};
_ -> {ast_body(Body, Icode), ast_typerep(T, Icode)} _ -> {ast_body(Body, Icode), ast_typerep(T, Icode)}
end, end,
NewIcode = ast_fun_to_icode(FunName, FunAttrs, FunArgs, FunBody, TypeRep, Icode), QName = aeso_icode:qualify(Name, Icode),
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, Icode),
contract_to_icode(Rest, NewIcode); contract_to_icode(Rest, NewIcode);
contract_to_icode([{letrec,_,Defs}|Rest], Icode) -> contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% OBS! This code ignores the letrec structure of the source, %% OBS! This code ignores the letrec structure of the source,
@@ -94,11 +110,14 @@ contract_to_icode([{letrec,_,Defs}|Rest], Icode) ->
%% just to parse a list of (mutually recursive) definitions. %% just to parse a list of (mutually recursive) definitions.
contract_to_icode(Defs++Rest, Icode); contract_to_icode(Defs++Rest, Icode);
contract_to_icode([], Icode) -> Icode; contract_to_icode([], Icode) -> Icode;
contract_to_icode(_Code, Icode) -> contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
%% TODO debug output for debug("Unhandled code ~p~n",[Code]), contract_to_icode(Code, Icode);
Icode. contract_to_icode([Decl | Code], Icode) ->
io:format("Unhandled declaration: ~p\n", [Decl]),
contract_to_icode(Code, Icode).
ast_id({id, _, Id}) -> Id. ast_id({id, _, Id}) -> Id;
ast_id({qid, _, Id}) -> Id.
ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) -> ast_args([{arg, _, Name, Type}|Rest], Acc, Icode) ->
ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode); ast_args(Rest, [{ast_id(Name), ast_type(Type, Icode)}| Acc], Icode);
@@ -121,7 +140,7 @@ ast_type(T, Icode) ->
ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) -> ast_body(?qid_app(["Chain","spend"], [To, Amount], _, _), Icode) ->
prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []}); prim_call(?PRIM_CALL_SPEND, ast_body(Amount, Icode), [ast_body(To, Icode)], [word], {tuple, []});
ast_body(?qid_app(["Chain","event"], [Event], _, _), Icode) -> ast_body(?qid_app([Con, "Chain", "event"], [Event], _, _), Icode = #{ contract_name := Con }) ->
aeso_builtins:check_event_type(Icode), aeso_builtins:check_event_type(Icode),
builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]); builtin_call({event, maps:get(event_type, Icode)}, [ast_body(Event, Icode)]);
@@ -152,10 +171,10 @@ ast_body({qid, _, ["Chain", "spend"]}, _Icode) ->
gen_error({underapplied_primitive, 'Chain.spend'}); gen_error({underapplied_primitive, 'Chain.spend'});
%% State %% State
ast_body({id, _, "state"}, _Icode) -> prim_state; ast_body({qid, _, [Con, "state"]}, #{ contract_name := Con }) -> prim_state;
ast_body(?id_app("put", [NewState], _, _), Icode) -> ast_body(?qid_app([Con, "put"], [NewState], _, _), Icode = #{ contract_name := Con }) ->
#prim_put{ state = ast_body(NewState, Icode) }; #prim_put{ state = ast_body(NewState, Icode) };
ast_body({id, _, "put"}, _Icode) -> ast_body({qid, _, [Con, "put"]}, #{ contract_name := Con }) ->
gen_error({underapplied_primitive, put}); %% TODO: eta gen_error({underapplied_primitive, put}); %% TODO: eta
%% Abort %% Abort
@@ -316,6 +335,23 @@ ast_body({map, _, Map, [Upd]}, Icode) ->
ast_body({map, Ann, Map, [Upd | Upds]}, Icode) -> ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode); ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto
ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings %% Strings
%% -- String length %% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
@@ -331,6 +367,33 @@ ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) }; #unop{ op = 'sha3', rand = ast_body(String, Icode) };
%% -- Bits
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
#integer{ value = 0 };
ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
#integer{ value = 1 bsl 256 - 1 };
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
%% -- Conversion %% -- Conversion
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) -> ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]); builtin_call(int_to_str, [ast_body(Int, Icode)]);
@@ -340,7 +403,8 @@ ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
%% Other terms %% Other terms
ast_body({id, _, Name}, _Icode) -> ast_body({id, _, Name}, _Icode) ->
%% TODO Look up id in env #var_ref{name = Name};
ast_body({qid, _, Name}, _Icode) ->
#var_ref{name = Name}; #var_ref{name = Name};
ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
Value = if Bool -> 1 ; true -> 0 end, Value = if Bool -> 1 ; true -> 0 end,
@@ -397,9 +461,15 @@ ast_body({proj, _, {typed, _, _, {con, _, Contract}}, {id, _, FunName}}, _Icode)
string:join([Contract, FunName], ".")}); string:join([Contract, FunName], ".")});
ast_body({con, _, Name}, Icode) -> ast_body({con, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag}]};
ast_body({qcon, _, Name}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag}]}; #tuple{cpts = [#integer{value = Tag}]};
ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) -> ast_body({app, _, {typed, _, {con, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag([Name], Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app, _, {typed, _, {qcon, _, Name}, _}, Args}, Icode) ->
Tag = aeso_icode:get_constructor_tag(Name, Icode), Tag = aeso_icode:get_constructor_tag(Name, Icode),
#tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]}; #tuple{cpts = [#integer{value = Tag} | [ ast_body(Arg, Icode) || Arg <- Args ]]};
ast_body({app,As,Fun,Args}, Icode) -> ast_body({app,As,Fun,Args}, Icode) ->
@@ -509,12 +579,6 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
ast_binop('++', _, A, B, Icode) -> ast_binop('++', _, A, B, Icode) ->
#funcall{ function = #var_ref{ name = {builtin, list_concat} }, #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] }; args = [ast_body(A, Icode), ast_body(B, Icode)] };
ast_binop('bsl', _, A, B, Icode) ->
#binop{op = '*', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop('bsr', _, A, B, Icode) ->
#binop{op = 'div', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -593,6 +657,16 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
type_hash= #integer{value = TypeHash} type_hash= #integer{value = TypeHash}
}. }.
generic_hash_primop(PrimOp, Term, Type, Icode) ->
ArgType = ast_type(Type, Icode),
TypeValue = type_value(ArgType),
prim_call(PrimOp, #integer{value = 0},
[TypeValue, ast_body(Term, Icode)],
[typerep, ArgType], word).
string_hash_primop(PrimOp, String, Icode) ->
prim_call(PrimOp, #integer{value = 0}, [ast_body(String, Icode)], [string], word).
make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) -> make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
TVars = [ X || {tvar, _, X} <- Args ], TVars = [ X || {tvar, _, X} <- Args ],
fun(Types) -> fun(Types) ->
@@ -697,6 +771,15 @@ has_maps({list, T}) -> has_maps(T);
has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts); has_maps({tuple, Ts}) -> lists:any(fun has_maps/1, Ts);
has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)). has_maps({variant, Cs}) -> lists:any(fun has_maps/1, lists:append(Cs)).
%% A function is private if marked 'private' or 'internal', or if it's not
%% defined in the main contract name space. (NOTE: changes when we introduce
%% inheritance).
is_private(Ann, #{ contract_name := MainContract } = Icode) ->
{_, _, CurrentNamespace} = aeso_icode:get_namespace(Icode),
proplists:get_value(private, Ann, false) orelse
proplists:get_value(internal, Ann, false) orelse
MainContract /= CurrentNamespace.
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
%% Builtins %% Builtins
%% ------------------------------------------------------------------- %% -------------------------------------------------------------------
@@ -708,3 +791,39 @@ builtin_call(Builtin, Args) ->
add_builtins(Icode = #{functions := Funs}) -> add_builtins(Icode = #{functions := Funs}) ->
Builtins = aeso_builtins:used_builtins(Funs), Builtins = aeso_builtins:used_builtins(Funs),
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}. Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}.
%% -------------------------------------------------------------------
%% Deadcode elimination
%% -------------------------------------------------------------------
deadcode_elimination(Icode = #{ functions := Funs }) ->
PublicNames = [ Name || {Name, Ann, _, _, _} <- Funs, not lists:member(private, Ann) ],
ArgsToPat = fun(Args) -> [ #var_ref{ name = X } || {X, _} <- Args ] end,
Defs = maps:from_list([ {Name, {binder, ArgsToPat(Args), Body}} || {Name, _, Args, Body, _} <- Funs ]),
UsedNames = chase_names(Defs, PublicNames, #{}),
UsedFuns = [ Def || Def = {Name, _, _, _, _} <- Funs, maps:is_key(Name, UsedNames) ],
Icode#{ functions := UsedFuns }.
chase_names(_Defs, [], Used) -> Used;
chase_names(Defs, [X | Xs], Used) ->
%% can happen when compiling __call contracts
case maps:is_key(X, Used) orelse not maps:is_key(X, Defs) of
true -> chase_names(Defs, Xs, Used); %% already chased
false ->
Def = maps:get(X, Defs),
Vars = maps:keys(free_vars(Def)),
chase_names(Defs, Vars ++ Xs, Used#{ X => true })
end.
free_vars(#var_ref{ name = X }) -> #{ X => true };
free_vars(#arg{ name = X }) -> #{ X => true };
free_vars({binder, Pat, Body}) ->
maps:without(maps:keys(free_vars(Pat)), free_vars(Body));
free_vars(#switch{ expr = E, cases = Cases }) ->
free_vars([E | [{binder, P, B} || {P, B} <- Cases]]);
free_vars(#lambda{ args = Xs, body = E }) ->
free_vars({binder, Xs, E});
free_vars(T) when is_tuple(T) -> free_vars(tuple_to_list(T));
free_vars([H | T]) -> maps:merge(free_vars(H), free_vars(T));
free_vars(_) -> #{}.
+67 -50
View File
@@ -38,7 +38,7 @@ 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_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_from_list) -> [map_put]; builtin_deps1(map_from_list) -> [map_put];
builtin_deps1(str_equal) -> [str_equal_p]; builtin_deps1(str_equal) -> [str_equal_p];
builtin_deps1(string_concat) -> [string_concat_inner1, string_concat_inner2]; builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
builtin_deps1(int_to_str) -> [{baseX_int, 10}]; builtin_deps1(int_to_str) -> [{baseX_int, 10}];
builtin_deps1(addr_to_str) -> [{baseX_int, 58}]; builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
@@ -81,8 +81,9 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(EXP(A, B), op('^', A, B)). -define(EXP(A, B), op('^', A, B)).
-define(AND(A, B), op('&&', A, B)). -define(AND(A, B), op('&&', A, B)).
-define(BSL(X, B), ?MUL(X, ?EXP(2, ?MUL(B, 8)))). %% Bit shift operations takes their arguments backwards!?
-define(BSR(X, B), ?DIV(X, ?EXP(2, ?MUL(B, 8)))). -define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}. op(Op, A, B) -> {binop, Op, operand(A), operand(B)}.
@@ -105,21 +106,22 @@ check_event_type(Icode) ->
end. end.
check_event_type(Evts, Icode) -> check_event_type(Evts, Icode) ->
[ check_event_type(Name, T, Icode) [ check_event_type(Name, Ix, T, Icode)
|| {constr_t, _, {con, _, Name}, Types} <- Evts, T <- Types ]. || {constr_t, Ann, {con, _, Name}, Types} <- Evts,
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
check_event_type(EvtName, Type, Icode) -> check_event_type(EvtName, Ix, Type, Icode) ->
VMType = VMType =
try try
aeso_ast_to_icode:ast_typerep(Type, Icode) aeso_ast_to_icode:ast_typerep(Type, Icode)
catch _:_ -> catch _:_ ->
error({EvtName, could_not_resolve_type, Type}) error({EvtName, could_not_resolve_type, Type})
end, end,
case aeso_syntax:get_ann(indexed, Type, false) of case {Ix, VMType} of
true when VMType == word -> ok; {indexed, word} -> ok;
false when VMType == string -> ok; {notindexed, string} -> ok;
true -> error({EvtName, indexed_field_should_be_word, is, VMType}); {indexed, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
false -> error({EvtName, payload_should_be_string, is, VMType}) {notindexed, _} -> error({EvtName, payload_should_be_string, is, VMType})
end. end.
bfun(B, {IArgs, IExpr, IRet}) -> bfun(B, {IArgs, IExpr, IRet}) ->
@@ -143,9 +145,11 @@ builtin_function(BF) ->
string_length -> bfun(BF, builtin_string_length()); string_length -> bfun(BF, builtin_string_length());
string_concat -> bfun(BF, builtin_string_concat()); string_concat -> bfun(BF, builtin_string_concat());
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1()); string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
string_concat_inner2 -> bfun(BF, builtin_string_concat_inner2()); 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_p -> bfun(BF, builtin_str_equal_p());
str_equal -> bfun(BF, builtin_str_equal()); str_equal -> bfun(BF, builtin_str_equal());
popcount -> bfun(BF, builtin_popcount());
int_to_str -> bfun(BF, builtin_int_to_str()); int_to_str -> bfun(BF, builtin_int_to_str());
addr_to_str -> bfun(BF, builtin_addr_to_str()); addr_to_str -> bfun(BF, builtin_addr_to_str());
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X)); {baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
@@ -166,16 +170,15 @@ builtin_event(EventT) ->
A = fun(X) -> aeb_opcodes:mnemonic(X) end, A = fun(X) -> aeb_opcodes:mnemonic(X) end,
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end, VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end, ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
IsIndexed = fun(T) -> aeso_syntax:get_ann(indexed, T, false) end,
Payload = %% Should put data ptr, length on stack. Payload = %% Should put data ptr, length on stack.
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]}; fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr ([V]) -> {seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]} %% ptr+32, length
end, end,
Clause = Clause =
fun(_Tag, {con, _, Con}, Types) -> fun(_Tag, {con, _, Con}, IxTypes) ->
Indexed = [ Var || {Var, Type} <- lists:zip(ArgPats(Types), Types), Types = [ T || {_Ix, T} <- IxTypes ],
IsIndexed(Type) ], Indexed = [ Var || {Var, {indexed, _Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
EvtIndex = {unop, 'sha3', str_to_icode(Con)}, EvtIndex = {unop, 'sha3', str_to_icode(Con)},
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)} {event, lists:reverse(Indexed) ++ [EvtIndex], Payload(ArgPats(Types) -- Indexed)}
end, end,
@@ -186,8 +189,8 @@ builtin_event(EventT) ->
{[{"e", event}], {[{"e", event}],
{switch, v(e), {switch, v(e),
[{Pat(Tag, Types), Clause(Tag, Con, Types)} [{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|| {Tag, {constr_t, _, Con, Types}} <- lists:zip(Tags, Cons) ]}, || {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
{tuple, []}}. {tuple, []}}.
%% Abort primitive. %% Abort primitive.
@@ -321,14 +324,17 @@ builtin_string_concat() ->
{[{"s1", string}, {"s2", string}], {[{"s1", string}, {"s2", string}],
?DEREF(n1, s1, ?DEREF(n1, s1,
?DEREF(n2, s2, ?DEREF(n2, s2,
{ifte, ?EQ(n2, 0), {ifte, ?EQ(n1, 0),
?V(s1), %% Second string is empty return first string ?V(s2), %% First string is empty return second string
?LET(ret, {inline_asm, [?A(?MSIZE)]}, {ifte, ?EQ(n2, 0),
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len ?V(s1), %% Second string is empty return first string
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]), ?LET(ret, {inline_asm, [?A(?MSIZE)]},
{inline_asm, [?A(?POP)]}, %% Discard fun ret val {seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
?V(ret) %% Put the actual return value ?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}. word}.
@@ -336,33 +342,33 @@ builtin_string_concat_inner1() ->
%% Copy all whole words from the first string, and set up for word fusion %% 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. %% Special case when the length of the first string is divisible by 32.
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}], {[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
?DEREF(w1, p1, ?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
{ifte, ?GT(n1, 32), ?LET(nx, ?MOD(n1, 32),
{seq, [?V(w1), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, {ifte, ?EQ(nx, 0),
?call(string_concat_inner1, [?SUB(n1, 32), ?NXT(p1), ?V(n2), ?V(p2)])]}, ?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
{ifte, ?EQ(n1, 0), {seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
?call(string_concat_inner2, [?I(32), ?I(0), ?V(n2), ?V(p2)]), ?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
?call(string_concat_inner2, [?SUB(32, n1), ?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}. word}.
builtin_string_concat_inner2() -> builtin_string_shift_copy() ->
%% Current "work in progess" word 'x', has 'o' bytes that are "free" - fill them from {[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
%% words of the second string. ?DEREF(w, p,
{[{"o", word}, {"x", word}, {"n2", word}, {"p2", pointer}], {seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
{ifte, ?LT(n2, 1), {ifte, ?GT(n, ?SUB(32, off)),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}, %% Use MSIZE as dummy return value ?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
?DEREF(w2, p2, {inline_asm, [?A(?MSIZE)]}}]
{ifte, ?GT(n2, o), }),
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_concat_inner2,
[?V(o), ?BSL(w2, o), ?SUB(n2, 32), ?NXT(p2)])
]},
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]} %% Use MSIZE as dummy return value
})
},
word}. word}.
builtin_str_equal_p() -> builtin_str_equal_p() ->
@@ -393,6 +399,17 @@ builtin_str_equal() ->
)), )),
word}. 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() -> builtin_int_to_str() ->
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}. {[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
+314 -98
View File
@@ -11,131 +11,327 @@
-export([ file/1 -export([ file/1
, file/2 , file/2
, from_string/2 , from_string/2
, check_call/2 , check_call/4
, create_calldata/3 , create_calldata/3
, version/0 , version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4
, to_sophia_value/5
]). ]).
-include_lib("aebytecode/include/aeb_opcodes.hrl"). -include_lib("aebytecode/include/aeb_opcodes.hrl").
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type option() :: pp_sophia_code | pp_ast | pp_icode | pp_assembler | -type option() :: pp_sophia_code
pp_bytecode. | pp_ast
| pp_types
| pp_typed_ast
| pp_icode
| pp_assembler
| pp_bytecode
| {include, {file_system, [string()]} |
{explicit_files, #{string() => binary()}}}
| {src_file, string()}.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
, options/0 , options/0
]). ]).
-define(COMPILER_VERSION_1, 1). -spec version() -> {ok, binary()} | {error, term()}.
-define(COMPILER_VERSION_2, 2).
-define(COMPILER_VERSION, ?COMPILER_VERSION_2).
-spec version() -> pos_integer().
version() -> version() ->
?COMPILER_VERSION. case lists:keyfind(aesophia, 1, application:loaded_applications()) of
false ->
case application:load(aesophia) of
ok ->
case application:get_key(aesophia, vsn) of
{ok, VsnString} ->
{ok, list_to_binary(VsnString)};
undefined ->
{error, failed_to_load_aesophia}
end;
Err = {error, _} ->
Err
end;
{_App, _Des, VsnString} ->
{ok, list_to_binary(VsnString)}
end.
-spec file(string()) -> map(). -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). Dir = filename:dirname(Filename),
{ok, Cwd} = file:get_cwd(),
file(Filename, [{include, {file_system, [Cwd, Dir]}}]).
-spec file(string(), options()) -> map(). -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(Filename, Options) -> file(File, Options) ->
C = read_contract(Filename), case read_contract(File) of
from_string(C, Options). {ok, Bin} -> from_string(Bin, [{src_file, File} | Options]);
{error, Error} ->
ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
end.
-spec from_string(string(), options()) -> map(). -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) -> from_string(ContractString, Options) ->
Ast = parse(ContractString, Options), try
ok = pp_sophia_code(Ast, Options), #{icode := Icode} = string_to_icode(ContractString, Options),
ok = pp_ast(Ast, Options), TypeInfo = extract_type_info(Icode),
TypedAst = aeso_ast_infer_types:infer(Ast, Options), Assembler = assemble(Icode, Options),
%% pp_types is handled inside aeso_ast_infer_types. pp_assembler(Assembler, Options),
ok = pp_typed_ast(TypedAst, Options), ByteCodeList = to_bytecode(Assembler, Options),
ICode = to_icode(TypedAst, Options), ByteCode = << << B:8 >> || B <- ByteCodeList >>,
TypeInfo = extract_type_info(ICode), pp_bytecode(ByteCode, Options),
ok = pp_icode(ICode, Options), {ok, Version} = version(),
Assembler = assemble(ICode, Options), {ok, #{byte_code => ByteCode,
ok = pp_assembler(Assembler, Options), compiler_version => Version,
ByteCodeList = to_bytecode(Assembler, Options), contract_source => ContractString,
ByteCode = << << B:8 >> || B <- ByteCodeList >>, type_info => TypeInfo
ok = pp_bytecode(ByteCode, Options), }}
#{byte_code => ByteCode, type_info => TypeInfo, catch
contract_source => ContractString, %% The compiler errors.
compiler_version => version()}. error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
-define(CALL_NAME, "__call"). -spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
string_to_icode(ContractString, Options0) ->
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, Options),
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
pp_typed_ast(TypedAst, Options),
Icode = ast_to_icode(TypedAst, Options),
pp_icode(Icode, Options),
#{ typed_ast => TypedAst,
type_env => TypeEnv,
icode => Icode }.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call").
-define(DECODE_NAME, "__decode").
%% Takes a string containing a contract with a declaration/prototype of a %% Takes a string containing a contract with a declaration/prototype of a
%% function (foo, say) and a function __call() = foo(args) calling this %% function (foo, say) and adds function __call() = foo(args) calling this
%% function. Returns the name of the called functions, typereps and Erlang %% function. Returns the name of the called functions, typereps and Erlang
%% terms for the arguments. %% terms for the arguments.
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()} %% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T)
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()}
when Type :: term(). when Type :: term().
check_call(ContractString, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
Ast = parse(ContractString, Options), PatchFun = fun(T) -> {tuple, [typerep, T]} end,
ok = pp_sophia_code(Ast, Options), case check_call(Source, FunName, Args, Options, PatchFun) of
ok = pp_ast(Ast, Options), Err = {error, _} when Args == [] ->
TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]), %% Try with default init-function
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), case check_call(insert_init_function(Source, Options), FunName, Args, Options, PatchFun) of
ok = pp_typed_ast(TypedAst, Options), {error, _} -> Err; %% The first error is most likely better...
Icode = to_icode(TypedAst, Options), Res -> Res
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], end;
RetVMType = case RetType of Res ->
{id, _, "_"} -> any; Res
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) end;
end, check_call(Source, FunName, Args, Options) ->
ok = pp_icode(Icode, Options), PatchFun = fun(T) -> T end,
#{ functions := Funs } = Icode, check_call(Source, FunName, Args, Options, PatchFun).
ArgIcode = get_arg_icode(Funs),
try [ icode_to_term(T, Arg) || {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ] of check_call(ContractString0, FunName, Args, Options, PatchFun) ->
ArgTerms -> try
{ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms} %% First check the contract without the __call function and no permissive literals
catch throw:Err -> #{} = string_to_icode(ContractString0, Options),
{error, Err} ContractString = insert_call_function(ContractString0, FunName, Args, Options),
#{typed_ast := TypedAst,
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of
{id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end,
#{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) ||
{T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
{ok, FunName, {ArgVMTypes, PatchFun(RetVMType)}, ArgTerms}
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
-spec create_calldata(map(), string(), string()) -> %% Add the __call function to a contract.
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()} -spec insert_call_function(string(), string(), [string()], options()) -> string().
| {error, argument_syntax_error}. insert_call_function(Code, FunName, Args, Options) ->
create_calldata(Contract, "", CallCode) when is_map(Contract) -> Ast = parse(Code, Options),
case check_call(CallCode, []) of Ind = last_contract_indent(Ast),
{ok, FunName, {ArgTypes, RetType}, Args} -> lists:flatten(
aeso_abi:create_calldata(Contract, FunName, Args, ArgTypes, RetType); [ Code,
"\n\n",
lists:duplicate(Ind, " "),
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
]).
-spec insert_init_function(string(), options()) -> string().
insert_init_function(Code, Options) ->
Ast = parse(Code, Options),
Ind = last_contract_indent(Ast),
lists:flatten(
[ Code,
"\n\n",
lists:duplicate(Ind, " "), "function init() = ()\n"
]).
last_contract_indent(Decls) ->
case lists:last(Decls) of
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
_ -> 0
end.
-spec to_sophia_value(string(), string(), ok | error | revert, aeso_sophia:data()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(ContractString, Fun, ResType, Data) ->
to_sophia_value(ContractString, Fun, ResType, Data, []).
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
{ok, aeso_syntax:expr()} | {error, term()}.
to_sophia_value(_, _, error, Err, _Options) ->
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
to_sophia_value(_, _, revert, Data, _Options) ->
case aeso_heap:from_binary(string, Data) of
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
{error, _} = Err -> Err {error, _} = Err -> Err
end; end;
create_calldata(Contract, Function, Argument) when is_map(Contract) -> to_sophia_value(ContractString, FunName, ok, Data, Options) ->
%% Slightly hacky shortcut to let you get away without writing the full try
%% call contract code. #{ typed_ast := TypedAst,
%% Function should be "foo : type", and type_env := TypeEnv,
%% Argument should be "Arg1, Arg2, .., ArgN" (no parens) icode := Icode } = string_to_icode(ContractString, Options),
case string:lexemes(Function, ": ") of {ok, Type0} = get_decode_type(FunName, TypedAst),
%% If function is a single word fallback to old calldata generation Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
[FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument); VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
[FunName | _] -> case aeso_heap:from_binary(VmType, Data) of
Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space {ok, VmValue} ->
CallContract = lists:flatten( try
[ "contract Call =\n" {ok, translate_vm_value(VmType, Type, VmValue)}
, " function ", Function, "\n" catch throw:cannot_translate_to_sophia ->
, " function __call() = ", FunName, "(", Args, ")" Type0Str = prettypr:format(aeso_pretty:type(Type0)),
]), {error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
create_calldata(Contract, "", CallContract) [Data, VmType, Type0Str]))],
fun (E) -> E end)}
end;
{error, _Err} ->
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
fun(E) -> E end)}
end
catch
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_function}} ->
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
%% TODO: somewhere else
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
{hash, [], <<Hi:256, Lo:256>>};
translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S};
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
none -> {con, [], "None"};
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
case Val of
{variant, 0, []} -> {con, [], "None"};
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
end;
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
when length(VmTypes) == length(Types),
length(VmTypes) == tuple_size(Val) ->
{tuple, [], [translate_vm_value(VmType, Type, X)
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
when length(VmTypes) == length(Fields),
length(VmTypes) == tuple_size(Val) ->
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
when is_map(Map) ->
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
translate_vm_value(VmValType, ValType, Val)}
|| {Key, Val} <- maps:to_list(Map) ]};
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
when length(VmCons) == length(Cons),
length(VmCons) > Tag ->
VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons),
translate_vm_value(VmTypes, ConType, Args);
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) ->
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
translate_vm_value(_VmType, _Type, _Data) ->
throw(cannot_translate_to_sophia).
-spec create_calldata(string(), string(), [string()]) ->
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
| {error, term()}.
create_calldata(Code, Fun, Args) ->
case check_call(Code, Fun, Args, []) of
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
aeso_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
{error, _} = Err -> Err
end.
get_arg_icode(Funs) -> get_arg_icode(Funs) ->
[Args] = [ Args || {?CALL_NAME, _, _, {funcall, _, Args}, _} <- Funs ], case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
Args. [Args] -> Args;
[] -> error({missing_call_function, Funs})
end.
get_call_type([{contract, _, _, Defs}]) -> get_call_type([{contract, _, _, Defs}]) ->
case [ {FunName, FunType} case [ {lists:last(QFunName), FunType}
|| {letfun, _, {id, _, ?CALL_NAME}, [], _Ret, || {letfun, _, {id, _, ?CALL_NAME}, [], _Ret,
{typed, _, {typed, _,
{app, _, {app, _,
{typed, _, {id, _, FunName}, FunType}, _}, _}} <- Defs ] of {typed, _, {qid, _, QFunName}, FunType}, _}, _}} <- Defs ] of
[Call] -> {ok, Call}; [Call] -> {ok, Call};
[] -> {error, missing_call_function} [] -> {error, missing_call_function}
end; end;
@@ -143,6 +339,18 @@ get_call_type([_ | Contracts]) ->
%% The __call should be in the final contract %% The __call should be in the final contract
get_call_type(Contracts). get_call_type(Contracts).
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
GetType = fun({letfun, _, {id, _, Name}, _, Ret, _}) when Name == FunName -> [Ret];
({fun_decl, _, {id, _, Name}, {fun_t, _, _, _, Ret}}) when Name == FunName -> [Ret];
(_) -> [] end,
case lists:flatmap(GetType, Defs) of
[Type] -> {ok, Type};
[] -> {error, missing_function}
end;
get_decode_type(FunName, [_ | Contracts]) ->
%% The __decode should be in the final contract
get_decode_type(FunName, Contracts).
%% Translate an icode value (error if not value) to an Erlang term that can be %% Translate an icode value (error if not value) to an Erlang term that can be
%% consumed by aeso_heap:to_binary(). %% consumed by aeso_heap:to_binary().
icode_to_term(word, {integer, N}) -> N; icode_to_term(word, {integer, N}) -> N;
@@ -177,10 +385,7 @@ icode_to_term(T, V) ->
icodes_to_terms(Ts, Vs) -> icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ]. [ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ].
parse(C,_Options) -> ast_to_icode(TypedAst, Options) ->
parse_string(C).
to_icode(TypedAst, Options) ->
aeso_ast_to_icode:convert_typed(TypedAst, Options). aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) -> assemble(Icode, Options) ->
@@ -194,7 +399,9 @@ to_bytecode([Op|Rest], Options) ->
to_bytecode([], _) -> []. to_bytecode([], _) -> [].
extract_type_info(#{functions := Functions} =_Icode) -> extract_type_info(#{functions := Functions} =_Icode) ->
TypeInfo = [aeso_abi:function_type_info(list_to_binary(Name), Args, TypeRep) ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)),
ArgTypesOnly(Args), TypeRep)
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions, || {Name, Attrs, Args,_Body, TypeRep} <- Functions,
not is_tuple(Name), not is_tuple(Name),
not lists:member(private, Attrs) not lists:member(private, Attrs)
@@ -229,9 +436,9 @@ sophia_type_to_typerep(String) ->
catch _:_ -> {error, bad_type} catch _:_ -> {error, bad_type}
end. end.
parse_string(Text) -> parse(Text, Options) ->
%% Try and return something sensible here! %% Try and return something sensible here!
case aeso_parser:string(Text) of case aeso_parser:string(Text, Options) of
%% Yay, it worked! %% Yay, it worked!
{ok, Contract} -> Contract; {ok, Contract} -> Contract;
%% Scan errors. %% Scan errors.
@@ -244,14 +451,23 @@ parse_string(Text) ->
parse_error(Pos, Error); parse_error(Pos, Error);
{error, {Pos, ambiguous_parse, As}} -> {error, {Pos, ambiguous_parse, As}} ->
ErrorString = io_lib:format("Ambiguous ~p", [As]), ErrorString = io_lib:format("Ambiguous ~p", [As]),
parse_error(Pos, ErrorString) parse_error(Pos, ErrorString);
%% Include error
{error, {Pos, include_error, File}} ->
parse_error(Pos, io_lib:format("could not find include file '~s'", [File]))
end. end.
parse_error({Line,Pos}, ErrorString) -> parse_error(Pos, ErrorString) ->
Error = io_lib:format("line ~p, column ~p: ~s", [Line,Pos,ErrorString]), Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
error({parse_errors,[Error]}). error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
{ok, Bin} = file:read_file(Name), file:read_file(Name).
binary_to_list(Bin).
pos_error({Line, Pos}) ->
io_lib:format("line ~p, column ~p", [Line, Pos]);
pos_error({no_file, Line, Pos}) ->
pos_error({Line, Pos});
pos_error({File, Line, Pos}) ->
io_lib:format("file ~s, line ~p, column ~p", [File, Line, Pos]).
+39 -7
View File
@@ -9,7 +9,18 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_icode). -module(aeso_icode).
-export([new/1, pp/1, set_name/2, set_functions/2, map_typerep/2, option_typerep/1, get_constructor_tag/2]). -export([new/1,
pp/1,
set_name/2,
set_namespace/2,
enter_namespace/2,
get_namespace/1,
qualify/2,
set_functions/2,
map_typerep/2,
option_typerep/1,
get_constructor_tag/2]).
-export_type([icode/0]). -export_type([icode/0]).
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
@@ -29,12 +40,13 @@
-type icode() :: #{ contract_name => string() -type icode() :: #{ contract_name => string()
, functions => [fun_dec()] , functions => [fun_dec()]
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
, env => [bindings()] , env => [bindings()]
, state_type => aeso_sophia:type() , state_type => aeso_sophia:type()
, event_type => aeso_sophia:type() , event_type => aeso_sophia:type()
, types => #{ type_name() => type_def() } , types => #{ type_name() => type_def() }
, type_vars => #{ string() => aeso_sophia:type() } , type_vars => #{ string() => aeso_sophia:type() }
, constructors => #{ string() => integer() } %% name to tag , constructors => #{ [string()] => integer() } %% name to tag
, options => [any()] , options => [any()]
}. }.
@@ -59,6 +71,7 @@ builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
#{ "bool" => Word #{ "bool" => Word
, "int" => Word , "int" => Word
, "bits" => Word
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
, "hash" => Word , "hash" => Word
@@ -72,10 +85,10 @@ builtin_types() ->
}. }.
builtin_constructors() -> builtin_constructors() ->
#{ "RelativeTTL" => 0 #{ ["RelativeTTL"] => 0
, "FixedTTL" => 1 , ["FixedTTL"] => 1
, "None" => 0 , ["None"] => 0
, "Some" => 1 }. , ["Some"] => 1 }.
map_typerep(K, V) -> map_typerep(K, V) ->
{map, K, V}. {map, K, V}.
@@ -90,11 +103,30 @@ new_env() ->
set_name(Name, Icode) -> set_name(Name, Icode) ->
maps:put(contract_name, Name, Icode). maps:put(contract_name, Name, Icode).
-spec set_namespace(aeso_syntax:con() | aeso_syntax:qcon(), icode()) -> icode().
set_namespace(NS, Icode) -> Icode#{ namespace => NS }.
-spec enter_namespace(aeso_syntax:con(), icode()) -> icode().
enter_namespace(NS, Icode = #{ namespace := NS1 }) ->
Icode#{ namespace => aeso_syntax:qualify(NS1, NS) };
enter_namespace(NS, Icode) ->
Icode#{ namespace => NS }.
-spec get_namespace(icode()) -> false | aeso_syntax:con() | aeso_syntax:qcon().
get_namespace(Icode) -> maps:get(namespace, Icode, false).
-spec qualify(aeso_syntax:id() | aeso_syntax:con(), icode()) -> aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon().
qualify(X, Icode) ->
case get_namespace(Icode) of
false -> X;
NS -> aeso_syntax:qualify(NS, X)
end.
-spec set_functions([fun_dec()], icode()) -> icode(). -spec set_functions([fun_dec()], icode()) -> icode().
set_functions(NewFuns, Icode) -> set_functions(NewFuns, Icode) ->
maps:put(functions, NewFuns, Icode). maps:put(functions, NewFuns, Icode).
-spec get_constructor_tag(string(), icode()) -> integer(). -spec get_constructor_tag([string()], icode()) -> integer().
get_constructor_tag(Name, #{constructors := Constructors}) -> get_constructor_tag(Name, #{constructors := Constructors}) ->
case maps:get(Name, Constructors, undefined) of case maps:get(Name, Constructors, undefined) of
undefined -> error({undefined_constructor, Name}); undefined -> error({undefined_constructor, Name});
+1 -1
View File
@@ -20,7 +20,7 @@
, args :: arg_list() , args :: arg_list()
, body :: expr()}). , body :: expr()}).
-record(var_ref, { name :: string() | {builtin, atom() | tuple()}}). -record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
-record(prim_call_contract, -record(prim_call_contract,
{ gas :: expr() { gas :: expr()
+4 -2
View File
@@ -17,7 +17,7 @@
i(Code) -> aeb_opcodes:mnemonic(Code). i(Code) -> aeb_opcodes:mnemonic(Code).
%% We don't track purity or statefulness in the type checker yet. %% We don't track purity or statefulness in the type checker yet.
is_stateful({FName, _, _, _, _}) -> FName /= "init". is_stateful({FName, _, _, _, _}) -> lists:last(FName) /= "init".
is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs). is_public({_Name, Attrs, _Args, _Body, _Type}) -> not lists:member(private, Attrs).
@@ -105,7 +105,7 @@ make_args(Args) ->
fun_hash({FName, _, Args, _, TypeRep}) -> fun_hash({FName, _, Args, _, TypeRep}) ->
ArgType = {tuple, [T || {_, T} <- Args]}, ArgType = {tuple, [T || {_, T} <- Args]},
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(FName), ArgType, TypeRep), <<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
{integer, Hash}. {integer, Hash}.
%% Expects two return addresses below N elements on the stack. Picks the top %% Expects two return addresses below N elements on the stack. Picks the top
@@ -562,6 +562,8 @@ assemble_infix('^') -> i(?EXP);
assemble_infix('bor') -> i(?OR); assemble_infix('bor') -> i(?OR);
assemble_infix('band') -> i(?AND); assemble_infix('band') -> i(?AND);
assemble_infix('bxor') -> i(?XOR); 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(?SLT); %% comparisons are SIGNED
assemble_infix('>') -> i(?SGT); assemble_infix('>') -> i(?SGT);
assemble_infix('==') -> i(?EQ); assemble_infix('==') -> i(?EQ);
+1 -1
View File
@@ -19,7 +19,7 @@
-export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]). -export_type([parser/1, parser_expr/1, pos/0, token/0, tokens/0]).
-type pos() :: {integer(), integer()}. -type pos() :: {string() | no_file, integer(), integer()} | {integer(), integer()}.
-type token() :: {atom(), pos(), term()} | {atom(), pos()}. -type token() :: {atom(), pos(), term()} | {atom(), pos()}.
-type tokens() :: [token()]. -type tokens() :: [token()].
-type error() :: {pos(), string() | no_error}. -type error() :: {pos(), string() | no_error}.
+89 -28
View File
@@ -5,28 +5,37 @@
-module(aeso_parser). -module(aeso_parser).
-export([string/1, -export([string/1,
string/2,
type/1]). type/1]).
-include("aeso_parse_lib.hrl"). -include("aeso_parse_lib.hrl").
-spec string(string()) -> -type parse_result() :: {ok, aeso_syntax:ast()}
{ok, aeso_syntax:ast()} | {error, {aeso_parse_lib:pos(), atom(), term()}}
| {error, {aeso_parse_lib:pos(), | {error, {aeso_parse_lib:pos(), atom()}}.
atom(),
term()}} -spec string(string()) -> parse_result().
| {error, {aeso_parse_lib:pos(),
atom()}}.
string(String) -> string(String) ->
parse_and_scan(file(), String). string(String, []).
-spec string(string(), aeso_compiler:options()) -> parse_result().
string(String, Opts) ->
case parse_and_scan(file(), String, Opts) of
{ok, AST} ->
expand_includes(AST, Opts);
Err = {error, _} ->
Err
end.
type(String) -> type(String) ->
parse_and_scan(type(), String). parse_and_scan(type(), String, []).
parse_and_scan(P, S) -> parse_and_scan(P, S, Opts) ->
case aeso_scan:scan(S) of set_current_file(proplists:get_value(src_file, Opts, no_file)),
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); case aeso_scan:scan(S) of
Error -> Error {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
end. Error -> Error
end.
%% -- Parsing rules ---------------------------------------------------------- %% -- Parsing rules ----------------------------------------------------------
@@ -36,7 +45,9 @@ decl() ->
?LAZY_P( ?LAZY_P(
choice( choice(
%% Contract declaration %% Contract declaration
[ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4}) [ ?RULE(keyword(contract), con(), tok('='), maybe_block(decl()), {contract, _1, _2, _4})
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, _2})
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -57,9 +68,14 @@ decl() ->
modifiers() -> modifiers() ->
many(choice([token(stateful), token(public), token(private), token(internal)])). many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Node) -> add_modifiers([], Node) -> Node;
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end, add_modifiers(Mods = [Tok | _], Node) ->
Node, Mods). %% Set the position to the position of the first modifier. This is
%% important for code transformation tools (like what we do in
%% create_calldata) to be able to get the indentation of the declaration.
set_pos(get_pos(Tok),
lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
Node, Mods)).
%% -- Type declarations ------------------------------------------------------ %% -- Type declarations ------------------------------------------------------
@@ -176,11 +192,11 @@ expr200() -> infixr(expr300(), binop('||')).
expr300() -> infixr(expr400(), binop('&&')). expr300() -> infixr(expr400(), binop('&&')).
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
expr500() -> infixr(expr600(), binop(['::', '++'])). expr500() -> infixr(expr600(), binop(['::', '++'])).
expr600() -> infixl(expr650(), binop(['+', '-', 'bor', 'bxor', 'bsr', 'bsl'])). expr600() -> infixl(expr650(), binop(['+', '-'])).
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
expr700() -> infixl(expr750(), binop(['*', '/', mod, 'band'])). expr700() -> infixl(expr750(), binop(['*', '/', mod])).
expr750() -> infixl(expr800(), binop(['^'])). expr750() -> infixl(expr800(), binop(['^'])).
expr800() -> ?RULE(many(choice(token('!'), token('bnot'))), expr900(), prefixes(_1, _2)). expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)).
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
exprAtom() -> exprAtom() ->
@@ -301,6 +317,7 @@ binop(Ops) ->
con() -> token(con). con() -> token(con).
id() -> token(id). id() -> token(id).
tvar() -> token(tvar). tvar() -> token(tvar).
str() -> token(string).
token(Tag) -> token(Tag) ->
?RULE(tok(Tag), ?RULE(tok(Tag),
@@ -336,10 +353,17 @@ bracket_list(P) -> brackets(comma_sep(P)).
-type ann_col() :: aeso_syntax:ann_col(). -type ann_col() :: aeso_syntax:ann_col().
-spec pos_ann(ann_line(), ann_col()) -> ann(). -spec pos_ann(ann_line(), ann_col()) -> ann().
pos_ann(Line, Col) -> [{line, Line}, {col, Col}]. pos_ann(Line, Col) -> [{file, current_file()}, {line, Line}, {col, Col}].
current_file() ->
get('$current_file').
set_current_file(File) ->
put('$current_file', File).
ann_pos(Ann) -> ann_pos(Ann) ->
{proplists:get_value(line, Ann), {proplists:get_value(file, Ann),
proplists:get_value(line, Ann),
proplists:get_value(col, Ann)}. proplists:get_value(col, Ann)}.
get_ann(Ann) when is_list(Ann) -> Ann; get_ann(Ann) when is_list(Ann) -> Ann;
@@ -357,10 +381,10 @@ set_ann(Key, Val, Node) ->
setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})). setelement(2, Node, lists:keystore(Key, 1, Ann, {Key, Val})).
get_pos(Node) -> get_pos(Node) ->
{get_ann(line, Node), get_ann(col, Node)}. {current_file(), get_ann(line, Node), get_ann(col, Node)}.
set_pos({L, C}, Node) -> set_pos({F, L, C}, Node) ->
set_ann(line, L, set_ann(col, C, Node)). set_ann(file, F, set_ann(line, L, set_ann(col, C, Node))).
infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}). infix(L, Op, R) -> set_ann(format, infix, {app, get_ann(L), Op, [L, R]}).
@@ -442,8 +466,10 @@ parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
parse_field_pattern({field, Ann, F, E}) -> parse_field_pattern({field, Ann, F, E}) ->
{field, Ann, F, parse_pattern(E)}. {field, Ann, F, parse_pattern(E)}.
return_error({L, C}, Err) -> return_error({no_file, L, C}, Err) ->
fail(io_lib:format("~p:~p:\n~s", [L, C, Err])). fail(io_lib:format("~p:~p:\n~s", [L, C, Err]));
return_error({F, L, C}, Err) ->
fail(io_lib:format("In ~s at ~p:~p:\n~s", [F, L, C, Err])).
-spec ret_doc_err(ann(), prettypr:document()) -> no_return(). -spec ret_doc_err(ann(), prettypr:document()) -> no_return().
ret_doc_err(Ann, Doc) -> ret_doc_err(Ann, Doc) ->
@@ -455,3 +481,38 @@ bad_expr_err(Reason, E) ->
prettypr:sep([prettypr:text(Reason ++ ":"), prettypr:sep([prettypr:text(Reason ++ ":"),
prettypr:nest(2, aeso_pretty:expr(E))])). prettypr:nest(2, aeso_pretty:expr(E))])).
%% -- Helper functions -------------------------------------------------------
expand_includes(AST, Opts) ->
expand_includes(AST, [], Opts).
expand_includes([], Acc, _Opts) ->
{ok, lists:reverse(Acc)};
expand_includes([{include, S = {string, _, File}} | AST], Acc, Opts) ->
case read_file(File, Opts) of
{ok, Bin} ->
Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}),
case string(binary_to_list(Bin), Opts1) of
{ok, AST1} ->
expand_includes(AST1 ++ AST, Acc, Opts);
Err = {error, _} ->
Err
end;
{error, _} ->
{error, {get_pos(S), include_error, File}}
end;
expand_includes([E | AST], Acc, Opts) ->
expand_includes(AST, [E | Acc], Opts).
read_file(File, Opts) ->
case proplists:get_value(include, Opts, {explicit_files, #{}}) of
{file_system, Paths} ->
CandidateNames = [ filename:join(Dir, File) || Dir <- Paths ],
lists:foldr(fun(F, {error, _}) -> file:read_file(F);
(_F, OK) -> OK end, {error, not_found}, CandidateNames);
{explicit_files, Files} ->
case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found};
Src -> {ok, Src}
end
end.
+10 -9
View File
@@ -147,6 +147,8 @@ decl(D, Options) ->
-spec decl(aeso_syntax:decl()) -> doc(). -spec decl(aeso_syntax:decl()) -> doc().
decl({contract, _, C, Ds}) -> decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars); decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
@@ -234,8 +236,10 @@ type({app_t, _, Type, Args}) ->
beside(type(Type), tuple_type(Args)); beside(type(Type), tuple_type(Args));
type({tuple_t, _, Args}) -> type({tuple_t, _, Args}) ->
tuple_type(Args); tuple_type(Args);
type({named_arg_t, _, Name, Type, Default}) -> type({named_arg_t, _, Name, Type, _Default}) ->
follow(hsep(typed(name(Name), Type), text("=")), expr(Default)); %% Drop the default value
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
typed(name(Name), Type);
type(R = {record_t, _}) -> typedef(R); type(R = {record_t, _}) -> typedef(R);
type(T = {id, _, _}) -> name(T); type(T = {id, _, _}) -> name(T);
@@ -303,6 +307,8 @@ expr_p(P, E = {app, _, F = {Op, _}, Args}) when is_atom(Op) ->
{prefix, [A]} -> prefix(P, Op, A); {prefix, [A]} -> prefix(P, Op, A);
_ -> app(P, F, Args) _ -> app(P, F, Args)
end; end;
expr_p(_, {app, _, C={Tag, _, _}, []}) when Tag == con; Tag == qcon ->
expr_p(0, C);
expr_p(P, {app, _, F, Args}) -> expr_p(P, {app, _, F, Args}) ->
app(P, F, Args); app(P, F, Args);
%% -- Constants %% -- Constants
@@ -314,6 +320,7 @@ expr_p(_, E = {int, _, N}) ->
text(S); text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B)); expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16)); expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {unit, _}) -> text("()"); expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) -> expr_p(_, {char, _, C}) ->
@@ -359,20 +366,14 @@ bin_prec('++') -> {500, 600, 500};
bin_prec('::') -> {500, 600, 500}; bin_prec('::') -> {500, 600, 500};
bin_prec('+') -> {600, 600, 650}; bin_prec('+') -> {600, 600, 650};
bin_prec('-') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650};
bin_prec('bor') -> {600, 600, 650};
bin_prec('bxor') -> {600, 600, 650};
bin_prec('bsl') -> {600, 600, 650};
bin_prec('bsr') -> {600, 600, 650};
bin_prec('*') -> {700, 700, 750}; bin_prec('*') -> {700, 700, 750};
bin_prec('/') -> {700, 700, 750}; bin_prec('/') -> {700, 700, 750};
bin_prec(mod) -> {700, 700, 750}; bin_prec(mod) -> {700, 700, 750};
bin_prec('band') -> {700, 700, 750};
bin_prec('^') -> {750, 750, 800}. bin_prec('^') -> {750, 750, 800}.
-spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}.
un_prec('-') -> {650, 650}; un_prec('-') -> {650, 650};
un_prec('!') -> {800, 800}; un_prec('!') -> {800, 800}.
un_prec('bnot') -> {800, 800}.
equals(Ann, A, B) -> equals(Ann, A, B) ->
{app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}.
+2 -3
View File
@@ -36,9 +36,8 @@ lexer() ->
, {"\\*/", pop(skip())} , {"\\*/", pop(skip())}
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
"band", "bor", "bxor", "bsl", "bsr", "bnot"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =
+11 -5
View File
@@ -8,14 +8,14 @@
-module(aeso_syntax). -module(aeso_syntax).
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2]). -export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]). -export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_op/0]). -export_type([bin_op/0, un_op/0]).
-export_type([decl/0, letbind/0, typedef/0]). -export_type([decl/0, letbind/0, typedef/0]).
-export_type([arg/0, field_t/0, constructor_t/0]). -export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, pat/0]). -export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
-export_type([ast/0]). -export_type([ast/0]).
-type ast() :: [decl()]. -type ast() :: [decl()].
@@ -35,6 +35,7 @@
-type tvar() :: {tvar, ann(), name()}. -type tvar() :: {tvar, ann(), name()}.
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
@@ -75,10 +76,10 @@
-type op() :: bin_op() | un_op(). -type op() :: bin_op() | un_op().
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | 'band' | 'bor' | 'bsl' | 'bsr' | 'bxor' -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '||' | '&&' | '..'. | '||' | '&&' | '..'.
-type un_op() :: '-' | '!' | 'bnot'. -type un_op() :: '-' | '!'.
-type expr() -type expr()
:: {lam, ann(), [arg()], expr()} :: {lam, ann(), [arg()], expr()}
@@ -140,3 +141,8 @@ get_ann(Key, Node) ->
get_ann(Key, Node, Default) -> get_ann(Key, Node, Default) ->
proplists:get_value(Key, get_ann(Node), Default). proplists:get_value(Key, get_ann(Node), Default).
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
+134 -81
View File
@@ -6,89 +6,142 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
-module(aeso_syntax_utils). -module(aeso_syntax_utils).
-export([used_ids/1, used_types/1]). -export([used_ids/1, used_types/1, used/1]).
%% Var set combinators -record(alg, {zero, plus, scoped}).
none() -> [].
one(X) -> [X].
union_map(F, Xs) -> lists:umerge(lists:map(F, Xs)).
minus(Xs, Ys) -> Xs -- Ys.
%% Compute names used by a definition or expression. -type alg(A) :: #alg{ zero :: A
used_ids(Es) when is_list(Es) -> , plus :: fun((A, A) -> A)
union_map(fun used_ids/1, Es); , scoped :: fun((A, A) -> A) }.
used_ids({bind, A, B}) ->
minus(used_ids(B), used_ids(A));
%% Declarations
used_ids({contract, _, _, Decls}) -> used_ids(Decls);
used_ids({type_decl, _, _, _}) -> none();
used_ids({type_def, _, _, _, _}) -> none();
used_ids({fun_decl, _, _, _}) -> none();
used_ids({letval, _, _, _, E}) -> used_ids(E);
used_ids({letfun, _, _, Args, _, E}) -> used_ids({bind, Args, E});
used_ids({letrec, _, Decls}) -> used_ids(Decls);
%% Args
used_ids({arg, _, X, _}) -> used_ids(X);
used_ids({named_arg, _, _, E}) -> used_ids(E);
%% Constants
used_ids({int, _, _}) -> none();
used_ids({bool, _, _}) -> none();
used_ids({hash, _, _}) -> none();
used_ids({unit, _}) -> none();
used_ids({string, _, _}) -> none();
used_ids({char, _, _}) -> none();
%% Expressions
used_ids({lam, _, Args, E}) -> used_ids({bind, Args, E});
used_ids({'if', _, A, B, C}) -> used_ids([A, B, C]);
used_ids({switch, _, E, Bs}) -> used_ids([E, Bs]);
used_ids({app, _, E, Es}) -> used_ids([E | Es]);
used_ids({proj, _, E, _}) -> used_ids(E);
used_ids({tuple, _, Es}) -> used_ids(Es);
used_ids({list, _, Es}) -> used_ids(Es);
used_ids({typed, _, E, _}) -> used_ids(E);
used_ids({record, _, Fs}) -> used_ids(Fs);
used_ids({record, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, E, Fs}) -> used_ids([E, Fs]);
used_ids({map, _, KVs}) -> used_ids([ [K, V] || {K, V} <- KVs ]);
used_ids({map_get, _, M, K}) -> used_ids([M, K]);
used_ids({map_get, _, M, K, V}) -> used_ids([M, K, V]);
used_ids({block, _, Ss}) -> used_ids_s(Ss);
used_ids({Op, _}) when is_atom(Op) -> none();
used_ids({id, _, X}) -> [X];
used_ids({qid, _, _}) -> none();
used_ids({con, _, _}) -> none();
used_ids({qcon, _, _}) -> none();
%% Switch branches
used_ids({'case', _, P, E}) -> used_ids({bind, P, E});
%% Fields
used_ids({field, _, LV, E}) -> used_ids([LV, E]);
used_ids({field, _, LV, X, E}) -> used_ids([LV, {bind, X, E}]);
used_ids({proj, _, _}) -> none();
used_ids({map_get, _, E}) -> used_ids(E).
%% Statements -type kind() :: decl | type | bind_type | expr | bind_expr.
used_ids_s([]) -> none();
used_ids_s([S | Ss]) ->
used_ids([S, {bind, bound_ids(S), {block, [], Ss}}]).
bound_ids({letval, _, X, _, _}) -> one(X); -spec fold(alg(A), fun((kind(), _) -> A), kind(), E | [E]) -> A
bound_ids({letfun, _, X, _, _, _}) -> one(X); when E :: aeso_syntax:decl()
bound_ids({letrec, _, Decls}) -> union_map(fun bound_ids/1, Decls); | aeso_syntax:typedef()
bound_ids(_) -> none(). | aeso_syntax:field_t()
| aeso_syntax:constructor_t()
| aeso_syntax:type()
| aeso_syntax:expr()
| aeso_syntax:pat()
| aeso_syntax:arg()
| aeso_syntax:alt()
| aeso_syntax:elim()
| aeso_syntax:arg_expr()
| aeso_syntax:field(aeso_syntax:expr())
| aeso_syntax:stmt().
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
Same = fun(A) -> fold(Alg, Fun, K, A) end,
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
Type = fun(T) -> fold(Alg, Fun, type, T) end,
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
Top = Fun(K, X),
Bound = fun LB ({letval, _, Y, _, _}) -> BindExpr(Y);
LB ({letfun, _, F, _, _, _}) -> BindExpr(F);
LB ({letrec, _, Ds}) -> Sum(lists:map(LB, Ds));
LB (_) -> Zero
end,
Rec = case X of
%% lists (bound things in head scope over tail)
[A | As] -> Scoped(Same(A), Same(As));
%% decl()
{contract, _, _, Ds} -> Decl(Ds);
{namespace, _, _, Ds} -> Decl(Ds);
{type_decl, _, I, _} -> BindType(I);
{type_def, _, I, _, D} -> Plus(BindType(I), Decl(D));
{fun_decl, _, _, T} -> Type(T);
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
%% typedef()
{alias_t, T} -> Type(T);
{record_t, Fs} -> Type(Fs);
{variant_t, Cs} -> Type(Cs);
%% field_t() and constructor_t()
{field_t, _, _, T} -> Type(T);
{constr_t, _, _, Ts} -> Type(Ts);
%% type()
{fun_t, _, Named, Args, Ret} -> Type([Named, Args, Ret]);
{app_t, _, T, Ts} -> Type([T | Ts]);
{tuple_t, _, Ts} -> Type(Ts);
%% named_arg_t()
{named_arg_t, _, _, T, E} -> Plus(Type(T), Expr(E));
%% expr()
{lam, _, Args, E} -> Scoped(BindExpr(Args), Expr(E));
{'if', _, A, B, C} -> Expr([A, B, C]);
{switch, _, E, Alts} -> Expr([E, Alts]);
{app, _, A, As} -> Expr([A | As]);
{proj, _, E, _} -> Expr(E);
{tuple, _, As} -> Expr(As);
{list, _, As} -> Expr(As);
{typed, _, E, T} -> Plus(Expr(E), Type(T));
{record, _, Fs} -> Expr(Fs);
{record, _, E, Fs} -> Expr([E | Fs]);
{map, _, E, Fs} -> Expr([E | Fs]);
{map, _, KVs} -> Sum([Expr([Key, Val]) || {Key, Val} <- KVs]);
{map_get, _, A, B} -> Expr([A, B]);
{map_get, _, A, B, C} -> Expr([A, B, C]);
{block, _, Ss} -> Expr(Ss);
%% field()
{field, _, LV, E} -> Expr([LV, E]);
{field, _, LV, _, E} -> Expr([LV, E]);
%% arg()
{arg, _, X, T} -> Plus(Expr(X), Type(T));
%% alt()
{'case', _, P, E} -> Scoped(BindExpr(P), Expr(E));
%% elim()
{proj, _, _} -> Zero;
{map_get, _, E} -> Expr(E);
%% arg_expr()
{named_arg, _, _, E} -> Expr(E);
_ -> Alg#alg.zero
end,
(Alg#alg.plus)(Top, Rec).
%% Name dependencies
used_ids(E) ->
[ X || {term, [X]} <- used(E) ].
used_types(T) ->
[ X || {type, [X]} <- used(T) ].
-type entity() :: {term, [string()]}
| {type, [string()]}
| {namespace, [string()]}.
-spec entity_alg() -> alg([entity()]).
entity_alg() ->
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
Scoped = fun(Xs, Ys) ->
{Bound, Others} = lists:partition(IsBound, Ys),
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
lists:umerge(Xs -- Bound1, Others)
end,
#alg{ zero = []
, plus = fun lists:umerge/2
, scoped = Scoped }.
-spec used(_) -> [entity()].
used(D) ->
Kind = fun(expr) -> term;
(bind_expr) -> bound_term;
(type) -> type;
(bind_type) -> bound_type
end,
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
Xs =
fold(entity_alg(),
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(K, {con, _, X}) -> [{Kind(K), [X]}];
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
(_, _) -> []
end, decl, D),
lists:filter(NotBound, Xs).
used_types(Ts) when is_list(Ts) -> union_map(fun used_types/1, Ts);
used_types({type_def, _, _, _, T}) -> used_types(T);
used_types({alias_t, T}) -> used_types(T);
used_types({record_t, Fs}) -> used_types(Fs);
used_types({variant_t, Cs}) -> used_types(Cs);
used_types({field_t, _, _, T}) -> used_types(T);
used_types({constr_t, _, _, Ts}) -> used_types(Ts);
used_types({fun_t, _, Named, Args, T}) -> used_types([T | Named ++ Args]);
used_types({named_arg_t, _, _, T, _}) -> used_types(T);
used_types({app_t, _, T, Ts}) -> used_types([T | Ts]);
used_types({tuple_t, _, Ts}) -> used_types(Ts);
used_types({id, _, X}) -> one(X);
used_types({qid, _, _}) -> none();
used_types({con, _, _}) -> none();
used_types({qcon, _, _}) -> none();
used_types({tvar, _, _}) -> none().
+2 -1
View File
@@ -1,10 +1,11 @@
{application, aesophia, {application, aesophia,
[{description, "Contract Language for Aethernity"}, [{description, "Contract Language for Aethernity"},
{vsn, "1.2.0"}, {vsn, {cmd, "cat VERSION | tr -d '[:space:]'"}},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
stdlib, stdlib,
jsx,
syntax_tools, syntax_tools,
getopt, getopt,
aebytecode aebytecode
+12 -5
View File
@@ -4,6 +4,7 @@
-define(OPT_SPEC, -define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"} [ {src_file, undefined, undefined, string, "Sophia source code file"}
, {version, $V, "version", undefined, "Print compiler version"}
, {verbose, $v, "verbose", undefined, "Verbose output"} , {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"} , {help, $h, "help", undefined, "Show this message"}
, {outfile, $o, "out", string, "Output file (experimental)"} ]). , {outfile, $o, "out", string, "Output file (experimental)"} ]).
@@ -14,11 +15,13 @@ usage() ->
main(Args) -> main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} -> {ok, {Opts, []}} ->
case proplists:get_value(help, Opts, false) of case Opts of
false -> [version] ->
compile(Opts); print_vsn();
true -> [help] ->
usage() usage();
_ ->
compile(Opts)
end; end;
{ok, {_, NonOpts}} -> {ok, {_, NonOpts}} ->
@@ -69,3 +72,7 @@ write_outfile(Out, ResMap) ->
%% Lazy approach %% Lazy approach
file:write_file(Out, term_to_binary(ResMap)), file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]). io:format("Output written to: ~s\n", [Out]).
print_vsn() ->
{ok, Vsn} = aeso_compiler:version(),
io:format("Compiler version: ~s\n", [Vsn]).
+105 -17
View File
@@ -54,27 +54,115 @@ encode_decode_test() ->
ok. ok.
encode_decode_sophia_test() -> encode_decode_sophia_test() ->
{42} = encode_decode_sophia_string("int", "42"), Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of
{1} = encode_decode_sophia_string("bool", "true"), {X, X} -> ok;
{0} = encode_decode_sophia_string("bool", "false"), Other -> Other
{<<"Hello">>} = encode_decode_sophia_string("string", "\"Hello\""), end end,
{<<"Hello">>, [1,2,3], {variant, 1, [1]}} = ok = Check("int", "42"),
encode_decode_sophia_string( ok = Check("bool", "true"),
"(string, list(int), option(bool))", ok = Check("bool", "false"),
"\"Hello\", [1,2,3], Some(true)"), ok = Check("string", "\"Hello\""),
ok = Check("(string, list(int), option(bool))",
"(\"Hello\", [1, 2, 3], Some(true))"),
ok = Check("variant", "Blue({[\"x\"] = 1})"),
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
ok. ok.
encode_decode_sophia_string(SophiaType, String) -> encode_decode_sophia_string(SophiaType, String) ->
io:format("String ~p~n", [String]), io:format("String ~p~n", [String]),
Code = [ "contract Call =\n" Code = [ "contract MakeCall =\n"
, " function foo : ", SophiaType, " => _\n" , " type arg_type = ", SophiaType, "\n"
, " function __call() = foo(", String, ")\n" ], , " type an_alias('a) = (string, 'a)\n"
{ok, _, {Types, _}, Args} = aeso_compiler:check_call(lists:flatten(Code), []), , " record r = {x : an_alias(int), y : variant}\n"
Arg = list_to_tuple(Args), , " datatype variant = Red | Blue(map(string, int))\n"
Type = {tuple, Types}, , " function foo : arg_type => arg_type\n" ],
io:format("Type ~p~n", [Type]), case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
Data = encode(Arg), {ok, _, {[Type], _}, [Arg]} ->
decode(Type, Data). io:format("Type ~p~n", [Type]),
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
{ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end;
{error, Err} ->
io:format("~s\n", [Err]),
{error, Err}
end.
calldata_test() ->
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
ok.
calldata_init_test() ->
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
calldata_indent_test() ->
Test = fun(Extra) ->
encode_decode_calldata_(
parameterized_contract(Extra, "foo", ["int"]),
"foo", ["42"], word)
end,
Test(" stateful function bla() = ()"),
Test(" type x = int"),
Test(" private function bla : int => int"),
Test(" public stateful function bla(x : int) =\n"
" x + 1"),
Test(" stateful private function bla(x : int) : int =\n"
" x + 1"),
ok.
parameterized_contract(FunName, Types) ->
parameterized_contract([], FunName, Types).
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten(
["contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n"
" function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
oracle_test() ->
Contract =
"contract OracleTest =\n"
" function question(o, q : oracle_query(list(string), option(int))) =\n"
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
ok.
permissive_literals_fail_test() ->
Contract =
"contract OracleTest =\n"
" function haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
ok.
encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word).
encode_decode_calldata(FunName, Types, Args, RetType) ->
Code = parameterized_contract(FunName, Types),
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
?assertEqual(RetVMType1, RetVMType),
{ok, {_Hash, ArgTuple}} = aeso_heap:from_binary(CalldataType, Calldata),
tuple_to_list(ArgTuple).
encode_decode(T, D) -> encode_decode(T, D) ->
?assertEqual(D, decode(T, encode(D))), ?assertEqual(D, decode(T, encode(D))),
+45
View File
@@ -0,0 +1,45 @@
-module(aeso_aci_tests).
-include_lib("eunit/include/eunit.hrl").
do_test() ->
test_contract(1),
test_contract(2).
test_contract(N) ->
{Contract,DecACI} = test_cases(N),
{ok,Enc} = aeso_aci:encode(Contract),
?assertEqual(DecACI, jsx:decode(Enc)).
test_cases(1) ->
Contract = <<"contract C =\n"
" function a(i : int) = i+1\n">>,
DecodedACI = [{<<"contract">>,
[{<<"name">>,<<"C">>},
{<<"type_defs">>,[]},
{<<"functions">>,
[[{<<"name">>,<<"a">>},
{<<"arguments">>,
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
{<<"type">>,<<"int">>},
{<<"stateful">>,false}]]}]}],
{Contract,DecodedACI};
test_cases(2) ->
Contract = <<"contract C =\n"
" type allan = int\n"
" function a(i : allan) = i+1\n">>,
DecodedACI = [{<<"contract">>,
[{<<"name">>,<<"C">>},
{<<"type_defs">>,
[[{<<"name">>,<<"allan">>},
{<<"vars">>,[]},
{<<"typedef">>,<<"int">>}]]},
{<<"functions">>,
[[{<<"name">>,<<"a">>},
{<<"arguments">>,
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
{<<"type">>,<<"int">>},
{<<"stateful">>,false}]]}]}],
{Contract,DecodedACI}.
+153 -89
View File
@@ -10,15 +10,10 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
%% simple_compile_test_() -> ok.
%% Very simply test compile the given contracts. Only basic checks %% Very simply test compile the given contracts. Only basic checks
%% are made on the output, just that it is a binary which indicates %% are made on the output, just that it is a binary which indicates
%% that the compilation worked. %% that the compilation worked.
simple_compile_test_() -> simple_compile_test_() ->
{setup,
fun () -> ok end, %Setup
fun (_) -> ok end, %Cleanup
[ {"Testing the " ++ ContractName ++ " contract", [ {"Testing the " ++ ContractName ++ " contract",
fun() -> fun() ->
#{byte_code := ByteCode, #{byte_code := ByteCode,
@@ -28,13 +23,38 @@ simple_compile_test_() ->
end} || ContractName <- compilable_contracts() ] ++ end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
{type_errors, Errors} = compile(ContractName), case compile(ContractName) of
check_errors(lists:sort(ExpectedErrors), lists:sort(Errors)) <<"Type errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString);
<<"Parse errors\n", ErrorString/binary>> ->
check_errors(lists:sort(ExpectedErrors), ErrorString)
end
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] {ContractName, ExpectedErrors} <- failing_contracts() ] ++
}. [ {"Testing include with explicit files",
fun() ->
FileSystem = maps:from_list(
[ begin
{ok, Bin} = file:read_file(filename:join([aeso_test_utils:contract_path(), File])),
{File, Bin}
end || File <- ["included.aes", "../contracts/included2.aes"] ]),
#{byte_code := Code1} = compile("include", [{include, {explicit_files, FileSystem}}]),
#{byte_code := Code2} = compile("include"),
?assertMatch(true, Code1 == Code2)
end} ] ++
[ {"Testing deadcode elimination",
fun() ->
#{ byte_code := NoDeadCode } = compile("nodeadcode"),
#{ byte_code := DeadCode } = compile("deadcode"),
SizeNoDeadCode = byte_size(NoDeadCode),
SizeDeadCode = byte_size(DeadCode),
?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}),
ok
end} ].
check_errors(Expect, Actual) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@@ -42,10 +62,13 @@ check_errors(Expect, Actual) ->
end. end.
compile(Name) -> compile(Name) ->
try compile(Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
aeso_compiler:from_string(aeso_test_utils:read_contract(Name), [])
catch _:{type_errors, _} = E -> compile(Name, Options) ->
E String = aeso_test_utils:read_contract(Name),
case aeso_compiler:from_string(String, [{src_file, Name} | Options]) of
{ok, Map} -> Map;
{error, ErrorString} -> ErrorString
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@@ -68,89 +91,130 @@ compilable_contracts() ->
"stack", "stack",
"test", "test",
"builtin_bug", "builtin_bug",
"builtin_map_get_bug" "builtin_map_get_bug",
"nodeadcode",
"deadcode",
"variant_types",
"state_handling",
"events",
"include"
]. ].
%% Contracts that should produce type errors %% Contracts that should produce type errors
failing_contracts() -> failing_contracts() ->
[ {"name_clash", [ {"name_clash",
["Duplicate definitions of abort at\n - (builtin location)\n - line 14, column 3\n", [<<"Duplicate definitions of abort at\n"
"Duplicate definitions of double_def at\n - line 10, column 3\n - line 11, column 3\n", " - (builtin location)\n"
"Duplicate definitions of double_proto at\n - line 4, column 3\n - line 5, column 3\n", " - line 14, column 3">>,
"Duplicate definitions of proto_and_def at\n - line 7, column 3\n - line 8, column 3\n", <<"Duplicate definitions of double_def at\n"
"Duplicate definitions of put at\n - (builtin location)\n - line 15, column 3\n", " - line 10, column 3\n"
"Duplicate definitions of state at\n - (builtin location)\n - line 16, column 3\n"]} " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<"Duplicate definitions of put at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of state at\n"
" - (builtin location)\n"
" - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
["Unbound variable zz at line 17, column 21\n", [<<"Unbound variable zz at line 17, column 21">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int\n", " x : int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 46)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)\n", " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 45\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string\n", " string">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 50\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 56\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)\n", " - z : string (at line 39, column 10)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 38)\n", "arising from the projection of the field y (at line 22, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 42)\n", "arising from an assignment of the field y (at line 21, column 42)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 38)\n", "arising from an assignment of the field y (at line 20, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 35)\n", "arising from an assignment of the field y (at line 19, column 35)">>,
"Ambiguous record type with field y (at line 13, column 25) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)\n", " - r' (at line 5, column 10)">>,
"Record type r2 does not have field y (at line 15, column 22)\n", <<"Repeated name x in pattern\n"
"The field z is missing when constructing an element of type r2 (at line 15, column 24)\n", " x :: x (at line 26, column 7)">>,
"Repeated name x in pattern\n" <<"No record type with fields y, z (at line 14, column 22)">>,
" x :: x (at line 26, column 7)\n", <<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
"No record type with fields y, z (at line 14, column 22)\n"]} <<"Record type r2 does not have field y (at line 15, column 22)">>]}
, {"init_type_error", , {"init_type_error",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and ()\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
["The field x is missing when constructing an element of type r('a) (at line 7, column 40)\n", [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
"The field y is missing when constructing an element of type r(int) (at line 8, column 40)\n", <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)\n"]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
, {"namespace_clash",
[<<"The contract Call (at line 4, column 10) has the same name as a namespace at (builtin location)">>]}
, {"bad_events",
[<<"The payload type int (at line 10, column 30) should be string">>,
<<"The payload type alias_address (at line 12, column 30) equals address but it should be string">>,
<<"The indexed type string (at line 9, column 25) is not a word type">>,
<<"The indexed type alias_string (at line 11, column 25) equals string which is not a word type">>]}
, {"bad_events2",
[<<"The event constructor BadEvent1 (at line 9, column 7) has too many non-indexed values (max 1)">>,
<<"The event constructor BadEvent2 (at line 10, column 7) has too many indexed values (max 3)">>,
<<"The event constructor BadEvent3 (at line 11, column 7) has too many non-indexed values (max 1)">>,
<<"The payload type address (at line 11, column 17) should be string">>,
<<"The payload type int (at line 11, column 26) should be string">>]}
, {"type_clash",
[<<"Cannot unify int\n"
" and string\n"
"when checking the record projection at line 12, column 40\n"
" r.foo : (gas : int, value : int) => Remote.themap\n"
"against the expected type\n"
" (gas : int, value : int) => map(string, int)">>]}
, {"bad_include_and_ns",
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
<<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]}
]. ].
+2
View File
@@ -12,9 +12,11 @@ groups() ->
, aeso_parser_tests , aeso_parser_tests
, aeso_compiler_tests , aeso_compiler_tests
, aeso_abi_tests , aeso_abi_tests
, aeso_aci_tests
]}]. ]}].
aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests). aeso_scan_tests(_Config) -> ok = eunit:test(aeso_scan_tests).
aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests). aeso_parser_tests(_Config) -> ok = eunit:test(aeso_parser_tests).
aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests). aeso_compiler_tests(_Config) -> ok = eunit:test(aeso_compiler_tests).
aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests). aeso_abi_tests(_Config) -> ok = eunit:test(aeso_abi_tests).
aeso_aci_tests(_Config) -> ok = eunit:test(aeso_aci_tests).
-28
View File
@@ -1,28 +0,0 @@
-module(contract_tests).
-include_lib("eunit/include/eunit.hrl").
make_cmd() -> "make -C " ++ aeso_test_utils:contract_path().
contracts_test_() ->
{setup,
fun() -> os:cmd(make_cmd()) end,
fun(_) -> os:cmd(make_cmd() ++ " clean") end,
[ {"Testing the " ++ Contract ++ " contract",
fun() ->
?assertCmdOutput(Expected, filename:join(aeso_test_utils:contract_path(), Contract ++ "_test"))
end} || {Contract, Expected} <- contracts() ]}.
contracts() ->
[].
%% [{"voting",
%% "Delegate before vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% "Delegate after vote\n"
%% "Cake: 1\n"
%% "Beer: 2\n"
%% "Winner: Beer\n"
%% }].
-2
View File
@@ -36,8 +36,6 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => () (x, y :: _) => ()
function bitOperations(x, y) = bnot (0xff00 band x bsl 4 bxor 0xa5a5a5 bsr 4 bor y)
function mutual() = function mutual() =
let rec recFun(x : int) = mutFun(x) let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1) and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
+25
View File
@@ -0,0 +1,25 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(indexed string, string)
| BadEvent2(indexed int, int)
| BadEvent3(indexed alias_string, string)
| BadEvent4(indexed int, alias_address)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+24
View File
@@ -0,0 +1,24 @@
contract Events =
type alias_int = int
type alias_address = address
type alias_string = string
datatype event =
Event1(indexed alias_int, indexed int, string)
| Event2(alias_string, indexed alias_address)
| BadEvent1(string, string)
| BadEvent2(indexed int, indexed int, indexed int, indexed address)
| BadEvent3(address, int)
function f1(x : int, y : string) =
Chain.event(Event1(x, x+1, y))
function f2(s : string) =
Chain.event(Event2(s, Call.caller))
function f3(x : int) =
Chain.event(Event1(x, x + 2, Int.to_str(x + 7)))
function i2s(i : int) = Int.to_str(i)
function a2s(a : address) = Address.to_str(a)
+6
View File
@@ -0,0 +1,6 @@
contract Bad =
include "included.aes"
namespace Foo =
function foo() = 42
function foo() = 43
+1 -1
View File
@@ -1,6 +1,6 @@
// Test more advanced chain interactions // Test more advanced chain interactions
contract Chain = contract ChainTest =
record state = { last_bf : address } record state = { last_bf : address }
+21
View File
@@ -0,0 +1,21 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
function inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
+9
View File
@@ -0,0 +1,9 @@
include "included.aes"
include "../contracts/included2.aes"
contract Include =
function foo() =
Included.foo() < Included2a.bar()
function bar() =
Included2b.foo() > Included.foo()
+2
View File
@@ -0,0 +1,2 @@
namespace Included =
function foo() = 42
+5
View File
@@ -0,0 +1,5 @@
namespace Included2a =
function bar() = 43
namespace Included2b =
function foo() = 44
+13 -15
View File
@@ -5,7 +5,7 @@
contract MultiSig = contract MultiSig =
record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint } record pending_state = { yetNeeded : int, ownersDone : bits, index : int }
datatype event = datatype event =
Confirmation (address, hash) // of { .owner : Address, .operation : Hash } Confirmation (address, hash) // of { .owner : Address, .operation : Hash }
@@ -13,18 +13,18 @@ contract MultiSig =
| OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address } | OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address }
| OwnerAdded (address) // of { .newOwner : Address } | OwnerAdded (address) // of { .newOwner : Address }
| OwnerRemoved (address) // of { .removedOwner : Address } | OwnerRemoved (address) // of { .removedOwner : Address }
| ReqChanged (uint) // of { .newReq : uint } | ReqChanged (int) // of { .newReq : int }
let maxOwners : uint = 250 let maxOwners : int = 250
record state = { nRequired : uint record state = { nRequired : int
, nOwners : uint , nOwners : int
, owners : map(uint, address) , owners : map(int, address)
, ownerIndex : map(address, uint) , ownerIndex : map(address, int)
, pending : map(hash, pending_state) , pending : map(hash, pending_state)
, pendingIndex : list(address) } , pendingIndex : list(address) }
function init (owners : list(address), nRequired : uint) : state = function init (owners : list(address), nRequired : int) : state =
let n = length(owners) + 1 let n = length(owners) + 1
{ nRequired = nRequired, { nRequired = nRequired,
nOwners = n, nOwners = n,
@@ -39,10 +39,9 @@ contract MultiSig =
function revoke(operation : hash) = function revoke(operation : hash) =
let ownerIx = lookup(state.ownerIndex, caller()) let ownerIx = lookup(state.ownerIndex, caller())
let pending = lookup(state.pendingIndex, operation) let pending = lookup(state.pendingIndex, operation)
let ownerIxBit = 1 bsl (ownerIx - 1) let _ = require(Bits.test(pending.ownersDone, ownerIx))
let _ = require(pending.ownersDone band ownerIxBit > 0)
let pending' = pending { yetNeeded = pending.yetNeeded + 1 let pending' = pending { yetNeeded = pending.yetNeeded + 1
, ownersDone = pending.ownersDone - ownerIxBit } , ownersDone = Bits.clear(pending.ownersDone, ownerIx - 1) }
put(state{ pendingIndex.operator = pending' }) put(state{ pendingIndex.operator = pending' })
event(Revoke(caller, operation)) event(Revoke(caller, operation))
@@ -91,7 +90,7 @@ contract MultiSig =
, pendingIx = [] }, , pendingIx = [] },
event = [OwnerRemoved(oldOwner)] } event = [OwnerRemoved(oldOwner)] }
function changeRequirement(newReq : uint) = function changeRequirement(newReq : int) =
let _ = require(newReq =< state.nOwners) let _ = require(newReq =< state.nOwners)
switch(check_pending(callhash())) switch(check_pending(callhash()))
CheckFail(state') => { state = state' } CheckFail(state') => { state = state' }
@@ -102,7 +101,7 @@ contract MultiSig =
event = [ReqChanged(newReq)] } event = [ReqChanged(newReq)] }
function getOwner(ownerIx0 : uint) = function getOwner(ownerIx0 : int) =
lookup(state.owners, ownerIx0 + 1) lookup(state.owners, ownerIx0 + 1)
function isOwner(owner : address) = function isOwner(owner : address) =
@@ -116,8 +115,7 @@ contract MultiSig =
Some(pending) => Some(pending) =>
let _ = require(isOwner(owner)) let _ = require(isOwner(owner))
let ownerIx = lookup(state.ownerIndex, owner) let ownerIx = lookup(state.ownerIndex, owner)
let ownerIxBit = 1 bsl (ownerIx - 1) Bits.test(pending.ownersDone, ownerIx - 1)
(pending.ownersDone band ownerIxBit) != 0
/* Leave the rest for now... */ /* Leave the rest for now... */
+5
View File
@@ -0,0 +1,5 @@
// You can't shadow existing contracts or namespaces.
contract Call =
function whatever() = ()
+21
View File
@@ -0,0 +1,21 @@
namespace Lib =
private function rev(xs, ys) =
switch(xs)
[] => ys
x :: xs => rev(xs, x :: ys)
function reverse(xs : list('a)) : list('a) = rev(xs, [])
function eqlist(xs : list(int), ys : list(int)) =
switch((xs, ys))
([], []) => true
(x :: xs, y :: ys) => x == y && eqlist(xs, ys)
_ => false
contract TestNamespaces =
function palindrome(xs : list(int)) : bool =
Lib.eqlist(xs, Lib.reverse(xs))
+21
View File
@@ -0,0 +1,21 @@
namespace List =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map1(f, xs)
function map2(f : 'a => 'b, xs : list('a)) =
switch(xs)
[] => []
x :: xs => f(x) :: map2(f, xs)
contract Deadcode =
function inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
function inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs)
-7
View File
@@ -1,5 +1,4 @@
// - + * / mod arithmetic operators // - + * / mod arithmetic operators
// bnot band bor bxor bsl bsr bitwise operators
// ! && || logical operators // ! && || logical operators
// == != < > =< >= comparison operators // == != < > =< >= comparison operators
// :: ++ list operators // :: ++ list operators
@@ -13,12 +12,6 @@ contract Operators =
"/" => a / b "/" => a / b
"mod" => a mod b "mod" => a mod b
"^" => a ^ b "^" => a ^ b
"bnot" => bnot a
"band" => a band b
"bor" => a bor b
"bxor" => a bxor b
"bsl" => a bsl b
"bsr" => a bsr b
function bool_op(a : bool, b : bool, op : string) = function bool_op(a : bool, b : bool, op : string) =
switch(op) switch(op)
-1
View File
@@ -18,7 +18,6 @@
contract SimpleStorage = contract SimpleStorage =
type event = int
record state = { data : int } record state = { data : int }
function init(value : int) : state = { data = value } function init(value : int) : state = { data = value }
+13 -10
View File
@@ -1,19 +1,22 @@
contract Remote = contract Remote =
function look_at : (state) => () record rstate = { i : int, s : string, m : map(int, int) }
function look_at : (rstate) => ()
function return_s : (bool) => string function return_s : (bool) => string
function return_m : (bool) => map(int, int) function return_m : (bool) => map(int, int)
function get : (state) => state function get : (rstate) => rstate
function get_i : (state) => int function get_i : (rstate) => int
function get_s : (state) => string function get_s : (rstate) => string
function get_m : (state) => map(int, int) function get_m : (rstate) => map(int, int)
function fun_update_i : (state, int) => state function fun_update_i : (rstate, int) => rstate
function fun_update_s : (state, string) => state function fun_update_s : (rstate, string) => rstate
function fun_update_m : (state, map(int, int)) => state function fun_update_m : (rstate, map(int, int)) => rstate
function fun_update_mk : (state, int, int) => state function fun_update_mk : (rstate, int, int) => rstate
contract StateHandling = contract StateHandling =
record state = { i : int, s : string, m : map(int, int) }
type state = Remote.rstate
function init(r : Remote, i : int) = function init(r : Remote, i : int) =
let state0 = { i = 0, s = "undefined", m = {} } let state0 = { i = 0, s = "undefined", m = {} }
+13
View File
@@ -0,0 +1,13 @@
contract Remote =
type themap = map(int, string)
function foo : () => themap
contract Main =
type themap = map(string, int)
// Should fail
function foo(r : Remote) : themap = r.foo()