Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d16fb82e25 | |||
| d2cd97def7 | |||
| 5455d0fcd7 | |||
| 2d3e6ab6e0 | |||
| 70a0f77793 | |||
| 04b3227317 | |||
| d9be8b2fca | |||
| a38afe7693 | |||
| 5719730d8c | |||
| 51b63f9559 | |||
| 5e6af18c7b | |||
| 4324bfd49e | |||
| faa0ef9772 | |||
| f07954f62c |
+102
-16
@@ -31,23 +31,48 @@ generates the following JSON structure representing the contract interface:
|
||||
{
|
||||
"contract": {
|
||||
"name": "Answers",
|
||||
"type_defs": [
|
||||
"state": {
|
||||
"record": [
|
||||
{
|
||||
"name": "state",
|
||||
"vars": [],
|
||||
"typedef": "{a : map(string,int)}"
|
||||
"name": "a",
|
||||
"type": {
|
||||
"map": {
|
||||
"key": "string",
|
||||
"value": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_defs": [
|
||||
{
|
||||
"name": "answers",
|
||||
"vars": [],
|
||||
"typedef": "map(string,int)"
|
||||
"typedef": {
|
||||
"map": {
|
||||
"key": "string",
|
||||
"value": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"functions": [
|
||||
{
|
||||
"name": "init",
|
||||
"arguments": [],
|
||||
"type": "{a : map(string,int)}",
|
||||
"returns": {
|
||||
"record": [
|
||||
{
|
||||
"name": "a",
|
||||
"type": {
|
||||
"map": {
|
||||
"key": "string",
|
||||
"value": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"stateful": true
|
||||
},
|
||||
{
|
||||
@@ -62,7 +87,12 @@ generates the following JSON structure representing the contract interface:
|
||||
"type": "int"
|
||||
}
|
||||
],
|
||||
"type": "map(string,int)",
|
||||
"returns": {
|
||||
"map": {
|
||||
"key": "string",
|
||||
"value": "int"
|
||||
}
|
||||
},
|
||||
"stateful": false
|
||||
}
|
||||
]
|
||||
@@ -74,7 +104,7 @@ When that encoding is decoded the following include definition is generated:
|
||||
|
||||
```
|
||||
contract Answers =
|
||||
function new_answer : (string, int) => map(string,int)
|
||||
function new_answer : (string, int) => map(string, int)
|
||||
```
|
||||
|
||||
### Types
|
||||
@@ -85,7 +115,7 @@ json_string() = binary()
|
||||
|
||||
### Exports
|
||||
|
||||
#### encode(ContractString) -> {ok,JSONstring} | {error,ErrorString}
|
||||
#### encode_contract(ContractString) -> {ok,JSONstring} | {error,ErrorString}
|
||||
|
||||
Types
|
||||
|
||||
@@ -94,9 +124,21 @@ ConstractString = contract_string()
|
||||
JSONstring = json_string()
|
||||
```
|
||||
|
||||
This is equivalent to `aeso_aci:encode_contract(ConstractString, [])`.
|
||||
|
||||
#### encode_contract(ContractString, Options) -> {ok,JSONstring} | {error,ErrorString}
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
ConstractString = contract_string()
|
||||
Options = [option()]
|
||||
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.
|
||||
#### decode_contract(JSONstring) -> ConstractString.
|
||||
|
||||
Types
|
||||
|
||||
@@ -107,23 +149,67 @@ 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.
|
||||
|
||||
#### encode_type(TypeAST) -> JSONstring.
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
JSONstring = json_string()
|
||||
```
|
||||
|
||||
Generate the JSON encoding of a type from the AST of the type.
|
||||
|
||||
#### encode_arg(ArgAST) -> JSONstring.
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
JSONstring = json_string()
|
||||
```
|
||||
|
||||
Generate the JSON encoding of a function argument from the AST of the argument.
|
||||
|
||||
#### encode_stmt(StmtAST) -> JSONstring.
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
JSONstring = json_string()
|
||||
```
|
||||
|
||||
Generate the JSON encoding of a statement from the AST of the statement.
|
||||
|
||||
#### encode_expr(ExprAST) -> JSONstring.
|
||||
|
||||
Types
|
||||
|
||||
``` erlang
|
||||
JSONstring = json_string()
|
||||
```
|
||||
|
||||
Generate the JSON encoding of an expression from the AST of the expression.
|
||||
|
||||
### Notes
|
||||
|
||||
The deprecated functions `aseo_aci:encode/2` and `aeso_aci:decode/1` are still available but should not be used.
|
||||
|
||||
### 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"...>>
|
||||
{ok,<<"contract Answers =\n\n record state = { a : answers }\n type answers() = map(string, int)\n\n stateful functio"...>>}
|
||||
2> {ok,Encoding} = aeso_aci:encode_contract(Contract).
|
||||
{ok,<<"{\"contract\":{\"name\":\"Answers\",\"state\":{\"record\":[{\"name\":\"a\",\"type\":{\"map\":{\"key\":\"string\",\"value\":\"int\"}}}]"...>>}
|
||||
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">>
|
||||
4> Decoded = aeso_aci:decode_contract(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 "...>>
|
||||
<<"{\n \"contract\": {\n \"name\": \"Answers\",\n \"state\": {\n \"record\": [\n {\n \"name\": \"a\",\n "...>>
|
||||
```
|
||||
|
||||
The final call to `jsx:prettify(Encoding)` returns the encoding in a
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
|
||||
{ref, "28f6c42"}}}
|
||||
{ref, "e8253b0"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
{tag, "2.8.0"}}}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
|
||||
{ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+336
-157
@@ -9,11 +9,16 @@
|
||||
|
||||
-module(aeso_aci).
|
||||
|
||||
%% Old deprecated interface.
|
||||
-export([encode/1,encode/2,decode/1]).
|
||||
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
|
||||
|
||||
-export([encode_contract/1,encode_contract/2,decode_contract/1]).
|
||||
-export([encode_func/1,encode_type/1,encode_arg/1,
|
||||
encode_stmt/1,encode_expr/1]).
|
||||
|
||||
%% Define records for the various typed syntactic forms. These make
|
||||
%% the code easier but don't seem to exist elsewhere.
|
||||
%% the code easier but don't seem to exist elsewhere. Unfortunately
|
||||
%% sometimes the same typename is used with different fields.
|
||||
|
||||
%% Top-level
|
||||
-record(contract, {ann,con,decls}).
|
||||
@@ -24,6 +29,7 @@
|
||||
%% Types
|
||||
-record(app_t, {ann,id,fields}).
|
||||
-record(tuple_t, {ann,args}).
|
||||
-record(bytes_t, {ann,len}).
|
||||
-record(record_t, {fields}).
|
||||
-record(field_t, {ann,id,type}).
|
||||
-record(alias_t, {type}).
|
||||
@@ -39,43 +45,62 @@
|
||||
-record(qcon, {ann,names}).
|
||||
-record(tvar, {ann,name}).
|
||||
|
||||
%% Statements
|
||||
-record(block, {ann,body}).
|
||||
-record('if', {ann,test,then,else}). %Both statement and expression
|
||||
-record(letval, {ann,pat,type,exp}).
|
||||
-record(switch, {ann,arg,cases}).
|
||||
-record('case', {ann,pat,body}).
|
||||
|
||||
%% Expressions
|
||||
-record(bool, {ann,bool}).
|
||||
-record(int, {ann,value}).
|
||||
-record(string, {ann,bin}).
|
||||
-record(hash, {ann,hash}).
|
||||
-record(bytes, {ann,bin}).
|
||||
-record(tuple, {ann,args}).
|
||||
-record(list, {ann,args}).
|
||||
-record(record, {ann,fields}). %Create a record
|
||||
-record(field, {ann,name,value}). %A record field
|
||||
-record(proj, {ann,value}). %?
|
||||
-record(map, {ann,fields}). %Create a map
|
||||
-record(map_get, {ann,field}).
|
||||
-record(lam, {ann,args,body}).
|
||||
-record(app, {ann,func,args}).
|
||||
-record(typed, {ann,expr,type}).
|
||||
|
||||
%% encode(ContractString) -> {ok,JSON} | {error,String}.
|
||||
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
|
||||
%% The old deprecated interface.
|
||||
|
||||
encode(C) -> encode_contract(C).
|
||||
encode(C, Os) -> encode_contract(C, Os).
|
||||
decode(J) -> decode_contract(J).
|
||||
|
||||
%% encode_contract(ContractString) -> {ok,JSON} | {error,String}.
|
||||
%% encode_contract(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) ->
|
||||
encode_contract(ContractString) ->
|
||||
encode_contract(ContractString, []).
|
||||
encode_contract(ContractString, Options) when is_binary(ContractString) ->
|
||||
encode_contract(binary_to_list(ContractString), Options);
|
||||
encode_contract(ContractString, Options) ->
|
||||
try
|
||||
Ast = parse(ContractString, Options),
|
||||
%% io:format("~p\n", [Ast]),
|
||||
%% io:format("Ast\n~p\n", [Ast]),
|
||||
%% aeso_ast:pp(Ast),
|
||||
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
||||
%% io:format("~p\n", [TypedAst]),
|
||||
%% io:format("Typed ast\n~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)),
|
||||
Tdefs = do_encode_contract_typedefs(sort_decls(contract_types(Contract))),
|
||||
Fdefs = [ do_encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
|
||||
not is_private_func(F) ],
|
||||
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
|
||||
{<<"type_defs">>, Tdefs},
|
||||
{<<"functions">>, Fdefs}]}],
|
||||
Jmap = [{<<"contract">>,
|
||||
[{<<"name">>, do_encode_name(Cname)}] ++
|
||||
Tdefs ++
|
||||
[{<<"functions">>, Fdefs}]}],
|
||||
%% io:format("~p\n", [Jmap]),
|
||||
{ok,jsx:encode(Jmap)}
|
||||
catch
|
||||
@@ -94,221 +119,375 @@ join_errors(Prefix, Errors, Pfun) ->
|
||||
Ess = [ Pfun(E) || E <- Errors ],
|
||||
list_to_binary(string:join([Prefix|Ess], "\n")).
|
||||
|
||||
%% encode_func(Function) -> JSON
|
||||
%% do_encode_contract_typedefs(TypeDefs) -> [JSON].
|
||||
%% Return a list of typedefs and state and event if they occur.
|
||||
|
||||
do_encode_contract_typedefs(Tdefs) ->
|
||||
Fun = fun(T, {Ts,Ss,Es}) ->
|
||||
%% Only one state and event.
|
||||
case typedef_name(T) of
|
||||
"state" -> {Ts,[do_encode_state_typedef(T)],Es};
|
||||
"event" -> {Ts,Ss,[do_encode_event_typedef(T)]};
|
||||
_Name -> {Ts ++ [do_encode_typedef(T)],Ss,Es}
|
||||
end
|
||||
end,
|
||||
{Ts,Ss,Es} = lists:foldl(Fun, {[],[],[]}, Tdefs),
|
||||
Ss ++ [{<<"type_defs">>, Ts}] ++ Es.
|
||||
|
||||
%% do_encode_state_typedef(StateTdef) -> JSON.
|
||||
%% do_encode_event_typedef(EventTdef) -> JSON.
|
||||
|
||||
do_encode_state_typedef(State) ->
|
||||
Def = typedef_def(State),
|
||||
{<<"state">>,do_encode_alias(Def)}.
|
||||
|
||||
do_encode_event_typedef(State) ->
|
||||
Def = typedef_def(State),
|
||||
{<<"event">>,do_encode_alias(Def)}.
|
||||
|
||||
%% encode_func(TypedAST) -> JSON.
|
||||
%% Encode a function AST into a JSON structure.
|
||||
|
||||
encode_func(AST) ->
|
||||
jsx:encode(do_encode_func(AST)).
|
||||
|
||||
%% do_encode_func(Function) -> JSONmap
|
||||
%% Encode a function definition. Currently we are only interested in
|
||||
%% the interface and type.
|
||||
|
||||
encode_func(Fdef) ->
|
||||
do_encode_func(Fdef) ->
|
||||
Name = function_name(Fdef),
|
||||
Args = function_args(Fdef),
|
||||
Type = function_type(Fdef),
|
||||
[{<<"name">>, encode_name(Name)},
|
||||
{<<"arguments">>, encode_args(Args)},
|
||||
{<<"returns">>, encode_type(Type)},
|
||||
[{<<"name">>, do_encode_name(Name)},
|
||||
{<<"arguments">>, do_encode_args(Args)},
|
||||
{<<"returns">>, do_encode_type(Type)},
|
||||
{<<"stateful">>, is_stateful_func(Fdef)}].
|
||||
|
||||
%% encode_args(Args) -> [JSON].
|
||||
%% encode_arg(Args) -> JSON.
|
||||
%% encode_arg(TypedAST) -> JSON.
|
||||
%% Encode an argument AST into a JSON structure.
|
||||
|
||||
encode_args(Args) ->
|
||||
[ encode_arg(A) || A <- Args ].
|
||||
encode_arg(AST) ->
|
||||
jsx:encode(do_encode_arg(AST)).
|
||||
|
||||
encode_arg(#arg{id=Id,type=T}) ->
|
||||
[{<<"name">>,encode_type(Id)},
|
||||
{<<"type">>,[encode_type(T)]}].
|
||||
%% do_encode_args(ArgASTs) -> [JSONmap].
|
||||
%% do_encode_arg(ArgAST) -> JSONmap.
|
||||
|
||||
%% encode_types(Types) -> [JSON].
|
||||
%% encode_type(Type) -> JSON.
|
||||
do_encode_args(Args) ->
|
||||
[ do_encode_arg(A) || A <- Args ].
|
||||
|
||||
encode_types(Types) ->
|
||||
[ encode_type(T) || T <- Types ].
|
||||
do_encode_arg(#arg{id=Id,type=T}) ->
|
||||
[{<<"name">>,do_encode_type(Id)},
|
||||
{<<"type">>,do_encode_type(T)}].
|
||||
|
||||
encode_type(#tvar{name=N}) -> encode_name(N);
|
||||
encode_type(#id{name=N}) -> encode_name(N);
|
||||
encode_type(#con{name=N}) -> encode_name(N);
|
||||
encode_type(#qid{names=Ns}) ->
|
||||
encode_name(lists:join(".", Ns));
|
||||
encode_type(#qcon{names=Ns}) ->
|
||||
encode_name(lists:join(".", Ns)); %?
|
||||
encode_type(#tuple_t{args=As}) ->
|
||||
Eas = encode_types(As),
|
||||
%% encode_type(TypedAST) -> JSON.
|
||||
%% Encode a type AST into a JSON structure.
|
||||
|
||||
encode_type(AST) ->
|
||||
jsx:encode(do_encode_type(AST)).
|
||||
|
||||
%% do_encode_types([TypeAST]) -> [JSONmap].
|
||||
%% do_encode_type(TypeAST) -> JsonMap.
|
||||
|
||||
do_encode_types(Types) ->
|
||||
[ do_encode_type(T) || T <- Types ].
|
||||
|
||||
do_encode_type(#tvar{name=N}) -> do_encode_name(N);
|
||||
do_encode_type(#id{name=N}) -> do_encode_name(N);
|
||||
do_encode_type(#con{name=N}) -> do_encode_name(N);
|
||||
do_encode_type(#qid{names=Ns}) ->
|
||||
do_encode_name(lists:join(".", Ns));
|
||||
do_encode_type(#qcon{names=Ns}) ->
|
||||
do_encode_name(lists:join(".", Ns)); %?
|
||||
do_encode_type(#tuple_t{args=As}) ->
|
||||
Eas = do_encode_types(As),
|
||||
[{<<"tuple">>,Eas}];
|
||||
encode_type(#record_t{fields=Fs}) ->
|
||||
Efs = encode_fields(Fs),
|
||||
do_encode_type(#bytes_t{len=Len}) ->
|
||||
{<<"bytes">>,Len};
|
||||
do_encode_type(#record_t{fields=Fs}) ->
|
||||
Efs = do_encode_type_rec_fields(Fs),
|
||||
[{<<"record">>,Efs}];
|
||||
encode_type(#app_t{id=Id,fields=Fs}) ->
|
||||
Name = encode_type(Id),
|
||||
Efs = encode_types(Fs),
|
||||
%% Special case lists and maps as they are built-in types.
|
||||
do_encode_type(#app_t{id=#id{name="list"},fields=[F]}) ->
|
||||
Ef = do_encode_type(F),
|
||||
[{<<"list">>,Ef}];
|
||||
do_encode_type(#app_t{id=#id{name="map"},fields=Fs}) ->
|
||||
Ef = do_encode_type_mapo_field(Fs),
|
||||
[{<<"map">>,Ef}];
|
||||
%% Other applications.
|
||||
do_encode_type(#app_t{id=Id,fields=Fs}) ->
|
||||
Name = do_encode_type(Id),
|
||||
Efs = do_encode_types(Fs),
|
||||
[{Name,Efs}];
|
||||
encode_type(#variant_t{cons=Cs}) ->
|
||||
Ecs = encode_types(Cs),
|
||||
do_encode_type(#variant_t{cons=Cs}) ->
|
||||
Ecs = do_encode_types(Cs),
|
||||
[{<<"variant">>,Ecs}];
|
||||
encode_type(#constr_t{con=C,args=As}) ->
|
||||
Ec = encode_type(C),
|
||||
Eas = encode_types(As),
|
||||
do_encode_type(#constr_t{con=C,args=As}) ->
|
||||
Ec = do_encode_type(C),
|
||||
Eas = do_encode_types(As),
|
||||
[{Ec,Eas}];
|
||||
encode_type(#fun_t{args=As,type=T}) ->
|
||||
Eas = encode_types(As),
|
||||
Et = encode_type(T),
|
||||
do_encode_type(#fun_t{args=As,type=T}) ->
|
||||
Eas = do_encode_types(As),
|
||||
Et = do_encode_type(T),
|
||||
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
|
||||
|
||||
encode_name(Name) ->
|
||||
do_encode_name(Name) ->
|
||||
list_to_binary(Name).
|
||||
|
||||
%% encode_fields(Fields) -> [JSON].
|
||||
%% encode_field(Field) -> JSON.
|
||||
%% Encode a record field.
|
||||
%% do_encode_type_rec_fields(Fields) -> [JSONmap].
|
||||
%% do_encode_type_rec_field(Field) -> JSONmap.
|
||||
%% Encode a record field type.
|
||||
|
||||
encode_fields(Fs) ->
|
||||
[ encode_field(F) || F <- Fs ].
|
||||
do_encode_type_rec_fields(Fs) ->
|
||||
[ do_encode_type_rec_field(F) || F <- Fs ].
|
||||
|
||||
encode_field(#field_t{id=Id,type=T}) ->
|
||||
[{<<"name">>,encode_type(Id)},
|
||||
{<<"type">>,[encode_type(T)]}].
|
||||
do_encode_type_rec_field(#field_t{id=Id,type=T}) ->
|
||||
[{<<"name">>,do_encode_type(Id)},
|
||||
{<<"type">>,do_encode_type(T)}].
|
||||
|
||||
%% encode_typedef(TypeDef) -> JSON.
|
||||
%% do_encode_type_mapo_field(Field) -> JSONmap.
|
||||
%% Two fields for one map type.
|
||||
|
||||
encode_typedef(Type) ->
|
||||
do_encode_type_mapo_field([K,V]) ->
|
||||
[{<<"key">>,do_encode_type(K)},
|
||||
{<<"value">>,do_encode_type(V)}].
|
||||
|
||||
%% do_encode_typedef(TypeDefAST) -> JSON.
|
||||
|
||||
do_encode_typedef(Type) ->
|
||||
Name = typedef_name(Type),
|
||||
Vars = typedef_vars(Type),
|
||||
Def = typedef_def(Type),
|
||||
[{<<"name">>, encode_name(Name)},
|
||||
{<<"vars">>, encode_tvars(Vars)},
|
||||
{<<"typedef">>, encode_alias(Def)}].
|
||||
[{<<"name">>, do_encode_name(Name)},
|
||||
{<<"vars">>, do_encode_tvars(Vars)},
|
||||
{<<"typedef">>, do_encode_alias(Def)}].
|
||||
|
||||
encode_tvars(Vars) ->
|
||||
[ encode_tvar(V) || V <- Vars ].
|
||||
do_encode_tvars(Vars) ->
|
||||
[ do_encode_tvar(V) || V <- Vars ].
|
||||
|
||||
encode_tvar(#tvar{name=N}) ->
|
||||
[{<<"name">>, encode_name(N)}].
|
||||
do_encode_tvar(#tvar{name=N}) ->
|
||||
[{<<"name">>, do_encode_name(N)}].
|
||||
|
||||
encode_alias(#alias_t{type=T}) ->
|
||||
encode_type(T);
|
||||
encode_alias(A) -> encode_type(A).
|
||||
do_encode_alias(#alias_t{type=T}) ->
|
||||
do_encode_type(T);
|
||||
do_encode_alias(A) -> do_encode_type(A).
|
||||
|
||||
%% encode_stmt(Stmt) -> JSON.
|
||||
%% encode_stmt(StmtAST) -> JSON.
|
||||
%% Encode a statement AST into a JSON structure.
|
||||
|
||||
encode_stmt(E) ->
|
||||
encode_expr(E).
|
||||
encode_stmt(AST) ->
|
||||
jsx:encode(do_encode_stmt(AST)).
|
||||
|
||||
%% encode_exprs(Exprs) -> [JSON].
|
||||
%% encode_expr(Expr) -> JSON.
|
||||
%% do_encode_stmt(StmtAST) -> JSONmap.
|
||||
|
||||
encode_exprs(Es) ->
|
||||
[ encode_expr(E) || E <- Es ].
|
||||
do_encode_stmt(#typed{expr=E}) -> %Ignore the type
|
||||
do_encode_stmt(E);
|
||||
do_encode_stmt(#block{body=Body}) ->
|
||||
Eblock = [ do_encode_stmt(B) || B <- Body ],
|
||||
[{<<"block">>,Eblock}];
|
||||
do_encode_stmt(#'if'{test=Test,then=Then,else=Else}) ->
|
||||
%% This is both a statement and en expression.
|
||||
Etest = do_encode_expr(Test),
|
||||
Ethen = do_encode_stmt(Then),
|
||||
Eelse = do_encode_stmt(Else),
|
||||
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
|
||||
do_encode_stmt(#letval{pat=Pat,exp=Exp}) ->
|
||||
Epat = do_encode_expr(Pat),
|
||||
Eexp = do_encode_expr(Exp),
|
||||
[{<<"let">>,[{<<"pattern">>,Epat},{<<"expression">>,Eexp}]}];
|
||||
do_encode_stmt(#switch{arg=Arg,cases=Cases}) ->
|
||||
Earg = do_encode_expr(Arg),
|
||||
Ecases = [ do_encode_stmt_case(Case) || Case <- Cases ],
|
||||
[{<<"switch">>,[{<<"arg">>,Earg},{<<"cases">>,Ecases}]}];
|
||||
do_encode_stmt(E) ->
|
||||
do_encode_expr(E).
|
||||
|
||||
encode_expr(#id{name=N}) -> encode_name(N);
|
||||
encode_expr(#con{name=N}) -> encode_name(N);
|
||||
encode_expr(#qid{names=Ns}) ->
|
||||
encode_name(lists:join(".", Ns));
|
||||
encode_expr(#qcon{names=Ns}) ->
|
||||
encode_name(lists:join(".", Ns)); %?
|
||||
encode_expr(#typed{expr=E}) ->
|
||||
encode_expr(E);
|
||||
encode_expr(#bool{bool=B}) -> B;
|
||||
encode_expr(#int{value=V}) -> V;
|
||||
encode_expr(#string{bin=B}) -> B;
|
||||
encode_expr(#hash{hash=H}) -> H;
|
||||
encode_expr(#tuple{args=As}) ->
|
||||
Eas = encode_exprs(As),
|
||||
do_encode_stmt_case(#'case'{pat=Pat,body=Body}) ->
|
||||
Epat = do_encode_expr(Pat), %Patterns are expessions
|
||||
Ebody = do_encode_stmt(Body),
|
||||
[{<<"pattern">>,Epat},{<<"body">>,Ebody}].
|
||||
|
||||
%% encode_expr(ExprAST) -> JSON.
|
||||
%% Encode an expression AST into a JSON structure.
|
||||
|
||||
encode_expr(AST) ->
|
||||
jsx:encode(do_encode_expr(AST)).
|
||||
|
||||
%% do_encode_exprs(ExprASTs) -> [JSONmap].
|
||||
%% do_encode_expr(ExprAST) -> JSONmap.
|
||||
|
||||
do_encode_exprs(Es) ->
|
||||
[ do_encode_expr(E) || E <- Es ].
|
||||
|
||||
do_encode_expr(#typed{expr=E}) -> %Ignore the type
|
||||
do_encode_expr(E);
|
||||
do_encode_expr(#id{name=N}) -> do_encode_name(N);
|
||||
do_encode_expr(#con{name=N}) -> do_encode_name(N);
|
||||
do_encode_expr(#qid{names=Ns}) ->
|
||||
do_encode_name(lists:join(".", Ns));
|
||||
do_encode_expr(#qcon{names=Ns}) ->
|
||||
do_encode_name(lists:join(".", Ns)); %?
|
||||
do_encode_expr(#bool{bool=B}) -> B;
|
||||
do_encode_expr(#int{value=V}) -> V;
|
||||
do_encode_expr(#string{bin=B}) ->
|
||||
[{<<"string">>,B}];
|
||||
do_encode_expr(#bytes{bin=B}) -> B;
|
||||
do_encode_expr(#tuple{args=As}) ->
|
||||
Eas = do_encode_exprs(As),
|
||||
[{<<"tuple">>,Eas}];
|
||||
encode_expr(#list{args=As}) ->
|
||||
Eas = encode_exprs(As),
|
||||
do_encode_expr(#list{args=As}) ->
|
||||
Eas = do_encode_exprs(As),
|
||||
[{<<"list">>,Eas}];
|
||||
encode_expr(#app{func=F,args=As}) ->
|
||||
Ef = encode_expr(F),
|
||||
Eas = encode_exprs(As),
|
||||
do_encode_expr(#record{fields=Fs}) -> %Create a record
|
||||
Efs = do_encode_expr_rec_fields(Fs),
|
||||
[{<<"create_record">>,Efs}];
|
||||
do_encode_expr({record,_Ann,Rec,Fs}) -> %Update a record
|
||||
Erec = do_encode_expr(Rec),
|
||||
Efs = do_encode_expr_rec_fields(Fs),
|
||||
[{<<"update_record">>,[Erec,Efs]}];
|
||||
do_encode_expr(#lam{args=As,body=B}) ->
|
||||
Eas = do_encode_args(As),
|
||||
Eb = do_encode_stmt(B),
|
||||
[{<<"function">>,[{<<"arguments">>,Eas},{<<"body">>,Eb}]}];
|
||||
do_encode_expr(#map{fields=Fs}) -> %Create a map
|
||||
Efs = do_encode_expr_map_fields(Fs),
|
||||
[{<<"create_map">>,Efs}];
|
||||
do_encode_expr({map,_Ann,Map,Fs}) -> %Update a map
|
||||
Emap = do_encode_expr(Map),
|
||||
Efs = do_encode_expr_map_fields(Fs),
|
||||
[{<<"update_map">>,[Emap,Efs]}];
|
||||
do_encode_expr(#map_get{field=F}) ->
|
||||
do_encode_expr(F);
|
||||
do_encode_expr(#proj{value=V}) ->
|
||||
do_encode_expr(V);
|
||||
do_encode_expr(#app{func=F,args=As}) ->
|
||||
Ef = do_encode_expr(F),
|
||||
Eas = do_encode_exprs(As),
|
||||
[{<<"apply">>,[{<<"function">>,Ef},
|
||||
{<<"arguments">>,Eas}]}];
|
||||
encode_expr({Op,_Ann}) ->
|
||||
do_encode_expr(#'if'{test=Test,then=Then,else=Else}) ->
|
||||
%% This is both a statement and en expression.
|
||||
Etest = do_encode_expr(Test),
|
||||
Ethen = do_encode_expr(Then),
|
||||
Eelse = do_encode_expr(Else),
|
||||
[{<<"if">>,[{<<"test">>,Etest},{<<"then">>,Ethen},{<<"else">>,Eelse}]}];
|
||||
do_encode_expr({Op,_Ann}) ->
|
||||
list_to_binary(atom_to_list(Op)).
|
||||
|
||||
%% decode(JSON) -> ContractString.
|
||||
%% do_encode_expr_rec_fields(Fields) -> [JSON].
|
||||
%% do_encode_expr_rec_field(Field) -> JSON.
|
||||
%% Encode a record field expression.
|
||||
|
||||
do_encode_expr_rec_fields(Fs) ->
|
||||
[ do_encode_expr_rec_field(F) || F <- Fs ].
|
||||
|
||||
do_encode_expr_rec_field(#field{name=[N],value=V}) ->
|
||||
[{<<"name">>,do_encode_expr(N)},
|
||||
{<<"value">>,do_encode_expr(V)}].
|
||||
|
||||
%% do_encode_expr_map_fields(Fields) -> [JSON].
|
||||
%% do_encode_expr_map_field(Field) -> JSON.
|
||||
%% Encode a map field expression.
|
||||
|
||||
do_encode_expr_map_fields(Fs) ->
|
||||
[ do_encode_expr_map_field(F) || F <- Fs ].
|
||||
|
||||
do_encode_expr_map_field({K,V}) ->
|
||||
[{<<"key">>,do_encode_expr(K)},
|
||||
{<<"value">>,do_encode_expr(V)}];
|
||||
do_encode_expr_map_field(#field{name=[K],value=V}) ->
|
||||
[{<<"key">>,do_encode_expr(K)},
|
||||
{<<"value">>,do_encode_expr(V)}].
|
||||
|
||||
%% decode_contract(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) ->
|
||||
decode_contract(Json) ->
|
||||
Map = jsx:decode(Json, [return_maps]),
|
||||
%% io:format("~p\n", [Map]),
|
||||
#{<<"contract">> := C} = Map,
|
||||
list_to_binary(decode_contract(C)).
|
||||
#{<<"name">> := Name, <<"type_defs">> := Ts, <<"functions">> := Fs} = C,
|
||||
CS = ["contract"," ",io_lib:format("~s", [Name])," =\n",
|
||||
do_decode_tdefs(Ts),
|
||||
do_decode_funcs(Fs)],
|
||||
list_to_binary(CS).
|
||||
|
||||
decode_contract(#{<<"name">> := Name,
|
||||
<<"type_defs">> := Ts,
|
||||
<<"functions">> := Fs}) ->
|
||||
["contract"," ",io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts),
|
||||
decode_funcs(Fs)].
|
||||
do_decode_funcs(Fs) -> [ do_decode_func(F) || F <- Fs ].
|
||||
|
||||
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
||||
|
||||
decode_func(#{<<"name">> := <<"init">>}) -> [];
|
||||
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
|
||||
do_decode_func(#{<<"name">> := <<"init">>}) -> [];
|
||||
do_decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
|
||||
[" function"," ",io_lib:format("~s", [Name])," : ",
|
||||
decode_args(As)," => ",decode_type(T),$\n].
|
||||
do_decode_args(As)," => ",do_decode_type(T),$\n].
|
||||
|
||||
decode_args(As) ->
|
||||
Das = [ decode_arg(A) || A <- As ],
|
||||
do_decode_args(As) ->
|
||||
Das = [ do_decode_arg(A) || A <- As ],
|
||||
[$(,lists:join(", ", Das),$)].
|
||||
|
||||
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
|
||||
do_decode_arg(#{<<"type">> := T}) -> do_decode_type(T).
|
||||
|
||||
decode_types(Ets) ->
|
||||
[ decode_type(Et) || Et <- Ets ].
|
||||
do_decode_types(Ets) ->
|
||||
[ do_decode_type(Et) || Et <- Ets ].
|
||||
|
||||
decode_type(#{<<"tuple">> := Ets}) ->
|
||||
Ts = decode_types(Ets),
|
||||
[$(,lists:join(",", Ts),$)];
|
||||
decode_type(#{<<"record">> := Efs}) ->
|
||||
Fs = decode_fields(Efs),
|
||||
[${,lists:join(",", Fs),$}];
|
||||
decode_type(#{<<"list">> := [Et]}) ->
|
||||
T = decode_type(Et),
|
||||
do_decode_type(#{<<"tuple">> := Ets}) ->
|
||||
Ts = do_decode_types(Ets),
|
||||
[$(,lists:join(", ", Ts),$)];
|
||||
do_decode_type(#{<<"record">> := Efs}) ->
|
||||
Fs = do_decode_type_rec_fields(Efs),
|
||||
[${,lists:join(", ", Fs),$}];
|
||||
do_decode_type(#{<<"list">> := Et}) ->
|
||||
T = do_decode_type(Et),
|
||||
["list",$(,T,$)];
|
||||
decode_type(#{<<"map">> := Ets}) ->
|
||||
Ts = decode_types(Ets),
|
||||
["map",$(,lists:join(",", Ts),$)];
|
||||
decode_type(#{<<"variant">> := Ets}) ->
|
||||
Ts = decode_types(Ets),
|
||||
do_decode_type(#{<<"map">> := Et}) ->
|
||||
T = do_decode_type_map(Et),
|
||||
["map",$(,T,$)];
|
||||
do_decode_type(#{<<"variant">> := Ets}) ->
|
||||
Ts = do_decode_types(Ets),
|
||||
lists:join(" | ", Ts);
|
||||
decode_type(Econs) when is_map(Econs) -> %General constructor
|
||||
do_decode_type(Econs) when is_map(Econs) -> %General constructor
|
||||
%% io:format("~p\n", [Econs]),
|
||||
[{Ec,Ets}] = maps:to_list(Econs),
|
||||
C = decode_name(Ec),
|
||||
Ts = decode_types(Ets),
|
||||
[C,$(,lists:join(",", Ts),$)];
|
||||
decode_type(T) -> %Just raw names.
|
||||
decode_name(T).
|
||||
C = do_decode_name(Ec),
|
||||
Ts = do_decode_types(Ets),
|
||||
[C,$(,lists:join(", ", Ts),$)];
|
||||
do_decode_type(T) -> %Just raw names.
|
||||
do_decode_name(T).
|
||||
|
||||
decode_name(En) ->
|
||||
do_decode_name(En) ->
|
||||
binary_to_list(En).
|
||||
|
||||
decode_fields(Efs) ->
|
||||
[ decode_field(Ef) || Ef <- Efs ].
|
||||
do_decode_type_rec_fields(Efs) ->
|
||||
[ do_decode_type_rec_field(Ef) || Ef <- Efs ].
|
||||
|
||||
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
|
||||
Name = decode_name(En),
|
||||
Type = decode_type(Et),
|
||||
do_decode_type_rec_field(#{<<"name">> := En,<<"type">> := Et}) ->
|
||||
Name = do_decode_name(En),
|
||||
Type = do_decode_type(Et),
|
||||
[Name," : ",Type].
|
||||
|
||||
%% decode_tdefs(Json) -> [TypeString].
|
||||
do_decode_type_map(#{<<"key">> := Ek,<<"value">> := Ev}) ->
|
||||
Key = do_decode_type(Ek),
|
||||
Value = do_decode_type(Ev),
|
||||
[Key,", ",Value].
|
||||
|
||||
%% do_decode_tdefs(Json) -> [TypeString].
|
||||
%% Here we are only interested in the type definitions and ignore the
|
||||
%% aliases. We find them as they always have variants.
|
||||
|
||||
decode_tdefs(Ts) -> [ decode_tdef(T) ||
|
||||
do_decode_tdefs(Ts) -> [ do_decode_tdef(T) ||
|
||||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
|
||||
].
|
||||
|
||||
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
||||
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
|
||||
" = ",decode_type(T),$\n].
|
||||
do_decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
||||
[" datatype"," ",do_decode_name(Name),do_decode_tvars(Vs),
|
||||
" = ",do_decode_type(T),$\n].
|
||||
|
||||
decode_tvars([]) -> []; %No tvars, no parentheses
|
||||
decode_tvars(Vs) ->
|
||||
Dvs = [ decode_tvar(V) || V <- Vs ],
|
||||
do_decode_tvars([]) -> []; %No tvars, no parentheses
|
||||
do_decode_tvars(Vs) ->
|
||||
Dvs = [ do_decode_tvar(V) || V <- Vs ],
|
||||
[$(,lists:join(", ", Dvs),$)].
|
||||
|
||||
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
||||
do_decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
||||
|
||||
%% #contract{Ann, Con, [Declarations]}.
|
||||
|
||||
|
||||
@@ -56,7 +56,12 @@
|
||||
, fields :: [aeso_syntax:id()]
|
||||
, context :: why_record() }).
|
||||
|
||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{}.
|
||||
-record(is_contract_constraint,
|
||||
{ contract_t :: utype(),
|
||||
context :: aeso_syntax:expr() %% The address literal
|
||||
}).
|
||||
|
||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||
|
||||
-record(field_info,
|
||||
{ ann :: aeso_syntax:ann()
|
||||
@@ -341,6 +346,7 @@ global_env() ->
|
||||
Address = {id, Ann, "address"},
|
||||
Hash = {id, Ann, "hash"},
|
||||
Bits = {id, Ann, "bits"},
|
||||
Bytes = fun(Len) -> {bytes_t, Ann, Len} end,
|
||||
Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end,
|
||||
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
|
||||
Unit = {tuple_t, Ann, []},
|
||||
@@ -373,7 +379,9 @@ global_env() ->
|
||||
{"abort", Fun1(String, A)}])
|
||||
, types = MkDefs(
|
||||
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
|
||||
{"hash", 0}, {"signature", 0}, {"bits", 0},
|
||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
||||
{"signature", {[], {alias_t, Bytes(64)}}},
|
||||
{"bits", 0},
|
||||
{"option", 1}, {"list", 1}, {"map", 2},
|
||||
{"oracle", 2}, {"oracle_query", 2}
|
||||
]) },
|
||||
@@ -439,6 +447,7 @@ global_env() ->
|
||||
CryptoScope = #scope
|
||||
{ funs = MkDefs(
|
||||
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
|
||||
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
|
||||
{"sha3", Fun1(A, Hash)},
|
||||
{"sha256", Fun1(A, Hash)},
|
||||
{"blake2b", Fun1(A, Hash)}]) },
|
||||
@@ -497,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
||||
infer(Contracts) ->
|
||||
infer(Contracts, []).
|
||||
|
||||
-type option() :: permissive_address_literals | return_env.
|
||||
-type option() :: return_env.
|
||||
|
||||
-spec init_env(list(option())) -> env().
|
||||
init_env(_Options) -> global_env().
|
||||
@@ -675,6 +684,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id;
|
||||
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
|
||||
ensure_base_type(Type, Arity),
|
||||
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
|
||||
check_type(_Env, Type = {bytes_t, _Ann, _Len}, Arity) ->
|
||||
ensure_base_type(Type, Arity),
|
||||
Type;
|
||||
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
|
||||
Types1 = [ check_type(Env, T, 0) || T <- Types ],
|
||||
Type1 = check_type(Env, Type, Arity + length(Types)),
|
||||
@@ -898,20 +910,29 @@ infer_expr(_Env, Body={int, As, _}) ->
|
||||
{typed, As, Body, {id, As, "int"}};
|
||||
infer_expr(_Env, Body={string, As, _}) ->
|
||||
{typed, As, Body, {id, As, "string"}};
|
||||
infer_expr(_Env, Body={hash, As, Hash}) ->
|
||||
case byte_size(Hash) of
|
||||
32 -> {typed, As, Body, {id, As, "address"}};
|
||||
64 -> {typed, As, Body, {id, As, "signature"}}
|
||||
end;
|
||||
infer_expr(_Env, Body={bytes, As, Bin}) ->
|
||||
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
|
||||
infer_expr(_Env, Body={account_pubkey, As, _}) ->
|
||||
{typed, As, Body, {id, As, "address"}};
|
||||
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
|
||||
Q = fresh_uvar(As),
|
||||
R = fresh_uvar(As),
|
||||
{typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}};
|
||||
infer_expr(_Env, Body={oracle_query_id, As, _}) ->
|
||||
Q = fresh_uvar(As),
|
||||
R = fresh_uvar(As),
|
||||
{typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}};
|
||||
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
|
||||
Con = fresh_uvar(As),
|
||||
constrain([#is_contract_constraint{ contract_t = Con,
|
||||
context = Body }]),
|
||||
{typed, As, Body, Con};
|
||||
infer_expr(_Env, Body={id, As, "_"}) ->
|
||||
{typed, As, Body, fresh_uvar(As)};
|
||||
infer_expr(Env, Id = {id, As, _}) ->
|
||||
infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid ->
|
||||
{QName, Type} = lookup_name(Env, As, Id),
|
||||
{typed, As, QName, Type};
|
||||
infer_expr(Env, Id = {qid, As, _}) ->
|
||||
{QName, Type} = lookup_name(Env, As, Id),
|
||||
{typed, As, QName, Type};
|
||||
infer_expr(Env, Id = {con, As, _}) ->
|
||||
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
|
||||
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
|
||||
{typed, As, QName, Type};
|
||||
infer_expr(Env, {unit, As}) ->
|
||||
@@ -1348,6 +1369,16 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
||||
end,
|
||||
check_record_create_constraints(Env, Cs).
|
||||
|
||||
check_is_contract_constraints(_Env, []) -> ok;
|
||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||
#is_contract_constraint{ contract_t = Type, context = Lit } = C,
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
case lookup_type(Env, record_type_name(Type1)) of
|
||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
||||
_ -> type_error({not_a_contract_type, Type1, Lit})
|
||||
end,
|
||||
check_is_contract_constraints(Env, Cs).
|
||||
|
||||
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
|
||||
solve_field_constraints(Env, Constraints) ->
|
||||
%% First look for record fields that appear in only one type definition
|
||||
@@ -1446,9 +1477,12 @@ solve_known_record_types(Env, Constraints) ->
|
||||
DerefConstraints--SolvedConstraints.
|
||||
|
||||
destroy_and_report_unsolved_field_constraints(Env) ->
|
||||
{FieldCs, CreateCs} =
|
||||
{FieldCs, OtherCs} =
|
||||
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
||||
get_field_constraints()),
|
||||
{CreateCs, ContractCs} =
|
||||
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
||||
OtherCs),
|
||||
Unknown = solve_known_record_types(Env, FieldCs),
|
||||
if Unknown == [] -> ok;
|
||||
true ->
|
||||
@@ -1458,6 +1492,7 @@ destroy_and_report_unsolved_field_constraints(Env) ->
|
||||
end
|
||||
end,
|
||||
check_record_create_constraints(Env, CreateCs),
|
||||
check_is_contract_constraints(Env, ContractCs),
|
||||
destroy_field_constraints(),
|
||||
ok.
|
||||
|
||||
@@ -1583,6 +1618,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
|
||||
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
|
||||
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
||||
{constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)};
|
||||
unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) ->
|
||||
{named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default};
|
||||
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
||||
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
||||
unfold_types_in_type(Env, [H|T], Options) ->
|
||||
@@ -1638,6 +1675,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
|
||||
true;
|
||||
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
||||
true;
|
||||
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||
true;
|
||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
||||
unify(Env, Named1, Named2, When) andalso
|
||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||
@@ -1655,26 +1694,8 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
|
||||
unify1(Env, A, {app_t, _, T, []}, When) ->
|
||||
unify(Env, A, T, When);
|
||||
unify1(_Env, A, B, When) ->
|
||||
Ok =
|
||||
case get_option(permissive_address_literals, false) of
|
||||
true ->
|
||||
Kind = fun({qcon, _, _}) -> con;
|
||||
({con, _, _}) -> con;
|
||||
({id, _, "address"}) -> addr;
|
||||
({id, _, "hash"}) -> hash;
|
||||
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
|
||||
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
|
||||
(_) -> other end,
|
||||
%% If permissive_address_literals we allow unifying adresses
|
||||
%% with contract types or oracles/oracle queries
|
||||
case lists:usort([Kind(A), Kind(B)]) of
|
||||
[addr, K] -> K /= other;
|
||||
_ -> false
|
||||
end;
|
||||
false -> false
|
||||
end,
|
||||
[ cannot_unify(A, B, When) || not Ok ],
|
||||
Ok.
|
||||
cannot_unify(A, B, When),
|
||||
false.
|
||||
|
||||
dereference(T = {uvar, _, R}) ->
|
||||
case ets_lookup(type_vars, R) of
|
||||
@@ -1703,6 +1724,7 @@ occurs_check1(_, {con, _, _}) -> false;
|
||||
occurs_check1(_, {qid, _, _}) -> false;
|
||||
occurs_check1(_, {qcon, _, _}) -> false;
|
||||
occurs_check1(_, {tvar, _, _}) -> false;
|
||||
occurs_check1(_, {bytes_t, _, _}) -> false;
|
||||
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
|
||||
occurs_check(R, [Res, Named | Args]);
|
||||
occurs_check1(R, {app_t, _, T, Ts}) ->
|
||||
@@ -1819,6 +1841,11 @@ pp_error({undefined_field, Id}) ->
|
||||
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
||||
pp_error({not_a_record_type, Type, Why}) ->
|
||||
io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]);
|
||||
pp_error({not_a_contract_type, Type, Lit}) ->
|
||||
io_lib:format("The type ~s is not a contract type\n"
|
||||
"when checking that the contract literal at ~s\n~s\n"
|
||||
"has the type\n~s\n",
|
||||
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]);
|
||||
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||
Plural = [ $s || length(Nonlinear) > 1 ],
|
||||
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||
@@ -2043,6 +2070,8 @@ pp({qid, _, Name}) ->
|
||||
string:join(Name, ".");
|
||||
pp({con, _, Name}) ->
|
||||
Name;
|
||||
pp({qcon, _, Name}) ->
|
||||
string:join(Name, ".");
|
||||
pp({uvar, _, Ref}) ->
|
||||
%% Show some unique representation
|
||||
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
||||
@@ -2050,6 +2079,8 @@ pp({tvar, _, Name}) ->
|
||||
Name;
|
||||
pp({tuple_t, _, Cpts}) ->
|
||||
["(", pp(Cpts), ")"];
|
||||
pp({bytes_t, _, Len}) ->
|
||||
["bytes(", integer_to_list(Len), ")"];
|
||||
pp({app_t, _, T, []}) ->
|
||||
pp(T);
|
||||
pp({app_t, _, Type, Args}) ->
|
||||
|
||||
@@ -346,6 +346,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
|
||||
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
||||
[word, word, sign_t()], word);
|
||||
|
||||
ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) ->
|
||||
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0},
|
||||
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
||||
[bytes_t(32), bytes_t(64), bytes_t(64)], word);
|
||||
|
||||
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
|
||||
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
|
||||
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
|
||||
@@ -416,14 +421,17 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
|
||||
#integer{value = Value};
|
||||
ast_body({int, _, Value}, _Icode) ->
|
||||
#integer{value = Value};
|
||||
ast_body({hash, _, Hash}, _Icode) ->
|
||||
case Hash of
|
||||
<<Value:32/unit:8>> -> %% address
|
||||
#integer{value = Value};
|
||||
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
|
||||
#tuple{cpts = [#integer{value = Hi},
|
||||
#integer{value = Lo}]}
|
||||
ast_body({bytes, _, Bin}, _Icode) ->
|
||||
case aeb_memory:binary_to_words(Bin) of
|
||||
[Word] -> #integer{value = Word};
|
||||
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
|
||||
end;
|
||||
ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey;
|
||||
Key == contract_pubkey;
|
||||
Key == oracle_pubkey;
|
||||
Key == oracle_query_id ->
|
||||
<<Value:32/unit:8>> = Bin,
|
||||
#integer{value = Value};
|
||||
ast_body({string,_,Bin}, _Icode) ->
|
||||
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
|
||||
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
||||
@@ -688,6 +696,8 @@ ast_typerep({qid, _, Name}, Icode) ->
|
||||
lookup_type_id(Name, [], Icode);
|
||||
ast_typerep({con, _, _}, _) ->
|
||||
word; %% Contract type
|
||||
ast_typerep({bytes_t, _, Len}, _) ->
|
||||
{bytes, Len};
|
||||
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
|
||||
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
|
||||
lookup_type_id(Name, ArgReps, Icode);
|
||||
@@ -715,8 +725,8 @@ ast_typerep({variant_t, Cons}, Icode) ->
|
||||
ttl_t(Icode) ->
|
||||
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
||||
|
||||
sign_t() ->
|
||||
{tuple, [word, word]}.
|
||||
sign_t() -> bytes_t(64).
|
||||
bytes_t(Len) -> {bytes, Len}.
|
||||
|
||||
get_signature_arg(Args0) ->
|
||||
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
|
||||
@@ -750,6 +760,8 @@ type_value({list, A}) ->
|
||||
type_value({tuple, As}) ->
|
||||
#tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG },
|
||||
#list{ elems = [ type_value(A) || A <- As ] }] };
|
||||
type_value({bytes, Len}) ->
|
||||
#tuple{ cpts = [#integer{ value = ?TYPEREP_BYTES_TAG }, #integer{ value = Len }] };
|
||||
type_value({variant, Cs}) ->
|
||||
#tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG },
|
||||
#list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] };
|
||||
|
||||
+18
-13
@@ -104,13 +104,12 @@ from_string(ContractString, Options) ->
|
||||
%% General programming errors in the compiler just signal error.
|
||||
end.
|
||||
|
||||
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
|
||||
string_to_icode(ContractString, Options0) ->
|
||||
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
|
||||
-spec string_to_icode(string(), [option()]) -> map().
|
||||
string_to_icode(ContractString, Options) ->
|
||||
Ast = parse(ContractString, Options),
|
||||
pp_sophia_code(Ast, Options),
|
||||
pp_ast(Ast, Options),
|
||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
|
||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
||||
pp_typed_ast(TypedAst, Options),
|
||||
Icode = ast_to_icode(TypedAst, Options),
|
||||
pp_icode(Icode, Options),
|
||||
@@ -151,11 +150,11 @@ check_call(Source, FunName, Args, Options) ->
|
||||
|
||||
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
|
||||
try
|
||||
%% First check the contract without the __call function and no permissive literals
|
||||
%% First check the contract without the __call function
|
||||
#{} = string_to_icode(ContractString0, Options),
|
||||
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
||||
#{typed_ast := TypedAst,
|
||||
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
|
||||
icode := Icode} = string_to_icode(ContractString, Options),
|
||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||
RetVMType = case RetType of
|
||||
@@ -257,19 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||
end.
|
||||
|
||||
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
|
||||
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||
|
||||
%% TODO: somewhere else
|
||||
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
||||
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N);
|
||||
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N);
|
||||
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
|
||||
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
|
||||
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
|
||||
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
||||
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({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||
{bytes, [], <<Val:Len/unit:8>>};
|
||||
translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) ->
|
||||
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
|
||||
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]};
|
||||
@@ -402,6 +403,10 @@ icode_to_term(word, {integer, N}) -> N;
|
||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||
Str;
|
||||
icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 ->
|
||||
Value;
|
||||
icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32->
|
||||
list_to_tuple([W || {integer, W} <- Words]);
|
||||
icode_to_term({list, T}, {list, Vs}) ->
|
||||
[ icode_to_term(T, V) || V <- Vs ];
|
||||
icode_to_term({tuple, Ts}, {tuple, Vs}) ->
|
||||
|
||||
+29
-5
@@ -136,11 +136,15 @@ type200() ->
|
||||
type300() -> type400().
|
||||
|
||||
type400() ->
|
||||
?RULE(typeAtom(), optional(type_args()),
|
||||
choice(
|
||||
[?RULE(typeAtom(), optional(type_args()),
|
||||
case _2 of
|
||||
none -> _1;
|
||||
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
||||
end).
|
||||
end),
|
||||
?RULE(id("bytes"), parens(token(int)),
|
||||
{bytes_t, get_ann(_1), element(3, _2)})
|
||||
]).
|
||||
|
||||
typeAtom() ->
|
||||
?LAZY_P(choice(
|
||||
@@ -203,8 +207,8 @@ exprAtom() ->
|
||||
?LAZY_P(begin
|
||||
Expr = ?LAZY_P(expr()),
|
||||
choice(
|
||||
[ id(), con(), token(qid), token(qcon)
|
||||
, token(hash), token(string), token(char)
|
||||
[ id_or_addr(), con(), token(qid), token(qcon)
|
||||
, token(bytes), token(string), token(char)
|
||||
, token(int)
|
||||
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
||||
, {bool, keyword(true), true}
|
||||
@@ -326,6 +330,26 @@ token(Tag) ->
|
||||
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
||||
end).
|
||||
|
||||
id(Id) ->
|
||||
?LET_P({id, A, X} = Y, id(),
|
||||
if X == Id -> Y;
|
||||
true -> fail({A, "expected 'bytes'"})
|
||||
end).
|
||||
|
||||
id_or_addr() ->
|
||||
?RULE(id(), parse_addr_literal(_1)).
|
||||
|
||||
parse_addr_literal(Id = {id, Ann, Name}) ->
|
||||
case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of
|
||||
false -> Id;
|
||||
true ->
|
||||
try aeser_api_encoder:decode(list_to_binary(Name)) of
|
||||
{Type, Bin} -> {Type, Ann, Bin}
|
||||
catch _:_ ->
|
||||
Id
|
||||
end
|
||||
end.
|
||||
|
||||
%% -- Helpers ----------------------------------------------------------------
|
||||
|
||||
keyword(K) -> ann(tok(K)).
|
||||
@@ -457,7 +481,7 @@ parse_pattern(E = {id, _, _}) -> E;
|
||||
parse_pattern(E = {unit, _}) -> E;
|
||||
parse_pattern(E = {int, _, _}) -> E;
|
||||
parse_pattern(E = {bool, _, _}) -> E;
|
||||
parse_pattern(E = {hash, _, _}) -> E;
|
||||
parse_pattern(E = {bytes, _, _}) -> E;
|
||||
parse_pattern(E = {string, _, _}) -> E;
|
||||
parse_pattern(E = {char, _, _}) -> E;
|
||||
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
||||
|
||||
+12
-1
@@ -236,6 +236,8 @@ type({app_t, _, Type, Args}) ->
|
||||
beside(type(Type), tuple_type(Args));
|
||||
type({tuple_t, _, Args}) ->
|
||||
tuple_type(Args);
|
||||
type({bytes_t, _, Len}) ->
|
||||
text(lists:concat(["bytes(", Len, ")"]));
|
||||
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||
%% Drop the default value
|
||||
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||
@@ -319,8 +321,17 @@ expr_p(_, E = {int, _, N}) ->
|
||||
end,
|
||||
text(S);
|
||||
expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
|
||||
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
|
||||
expr_p(_, {bytes, _, Bin}) ->
|
||||
Digits = byte_size(Bin),
|
||||
<<N:Digits/unit:8>> = Bin,
|
||||
text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N])));
|
||||
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
|
||||
expr_p(_, {Type, _, Bin})
|
||||
when Type == account_pubkey;
|
||||
Type == contract_pubkey;
|
||||
Type == oracle_pubkey;
|
||||
Type == oracle_query_id ->
|
||||
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
|
||||
expr_p(_, {unit, _}) -> text("()");
|
||||
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
|
||||
expr_p(_, {char, _, C}) ->
|
||||
|
||||
+5
-7
@@ -20,7 +20,7 @@ lexer() ->
|
||||
CON = [UPPER, "[a-zA-Z0-9_]*"],
|
||||
INT = [DIGIT, "+"],
|
||||
HEX = ["0x", HEXDIGIT, "+"],
|
||||
HASH = ["#", HEXDIGIT, "+"],
|
||||
BYTES = ["#", HEXDIGIT, "+"],
|
||||
WS = "[\\000-\\ ]+",
|
||||
ID = [LOWER, "[a-zA-Z0-9_']*"],
|
||||
TVAR = ["'", ID],
|
||||
@@ -54,7 +54,7 @@ lexer() ->
|
||||
, {STRING, token(string, fun parse_string/1)}
|
||||
, {HEX, token(hex, fun parse_hex/1)}
|
||||
, {INT, token(int, fun list_to_integer/1)}
|
||||
, {HASH, token(hash, fun parse_hash/1)}
|
||||
, {BYTES, token(bytes, fun parse_bytes/1)}
|
||||
|
||||
%% Identifiers (qualified first!)
|
||||
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
|
||||
@@ -117,10 +117,8 @@ unescape([C | Chars], Acc) ->
|
||||
|
||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||
|
||||
parse_hash("#" ++ Chars) ->
|
||||
parse_bytes("#" ++ Chars) ->
|
||||
N = list_to_integer(Chars, 16),
|
||||
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
|
||||
true -> <<N:64/unit:8>>; %% signature
|
||||
false -> <<N:32/unit:8>> %% address
|
||||
end.
|
||||
Digits = (length(Chars) + 1) div 2,
|
||||
<<N:Digits/unit:8>>.
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
||||
| {app_t, ann(), type(), [type()]}
|
||||
| {tuple_t, ann(), [type()]}
|
||||
| {bytes_t, ann(), integer()}
|
||||
| id() | qid()
|
||||
| con() | qcon() %% contracts
|
||||
| tvar().
|
||||
@@ -70,6 +71,10 @@
|
||||
:: {int, ann(), integer()}
|
||||
| {bool, ann(), true | false}
|
||||
| {hash, ann(), binary()}
|
||||
| {account_pubkey, binary()}
|
||||
| {contract_pubkey, binary()}
|
||||
| {oracle_pubkey, binary()}
|
||||
| {oracle_query_id, binary()}
|
||||
| {unit, ann()}
|
||||
| {string, ann(), binary()}
|
||||
| {char, ann(), integer()}.
|
||||
|
||||
+18
-3
@@ -4,6 +4,9 @@
|
||||
-compile(export_all).
|
||||
|
||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
||||
-define(DUMMY_HASH_WORD, 16#123).
|
||||
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
|
||||
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
|
||||
|
||||
sandbox(Code) ->
|
||||
Parent = self(),
|
||||
@@ -97,7 +100,15 @@ calldata_test() ->
|
||||
Map = #{ <<"a">> => 4 },
|
||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
||||
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
|
||||
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
||||
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
||||
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
|
||||
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
|
||||
|
||||
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
||||
|
||||
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
||||
|
||||
ok.
|
||||
|
||||
calldata_init_test() ->
|
||||
@@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) ->
|
||||
|
||||
parameterized_contract(ExtraCode, FunName, Types) ->
|
||||
lists:flatten(
|
||||
["contract Dummy =\n",
|
||||
["contract Remote =\n"
|
||||
" function bla : () => ()\n\n"
|
||||
"contract Dummy =\n",
|
||||
ExtraCode, "\n",
|
||||
" type an_alias('a) = (string, 'a)\n"
|
||||
" record r = {x : an_alias(int), y : variant}\n"
|
||||
@@ -139,7 +152,9 @@ oracle_test() ->
|
||||
" 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"], []),
|
||||
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||
"oq_1111111111111111111111111111113AFEFpt5"], []),
|
||||
|
||||
ok.
|
||||
|
||||
permissive_literals_fail_test() ->
|
||||
|
||||
@@ -24,7 +24,7 @@ test_cases(1) ->
|
||||
[#{<<"name">> => <<"a">>,
|
||||
<<"arguments">> =>
|
||||
[#{<<"name">> => <<"i">>,
|
||||
<<"type">> => [<<"int">>]}],
|
||||
<<"type">> => <<"int">>}],
|
||||
<<"returns">> => <<"int">>,
|
||||
<<"stateful">> => false}]}},
|
||||
DecACI = <<"contract C =\n"
|
||||
@@ -44,7 +44,7 @@ test_cases(2) ->
|
||||
<<"functions">> =>
|
||||
[#{<<"arguments">> =>
|
||||
[#{<<"name">> => <<"i">>,
|
||||
<<"type">> => [<<"int">>]}],
|
||||
<<"type">> => <<"int">>}],
|
||||
<<"name">> => <<"a">>,
|
||||
<<"returns">> => <<"int">>,
|
||||
<<"stateful">> => false}]}},
|
||||
@@ -60,7 +60,7 @@ test_cases(3) ->
|
||||
[#{<<"arguments">> =>
|
||||
[#{<<"name">> => <<"i">>,
|
||||
<<"type">> =>
|
||||
[#{<<"C.bert">> => [<<"string">>]}]}],
|
||||
#{<<"C.bert">> => [<<"string">>]}}],
|
||||
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
|
||||
<<"stateful">> => false}],
|
||||
<<"name">> => <<"C">>,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
-module(aeso_compiler_tests).
|
||||
|
||||
-compile([export_all, nowarn_export_all]).
|
||||
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
%% Very simply test compile the given contracts. Only basic checks
|
||||
@@ -16,10 +18,14 @@
|
||||
simple_compile_test_() ->
|
||||
[ {"Testing the " ++ ContractName ++ " contract",
|
||||
fun() ->
|
||||
case compile(ContractName) of
|
||||
#{byte_code := ByteCode,
|
||||
contract_source := _,
|
||||
type_info := _} = compile(ContractName),
|
||||
?assertMatch(Code when is_binary(Code), ByteCode)
|
||||
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
|
||||
ErrBin ->
|
||||
io:format("\n~s", [ErrBin]),
|
||||
error(ErrBin)
|
||||
end
|
||||
end} || ContractName <- compilable_contracts() ] ++
|
||||
[ {"Testing error messages of " ++ ContractName,
|
||||
fun() ->
|
||||
@@ -98,7 +104,9 @@ compilable_contracts() ->
|
||||
"state_handling",
|
||||
"events",
|
||||
"include",
|
||||
"basic_auth"
|
||||
"basic_auth",
|
||||
"bitcoin_auth",
|
||||
"address_literals"
|
||||
].
|
||||
|
||||
%% Contracts that should produce type errors
|
||||
@@ -218,4 +226,80 @@ failing_contracts() ->
|
||||
, {"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.">>]}
|
||||
, {"bad_address_literals",
|
||||
[<<"The type bytes(32) is not a contract type\n"
|
||||
"when checking that the contract literal at line 32, column 5\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" bytes(32)">>,
|
||||
<<"The type oracle(int, bool) is not a contract type\n"
|
||||
"when checking that the contract literal at line 30, column 5\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<"The type address is not a contract type\n"
|
||||
"when checking that the contract literal at line 28, column 5\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" address">>,
|
||||
<<"Cannot unify oracle_query('1, '2)\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 25, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('1, '2)\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<"Cannot unify oracle_query('3, '4)\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 23, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('3, '4)\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>,
|
||||
<<"Cannot unify oracle_query('5, '6)\n"
|
||||
" and oracle(int, bool)\n"
|
||||
"when checking the type of the expression at line 21, column 5\n"
|
||||
" oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n"
|
||||
" oracle_query('5, '6)\n"
|
||||
"against the expected type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<"Cannot unify oracle('7, '8)\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 18, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('7, '8)\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<"Cannot unify oracle('9, '10)\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 16, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('9, '10)\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>,
|
||||
<<"Cannot unify oracle('11, '12)\n"
|
||||
" and oracle_query(int, bool)\n"
|
||||
"when checking the type of the expression at line 14, column 5\n"
|
||||
" ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n"
|
||||
" oracle('11, '12)\n"
|
||||
"against the expected type\n"
|
||||
" oracle_query(int, bool)">>,
|
||||
<<"Cannot unify address\n"
|
||||
" and oracle(int, bool)\n"
|
||||
"when checking the type of the expression at line 11, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<"Cannot unify address\n"
|
||||
" and Remote\n"
|
||||
"when checking the type of the expression at line 9, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" Remote">>,
|
||||
<<"Cannot unify address\n"
|
||||
" and bytes(32)\n"
|
||||
"when checking the type of the expression at line 7, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>]}
|
||||
].
|
||||
|
||||
@@ -48,7 +48,7 @@ all_tokens() ->
|
||||
%% Literals
|
||||
[ Lit(true), Lit(false)
|
||||
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
||||
, Tok(hash, Hash)
|
||||
, Tok(bytes, Hash)
|
||||
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
||||
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
|
||||
].
|
||||
@@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P;
|
||||
show_token({string, _, S}) -> fmt(binary_to_list(S));
|
||||
show_token({int, _, N}) -> fmt(N);
|
||||
show_token({hex, _, N}) -> fmt("0x~.16b", N);
|
||||
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
|
||||
show_token({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
|
||||
show_token({comment, _, S}) -> S;
|
||||
show_token({_, _, _}) -> "TODO".
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
contract Remote =
|
||||
function foo : () => ()
|
||||
|
||||
contract AddressLiterals =
|
||||
function addr() : address =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
function oracle() : oracle(int, bool) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
function query() : oracle_query(int, bool) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
function contr() : Remote =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
contract Remote =
|
||||
function foo : () => ()
|
||||
|
||||
contract AddressLiterals =
|
||||
function addr1() : bytes(32) =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
function addr2() : Remote =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
function addr3() : oracle(int, bool) =
|
||||
ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
|
||||
function oracle1() : oracle_query(int, bool) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
function oracle2() : bytes(32) =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
function oracle3() : Remote =
|
||||
ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
|
||||
function query1() : oracle(int, bool) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
function query2() : bytes(32) =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
function query3() : Remote =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
|
||||
function contr1() : address =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
function contr2() : oracle(int, bool) =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
function contr3() : bytes(32) =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
contract BitcoinAuth =
|
||||
record state = { nonce : int, owner : bytes(64) }
|
||||
|
||||
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
|
||||
|
||||
function authorize(n : int, s : signature) : bool =
|
||||
require(n >= state.nonce, "Nonce too low")
|
||||
require(n =< state.nonce, "Nonce too high")
|
||||
put(state{ nonce = n + 1 })
|
||||
switch(Auth.tx_hash)
|
||||
None => abort("Not in Auth context")
|
||||
Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s)
|
||||
|
||||
function to_sign(h : hash, n : int) : hash =
|
||||
Crypto.blake2b((h, n))
|
||||
|
||||
private function require(b : bool, err : string) =
|
||||
if(!b) abort(err)
|
||||
@@ -53,7 +53,7 @@ contract FundMe =
|
||||
require(state.total >= state.goal, "Project was not funded")
|
||||
spend({recipient = state.beneficiary,
|
||||
amount = Contract.balance })
|
||||
put(state{ beneficiary = #0 })
|
||||
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
|
||||
|
||||
private stateful function withdraw_contributor() =
|
||||
if(state.total >= state.goal)
|
||||
|
||||
Reference in New Issue
Block a user