Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dcae96ed21 | |||
| 94689dd0e9 | |||
| be7c0e1bd4 | |||
| 8eaafa736c | |||
| cf5a8aeb5f | |||
| 9e555a3121 | |||
| d051fa6c89 | |||
| d4238c0bdc | |||
| 389072fb12 | |||
| 1760593170 | |||
| d8dd6b900f | |||
| 74d4048d9f | |||
| 6bd2b7c483 | |||
| 5aed8b3ef5 | |||
| e1a798aef4 | |||
| 23cc8e1132 | |||
| 691ae72fbb | |||
| 8830095c7e | |||
| 251b876495 | |||
| f8ee8f7129 | |||
| 192ec207a7 | |||
| 0aa1c89556 | |||
| 71b97cba62 | |||
| 8a381e5ef1 | |||
| 386419f112 | |||
| 45a62f0807 | |||
| 3255c62e0e | |||
| 51b63f9559 | |||
| 5e6af18c7b | |||
| 4324bfd49e | |||
| faa0ef9772 | |||
| f07954f62c |
+22
-1
@@ -9,6 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
## [3.0.0] - 2019-05-21
|
||||||
|
### Added
|
||||||
|
- `stateful` annotations are now properly enforced. Functions must be marked stateful
|
||||||
|
in order to update the state or spend tokens.
|
||||||
|
- Primitives `Contract.creator`, `Address.is_contract`, `Address.is_oracle`,
|
||||||
|
`Oracle.check` and `Oracle.check_query` has been added to Sophia.
|
||||||
|
- A byte array type `bytes(N)` has been added to generalize `hash (== bytes(32))` and
|
||||||
|
`signature (== bytes(64))` and allow for byte arrays of arbitrary fixed length.
|
||||||
|
- `Crypto.ecverify_secp256k1` has been added.
|
||||||
|
### Changed
|
||||||
|
- Address literals (+ Oracle, Oracle query and remote contracts) have been changed
|
||||||
|
from `#<hex>` to address as `ak_<base58check>`, oracle `ok_<base58check>`,
|
||||||
|
oracle query `oq_<base58check>` and remote contract `ct_<base58check>`.
|
||||||
|
- The compilation and typechecking of `letfun` (e.g. `let m(f, xs) = map(f, xs)`) was
|
||||||
|
not working properly and has been fixed.
|
||||||
|
### Removed
|
||||||
|
- `let rec` has been removed from the language, it has never worked.
|
||||||
|
- The standalone CLI compiler is served in the repo `aeternity/aesophia_cli` and has
|
||||||
|
been completely removed from `aesophia`.
|
||||||
|
|
||||||
## [2.1.0] - 2019-04-11
|
## [2.1.0] - 2019-04-11
|
||||||
### Added
|
### Added
|
||||||
- Stubs (not yet wired up) for compilation to FATE
|
- Stubs (not yet wired up) for compilation to FATE
|
||||||
@@ -35,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||||
pass a (stubbed) contract string.
|
pass a (stubbed) contract string.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v2.1.0...HEAD
|
[Unreleased]: https://github.com/aeternity/aesophia/compare/v3.0.0...HEAD
|
||||||
|
[3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0
|
||||||
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
|
[2.1.0]: https://github.com/aeternity/aesophia/compare/v2.0.0...v2.1.0
|
||||||
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
|
[2.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
|
||||||
|
|||||||
+2
-17
@@ -3,34 +3,19 @@
|
|||||||
{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, "28f6c42"}}}
|
{ref, "2f4e188"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
{tag, "2.8.0"}}}
|
{tag, "2.8.0"}}}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
|
|
||||||
{escript_main_app, aesophia}.
|
|
||||||
{escript_name, aesophia}.
|
|
||||||
{escript_emu_args, "%%! \n"}.
|
|
||||||
{provider_hooks, [{post, [{compile, escriptize}]}]}.
|
|
||||||
|
|
||||||
{post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)",
|
|
||||||
escriptize,
|
|
||||||
"cp \"$REBAR_BUILD_DIR/bin/aesophia\" ./aesophia"},
|
|
||||||
{"win32",
|
|
||||||
escriptize,
|
|
||||||
"robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aesophia* "
|
|
||||||
"/njs /njh /nfl /ndl & exit /b 0"} % silence things
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{dialyzer, [
|
{dialyzer, [
|
||||||
{warnings, [unknown]},
|
{warnings, [unknown]},
|
||||||
{plt_apps, all_deps},
|
{plt_apps, all_deps},
|
||||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{relx, [{release, {aesophia, "2.1.0"},
|
{relx, [{release, {aesophia, "3.0.0"},
|
||||||
[aesophia, aebytecode, getopt]},
|
[aesophia, aebytecode, getopt]},
|
||||||
|
|
||||||
{dev_mode, true},
|
{dev_mode, true},
|
||||||
|
|||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
{"1.1.0",
|
{"1.1.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
|
{ref,"2f4e1888c241a7347ffec855ab6761c2c2972f37"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
|
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
|
||||||
1},
|
1},
|
||||||
{<<"base58">>,
|
{<<"base58">>,
|
||||||
{git,"https://github.com/aeternity/erl-base58.git",
|
{git,"https://github.com/aeternity/erl-base58.git",
|
||||||
|
|||||||
+5
-2
@@ -24,6 +24,7 @@
|
|||||||
%% Types
|
%% Types
|
||||||
-record(app_t, {ann,id,fields}).
|
-record(app_t, {ann,id,fields}).
|
||||||
-record(tuple_t, {ann,args}).
|
-record(tuple_t, {ann,args}).
|
||||||
|
-record(bytes_t, {ann,len}).
|
||||||
-record(record_t, {fields}).
|
-record(record_t, {fields}).
|
||||||
-record(field_t, {ann,id,type}).
|
-record(field_t, {ann,id,type}).
|
||||||
-record(alias_t, {type}).
|
-record(alias_t, {type}).
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
-record(bool, {ann,bool}).
|
-record(bool, {ann,bool}).
|
||||||
-record(int, {ann,value}).
|
-record(int, {ann,value}).
|
||||||
-record(string, {ann,bin}).
|
-record(string, {ann,bin}).
|
||||||
-record(hash, {ann,hash}).
|
-record(bytes, {ann,bin}).
|
||||||
-record(tuple, {ann,args}).
|
-record(tuple, {ann,args}).
|
||||||
-record(list, {ann,args}).
|
-record(list, {ann,args}).
|
||||||
-record(app, {ann,func,args}).
|
-record(app, {ann,func,args}).
|
||||||
@@ -133,6 +134,8 @@ encode_type(#qcon{names=Ns}) ->
|
|||||||
encode_type(#tuple_t{args=As}) ->
|
encode_type(#tuple_t{args=As}) ->
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
[{<<"tuple">>,Eas}];
|
[{<<"tuple">>,Eas}];
|
||||||
|
encode_type(#bytes_t{len=Len}) ->
|
||||||
|
{<<"bytes">>, Len};
|
||||||
encode_type(#record_t{fields=Fs}) ->
|
encode_type(#record_t{fields=Fs}) ->
|
||||||
Efs = encode_fields(Fs),
|
Efs = encode_fields(Fs),
|
||||||
[{<<"record">>,Efs}];
|
[{<<"record">>,Efs}];
|
||||||
@@ -208,7 +211,7 @@ encode_expr(#typed{expr=E}) ->
|
|||||||
encode_expr(#bool{bool=B}) -> B;
|
encode_expr(#bool{bool=B}) -> B;
|
||||||
encode_expr(#int{value=V}) -> V;
|
encode_expr(#int{value=V}) -> V;
|
||||||
encode_expr(#string{bin=B}) -> B;
|
encode_expr(#string{bin=B}) -> B;
|
||||||
encode_expr(#hash{hash=H}) -> H;
|
encode_expr(#bytes{bin=B}) -> B;
|
||||||
encode_expr(#tuple{args=As}) ->
|
encode_expr(#tuple{args=As}) ->
|
||||||
Eas = encode_exprs(As),
|
Eas = encode_exprs(As),
|
||||||
[{<<"tuple">>,Eas}];
|
[{<<"tuple">>,Eas}];
|
||||||
|
|||||||
+188
-64
@@ -56,7 +56,12 @@
|
|||||||
, fields :: [aeso_syntax:id()]
|
, fields :: [aeso_syntax:id()]
|
||||||
, context :: why_record() }).
|
, 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,
|
-record(field_info,
|
||||||
{ ann :: aeso_syntax:ann()
|
{ ann :: aeso_syntax:ann()
|
||||||
@@ -93,11 +98,14 @@
|
|||||||
-type scope() :: #scope{}.
|
-type scope() :: #scope{}.
|
||||||
|
|
||||||
-record(env,
|
-record(env,
|
||||||
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
{ scopes = #{ [] => #scope{}} :: #{ qname() => scope() }
|
||||||
, vars = [] :: [{name(), var_info()}]
|
, vars = [] :: [{name(), var_info()}]
|
||||||
, typevars = unrestricted :: unrestricted | [name()]
|
, typevars = unrestricted :: unrestricted | [name()]
|
||||||
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
, fields = #{} :: #{ name() => [field_info()] } %% fields are global
|
||||||
, namespace = [] :: qname()
|
, namespace = [] :: qname()
|
||||||
|
, in_pattern = false :: boolean()
|
||||||
|
, stateful = false :: boolean()
|
||||||
|
, current_function = none :: none | aeso_syntax:id()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type env() :: #env{}.
|
-type env() :: #env{}.
|
||||||
@@ -191,7 +199,7 @@ bind_state(Env) ->
|
|||||||
false -> {id, Ann, "event"} %% will cause type error if used(?)
|
false -> {id, Ann, "event"} %% will cause type error if used(?)
|
||||||
end,
|
end,
|
||||||
Env1 = bind_funs([{"state", State},
|
Env1 = bind_funs([{"state", State},
|
||||||
{"put", {fun_t, Ann, [], [State], Unit}}], Env),
|
{"put", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
|
||||||
|
|
||||||
%% We bind Chain.event in a local 'Chain' namespace.
|
%% We bind Chain.event in a local 'Chain' namespace.
|
||||||
pop_scope(
|
pop_scope(
|
||||||
@@ -341,6 +349,7 @@ global_env() ->
|
|||||||
Address = {id, Ann, "address"},
|
Address = {id, Ann, "address"},
|
||||||
Hash = {id, Ann, "hash"},
|
Hash = {id, Ann, "hash"},
|
||||||
Bits = {id, Ann, "bits"},
|
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,
|
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,
|
Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end,
|
||||||
Unit = {tuple_t, Ann, []},
|
Unit = {tuple_t, Ann, []},
|
||||||
@@ -350,11 +359,12 @@ global_env() ->
|
|||||||
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end,
|
||||||
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
|
Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end,
|
||||||
Fun1 = fun(S, T) -> Fun([S], T) end,
|
Fun1 = fun(S, T) -> Fun([S], T) end,
|
||||||
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
|
||||||
|
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
||||||
SignId = {id, Ann, "signature"},
|
SignId = {id, Ann, "signature"},
|
||||||
SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
|
SignDef = {bytes, Ann, <<0:64/unit:8>>},
|
||||||
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}},
|
||||||
SignFun = fun(Ts, T) -> {type_sig, Ann, [Signature], Ts, T} end,
|
SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [Signature], Ts, T} end,
|
||||||
TTL = {qid, Ann, ["Chain", "ttl"]},
|
TTL = {qid, Ann, ["Chain", "ttl"]},
|
||||||
Fee = Int,
|
Fee = Int,
|
||||||
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
[A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]),
|
||||||
@@ -372,8 +382,10 @@ global_env() ->
|
|||||||
%% Abort
|
%% Abort
|
||||||
{"abort", Fun1(String, A)}])
|
{"abort", Fun1(String, A)}])
|
||||||
, types = MkDefs(
|
, types = MkDefs(
|
||||||
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
|
[{"int", 0}, {"bool", 0}, {"char", 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},
|
{"option", 1}, {"list", 1}, {"map", 2},
|
||||||
{"oracle", 2}, {"oracle_query", 2}
|
{"oracle", 2}, {"oracle_query", 2}
|
||||||
]) },
|
]) },
|
||||||
@@ -381,7 +393,7 @@ global_env() ->
|
|||||||
ChainScope = #scope
|
ChainScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
%% Spend transaction.
|
%% Spend transaction.
|
||||||
[{"spend", Fun([Address, Int], Unit)},
|
[{"spend", StateFun([Address, Int], Unit)},
|
||||||
%% Chain environment
|
%% Chain environment
|
||||||
{"balance", Fun1(Address, Int)},
|
{"balance", Fun1(Address, Int)},
|
||||||
{"block_hash", Fun1(Int, Int)},
|
{"block_hash", Fun1(Int, Int)},
|
||||||
@@ -395,7 +407,7 @@ global_env() ->
|
|||||||
ContractScope = #scope
|
ContractScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"address", Address},
|
[{"address", Address},
|
||||||
%% {"owner", Int}, %% Not in EVM
|
{"creator", Address},
|
||||||
{"balance", Int}]) },
|
{"balance", Int}]) },
|
||||||
|
|
||||||
CallScope = #scope
|
CallScope = #scope
|
||||||
@@ -411,11 +423,13 @@ global_env() ->
|
|||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
|
[{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))},
|
||||||
{"query_fee", Fun([Oracle(Q, R)], Fee)},
|
{"query_fee", Fun([Oracle(Q, R)], Fee)},
|
||||||
{"query", Fun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
{"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
||||||
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
|
{"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)},
|
||||||
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
|
{"respond", SignFun([Oracle(Q, R), Query(Q, R), R], Unit)},
|
||||||
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
|
{"extend", SignFun([Oracle(Q, R), TTL], Unit)},
|
||||||
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))}]) },
|
{"get_answer", Fun([Oracle(Q, R), Query(Q, R)], option_t(Ann, R))},
|
||||||
|
{"check", Fun([Oracle(Q, R)], Bool)},
|
||||||
|
{"check_query", Fun([Oracle(Q,R), Query(Q, R)], Bool)}]) },
|
||||||
|
|
||||||
AENSScope = #scope
|
AENSScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
@@ -439,6 +453,7 @@ global_env() ->
|
|||||||
CryptoScope = #scope
|
CryptoScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
|
[{"ecverify", Fun([Hash, Address, SignId], Bool)},
|
||||||
|
{"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)},
|
||||||
{"sha3", Fun1(A, Hash)},
|
{"sha3", Fun1(A, Hash)},
|
||||||
{"sha256", Fun1(A, Hash)},
|
{"sha256", Fun1(A, Hash)},
|
||||||
{"blake2b", Fun1(A, Hash)}]) },
|
{"blake2b", Fun1(A, Hash)}]) },
|
||||||
@@ -472,7 +487,9 @@ global_env() ->
|
|||||||
|
|
||||||
%% Conversion
|
%% Conversion
|
||||||
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
||||||
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}]) },
|
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
|
||||||
|
{"is_oracle", Fun1(Address, Bool)},
|
||||||
|
{"is_contract", Fun1(Address, Bool)}]) },
|
||||||
|
|
||||||
#env{ scopes =
|
#env{ scopes =
|
||||||
#{ [] => TopScope
|
#{ [] => TopScope
|
||||||
@@ -497,7 +514,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
|||||||
infer(Contracts) ->
|
infer(Contracts) ->
|
||||||
infer(Contracts, []).
|
infer(Contracts, []).
|
||||||
|
|
||||||
-type option() :: permissive_address_literals | return_env.
|
-type option() :: return_env.
|
||||||
|
|
||||||
-spec init_env(list(option())) -> env().
|
-spec init_env(list(option())) -> env().
|
||||||
init_env(_Options) -> global_env().
|
init_env(_Options) -> global_env().
|
||||||
@@ -588,6 +605,8 @@ infer_contract(Env, What, Defs) ->
|
|||||||
SCCs = aeso_utils:scc(DepGraph),
|
SCCs = aeso_utils:scc(DepGraph),
|
||||||
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
%% io:format("Dependency sorted functions:\n ~p\n", [SCCs]),
|
||||||
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
{Env4, Defs1} = check_sccs(Env3, FunMap, SCCs, []),
|
||||||
|
%% Check that `init` doesn't read or write the state
|
||||||
|
check_state_dependencies(Env4, Defs1),
|
||||||
destroy_and_report_type_errors(Env4),
|
destroy_and_report_type_errors(Env4),
|
||||||
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
{Env4, TypeDefs ++ Decls ++ Defs1}.
|
||||||
|
|
||||||
@@ -675,6 +694,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id;
|
|||||||
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
|
check_type(Env, Type = {tuple_t, Ann, Types}, Arity) ->
|
||||||
ensure_base_type(Type, Arity),
|
ensure_base_type(Type, Arity),
|
||||||
{tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]};
|
{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) ->
|
check_type(Env, {app_t, Ann, Type, Types}, Arity) ->
|
||||||
Types1 = [ check_type(Env, T, 0) || T <- Types ],
|
Types1 = [ check_type(Env, T, 0) || T <- Types ],
|
||||||
Type1 = check_type(Env, Type, Arity + length(Types)),
|
Type1 = check_type(Env, Type, Arity + length(Types)),
|
||||||
@@ -781,7 +803,7 @@ check_sccs(Env = #env{}, Funs, [{acyclic, X} | SCCs], Acc) ->
|
|||||||
end;
|
end;
|
||||||
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
|
check_sccs(Env = #env{}, Funs, [{cyclic, Xs} | SCCs], Acc) ->
|
||||||
Defs = [ maps:get(X, Funs) || X <- Xs ],
|
Defs = [ maps:get(X, Funs) || X <- Xs ],
|
||||||
{TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], Defs}),
|
{TypeSigs, Defs1} = infer_letrec(Env, Defs),
|
||||||
Env1 = bind_funs(TypeSigs, Env),
|
Env1 = bind_funs(TypeSigs, Env),
|
||||||
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
|
check_sccs(Env1, Funs, SCCs, Defs1 ++ Acc).
|
||||||
|
|
||||||
@@ -823,7 +845,7 @@ check_special_funs(_, _) -> ok.
|
|||||||
|
|
||||||
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
|
typesig_to_fun_t({type_sig, Ann, Named, Args, Res}) -> {fun_t, Ann, Named, Args, Res}.
|
||||||
|
|
||||||
infer_letrec(Env, {letrec, Attrs, Defs}) ->
|
infer_letrec(Env, Defs) ->
|
||||||
create_constraints(),
|
create_constraints(),
|
||||||
Funs = [{Name, fresh_uvar(A)}
|
Funs = [{Name, fresh_uvar(A)}
|
||||||
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
|
|| {letfun, _, {id, A, Name}, _, _, _} <- Defs],
|
||||||
@@ -843,9 +865,12 @@ infer_letrec(Env, {letrec, Attrs, Defs}) ->
|
|||||||
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
|
TypeSigs = instantiate([Sig || {Sig, _} <- Inferred]),
|
||||||
NewDefs = instantiate([D || {_, D} <- Inferred]),
|
NewDefs = instantiate([D || {_, D} <- Inferred]),
|
||||||
[print_typesig(S) || S <- TypeSigs],
|
[print_typesig(S) || S <- TypeSigs],
|
||||||
{TypeSigs, {letrec, Attrs, NewDefs}}.
|
{TypeSigs, NewDefs}.
|
||||||
|
|
||||||
infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
|
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
|
||||||
|
Env = Env0#env{ stateful = aeso_syntax:get_ann(stateful, Attrib, false),
|
||||||
|
current_function = Fun },
|
||||||
|
check_unique_arg_names(Fun, Args),
|
||||||
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
|
ArgTypes = [{ArgName, check_type(Env, arg_type(T))} || {arg, _, ArgName, T} <- Args],
|
||||||
ExpectedType = check_type(Env, arg_type(What)),
|
ExpectedType = check_type(Env, arg_type(What)),
|
||||||
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
|
NewBody={typed, _, _, ResultType} = check_expr(bind_vars(ArgTypes, Env), Body, ExpectedType),
|
||||||
@@ -856,6 +881,13 @@ infer_letfun(Env, {letfun, Attrib, {id, NameAttrib, Name}, Args, What, Body}) ->
|
|||||||
{{Name, TypeSig},
|
{{Name, TypeSig},
|
||||||
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
|
{letfun, Attrib, {id, NameAttrib, Name}, NewArgs, ResultType, NewBody}}.
|
||||||
|
|
||||||
|
check_unique_arg_names(Fun, Args) ->
|
||||||
|
Name = fun({arg, _, {id, _, X}, _}) -> X end,
|
||||||
|
Names = lists:map(Name, Args),
|
||||||
|
Dups = lists:usort(Names -- lists:usort(Names)),
|
||||||
|
[ type_error({repeated_arg, Fun, Arg}) || Arg <- Dups ],
|
||||||
|
ok.
|
||||||
|
|
||||||
print_typesig({Name, TypeSig}) ->
|
print_typesig({Name, TypeSig}) ->
|
||||||
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
|
?PRINT_TYPES("Inferred ~s : ~s\n", [Name, pp(TypeSig)]).
|
||||||
|
|
||||||
@@ -879,6 +911,7 @@ lookup_name(Env, As, Id, Options) ->
|
|||||||
{Id, fresh_uvar(As)};
|
{Id, fresh_uvar(As)};
|
||||||
{QId, {_, Ty}} ->
|
{QId, {_, Ty}} ->
|
||||||
Freshen = proplists:get_value(freshen, Options, false),
|
Freshen = proplists:get_value(freshen, Options, false),
|
||||||
|
check_stateful(Env, Id, Ty),
|
||||||
Ty1 = case Ty of
|
Ty1 = case Ty of
|
||||||
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
|
{type_sig, _, _, _, _} -> freshen_type(typesig_to_fun_t(Ty));
|
||||||
_ when Freshen -> freshen_type(Ty);
|
_ when Freshen -> freshen_type(Ty);
|
||||||
@@ -887,6 +920,61 @@ lookup_name(Env, As, Id, Options) ->
|
|||||||
{set_qname(QId, Id), Ty1}
|
{set_qname(QId, Id), Ty1}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
check_stateful(#env{ stateful = false, current_function = Fun }, Id, Type = {type_sig, _, _, _, _}) ->
|
||||||
|
case aeso_syntax:get_ann(stateful, Type, false) of
|
||||||
|
false -> ok;
|
||||||
|
true ->
|
||||||
|
type_error({stateful_not_allowed, Id, Fun})
|
||||||
|
end;
|
||||||
|
check_stateful(_Env, _Id, _Type) -> ok.
|
||||||
|
|
||||||
|
%% Hack: don't allow passing the 'value' named arg if not stateful. This only
|
||||||
|
%% works since the user can't create functions with named arguments.
|
||||||
|
check_stateful_named_arg(#env{ stateful = false, current_function = Fun }, {id, _, "value"}, Default) ->
|
||||||
|
case Default of
|
||||||
|
{int, _, 0} -> ok;
|
||||||
|
_ -> type_error({value_arg_not_allowed, Default, Fun})
|
||||||
|
end;
|
||||||
|
check_stateful_named_arg(_, _, _) -> ok.
|
||||||
|
|
||||||
|
%% Check that `init` doesn't read or write the state
|
||||||
|
check_state_dependencies(Env, Defs) ->
|
||||||
|
Top = Env#env.namespace,
|
||||||
|
GetState = Top ++ ["state"],
|
||||||
|
SetState = Top ++ ["put"],
|
||||||
|
Init = Top ++ ["init"],
|
||||||
|
UsedNames = fun(X) -> [{Xs, Ann} || {{term, Xs}, Ann} <- aeso_syntax_utils:used(X)] end,
|
||||||
|
Funs = [ {Top ++ [Name], Fun} || Fun = {letfun, _, {id, _, Name}, _Args, _Type, _Body} <- Defs ],
|
||||||
|
Deps = maps:from_list([{Name, UsedNames(Def)} || {Name, Def} <- Funs]),
|
||||||
|
case maps:get(Init, Deps, false) of
|
||||||
|
false -> ok; %% No init, so nothing to check
|
||||||
|
_ ->
|
||||||
|
[ type_error({init_depends_on_state, state, Chain})
|
||||||
|
|| Chain <- get_call_chains(Deps, Init, GetState) ],
|
||||||
|
[ type_error({init_depends_on_state, put, Chain})
|
||||||
|
|| Chain <- get_call_chains(Deps, Init, SetState) ],
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Compute all paths (not sharing intermediate nodes) from Start to Stop in Graph.
|
||||||
|
get_call_chains(Graph, Start, Stop) ->
|
||||||
|
get_call_chains(Graph, #{}, queue:from_list([{Start, [], []}]), Stop, []).
|
||||||
|
|
||||||
|
get_call_chains(Graph, Visited, Queue, Stop, Acc) ->
|
||||||
|
case queue:out(Queue) of
|
||||||
|
{empty, _} -> lists:reverse(Acc);
|
||||||
|
{{value, {Stop, Ann, Path}}, Queue1} ->
|
||||||
|
get_call_chains(Graph, Visited, Queue1, Stop, [lists:reverse([{Stop, Ann} | Path]) | Acc]);
|
||||||
|
{{value, {Node, Ann, Path}}, Queue1} ->
|
||||||
|
case maps:is_key(Node, Visited) of
|
||||||
|
true -> get_call_chains(Graph, Visited, Queue1, Stop, Acc);
|
||||||
|
false ->
|
||||||
|
Calls = maps:get(Node, Graph, []),
|
||||||
|
NewQ = queue:from_list([{New, Ann1, [{Node, Ann} | Path]} || {New, Ann1} <- Calls]),
|
||||||
|
get_call_chains(Graph, Visited#{Node => true}, queue:join(Queue1, NewQ), Stop, Acc)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
check_expr(Env, Expr, Type) ->
|
check_expr(Env, Expr, Type) ->
|
||||||
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
|
E = {typed, _, _, Type1} = infer_expr(Env, Expr),
|
||||||
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
|
unify(Env, Type1, Type, {check_expr, Expr, Type1, Type}),
|
||||||
@@ -896,26 +984,35 @@ infer_expr(_Env, Body={bool, As, _}) ->
|
|||||||
{typed, As, Body, {id, As, "bool"}};
|
{typed, As, Body, {id, As, "bool"}};
|
||||||
infer_expr(_Env, Body={int, As, _}) ->
|
infer_expr(_Env, Body={int, As, _}) ->
|
||||||
{typed, As, Body, {id, As, "int"}};
|
{typed, As, Body, {id, As, "int"}};
|
||||||
|
infer_expr(_Env, Body={char, As, _}) ->
|
||||||
|
{typed, As, Body, {id, As, "char"}};
|
||||||
infer_expr(_Env, Body={string, As, _}) ->
|
infer_expr(_Env, Body={string, As, _}) ->
|
||||||
{typed, As, Body, {id, As, "string"}};
|
{typed, As, Body, {id, As, "string"}};
|
||||||
infer_expr(_Env, Body={hash, As, Hash}) ->
|
infer_expr(_Env, Body={bytes, As, Bin}) ->
|
||||||
case byte_size(Hash) of
|
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
|
||||||
32 -> {typed, As, Body, {id, As, "address"}};
|
infer_expr(_Env, Body={account_pubkey, As, _}) ->
|
||||||
64 -> {typed, As, Body, {id, As, "signature"}}
|
{typed, As, Body, {id, As, "address"}};
|
||||||
end;
|
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, "_"}) ->
|
infer_expr(_Env, Body={id, As, "_"}) ->
|
||||||
{typed, As, Body, fresh_uvar(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),
|
{QName, Type} = lookup_name(Env, As, Id),
|
||||||
{typed, As, QName, Type};
|
{typed, As, QName, Type};
|
||||||
infer_expr(Env, Id = {qid, As, _}) ->
|
infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon ->
|
||||||
{QName, Type} = lookup_name(Env, As, Id),
|
|
||||||
{typed, As, QName, Type};
|
|
||||||
infer_expr(Env, Id = {con, As, _}) ->
|
|
||||||
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
|
{QName, Type} = lookup_name(Env, As, Id, [freshen]),
|
||||||
{typed, As, QName, Type};
|
{typed, As, QName, Type};
|
||||||
infer_expr(Env, {unit, As}) ->
|
|
||||||
infer_expr(Env, {tuple, As, []});
|
|
||||||
infer_expr(Env, {tuple, As, Cpts}) ->
|
infer_expr(Env, {tuple, As, Cpts}) ->
|
||||||
NewCpts = [infer_expr(Env, C) || C <- Cpts],
|
NewCpts = [infer_expr(Env, C) || C <- Cpts],
|
||||||
CptTypes = [T || {typed, _, _, T} <- NewCpts],
|
CptTypes = [T || {typed, _, _, T} <- NewCpts],
|
||||||
@@ -969,7 +1066,7 @@ infer_expr(Env, {record, Attrs, Fields}) ->
|
|||||||
constrain([ #record_create_constraint{
|
constrain([ #record_create_constraint{
|
||||||
record_t = RecordType1,
|
record_t = RecordType1,
|
||||||
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
|
fields = [ FieldName || {field, _, [{proj, _, FieldName}], _} <- Fields ],
|
||||||
context = Attrs } ] ++
|
context = Attrs } || not Env#env.in_pattern ] ++
|
||||||
[begin
|
[begin
|
||||||
[{proj, _, FieldName}] = LV,
|
[{proj, _, FieldName}] = LV,
|
||||||
#field_constraint{
|
#field_constraint{
|
||||||
@@ -1038,6 +1135,7 @@ infer_expr(Env, {lam, Attrs, Args, Body}) ->
|
|||||||
|
|
||||||
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
infer_named_arg(Env, NamedArgs, {named_arg, Ann, Id, E}) ->
|
||||||
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
CheckedExpr = {typed, _, _, ArgType} = infer_expr(Env, E),
|
||||||
|
check_stateful_named_arg(Env, Id, E),
|
||||||
add_named_argument_constraint(
|
add_named_argument_constraint(
|
||||||
#named_argument_constraint{
|
#named_argument_constraint{
|
||||||
args = NamedArgs,
|
args = NamedArgs,
|
||||||
@@ -1098,7 +1196,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
|||||||
[] -> ok;
|
[] -> ok;
|
||||||
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
|
Nonlinear -> type_error({non_linear_pattern, Pattern, lists:usort(Nonlinear)})
|
||||||
end,
|
end,
|
||||||
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
|
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }),
|
||||||
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
|
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
|
||||||
NewBranch = check_expr(NewEnv, Branch, SwitchType),
|
NewBranch = check_expr(NewEnv, Branch, SwitchType),
|
||||||
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
|
||||||
@@ -1107,12 +1205,11 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
|
|||||||
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
%% NewStmts = infer_block(Env, Attrs, Stmts, BlockType)
|
||||||
infer_block(_Env, Attrs, [], BlockType) ->
|
infer_block(_Env, Attrs, [], BlockType) ->
|
||||||
error({impossible, empty_block, Attrs, BlockType});
|
error({impossible, empty_block, Attrs, BlockType});
|
||||||
infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
|
infer_block(Env, Attrs, [Def={letfun, Ann, _, _, _, _}|Rest], BlockType) ->
|
||||||
NewDef = infer_letfun(Env, Def),
|
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
|
||||||
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
|
FunT = freshen_type(typesig_to_fun_t(TypeSig)),
|
||||||
infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
|
NewE = bind_var({id, Ann, Name}, FunT, Env),
|
||||||
NewDef = infer_letrec(Env, Def),
|
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
||||||
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
|
|
||||||
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
|
infer_block(Env, _, [{letval, Attrs, Pattern, Type, E}|Rest], BlockType) ->
|
||||||
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
|
NewE = {typed, _, _, PatType} = infer_expr(Env, {typed, Attrs, E, arg_type(Type)}),
|
||||||
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
{'case', _, NewPattern, {typed, _, {block, _, NewRest}, _}} =
|
||||||
@@ -1157,6 +1254,8 @@ infer_prefix({IntOp,As}) when IntOp =:= '-' ->
|
|||||||
|
|
||||||
free_vars({int, _, _}) ->
|
free_vars({int, _, _}) ->
|
||||||
[];
|
[];
|
||||||
|
free_vars({char, _, _}) ->
|
||||||
|
[];
|
||||||
free_vars({string, _, _}) ->
|
free_vars({string, _, _}) ->
|
||||||
[];
|
[];
|
||||||
free_vars({bool, _, _}) ->
|
free_vars({bool, _, _}) ->
|
||||||
@@ -1348,6 +1447,16 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
|||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, Cs).
|
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.
|
-spec solve_field_constraints(env(), [field_constraint()]) -> ok.
|
||||||
solve_field_constraints(Env, Constraints) ->
|
solve_field_constraints(Env, Constraints) ->
|
||||||
%% First look for record fields that appear in only one type definition
|
%% First look for record fields that appear in only one type definition
|
||||||
@@ -1446,9 +1555,12 @@ solve_known_record_types(Env, Constraints) ->
|
|||||||
DerefConstraints--SolvedConstraints.
|
DerefConstraints--SolvedConstraints.
|
||||||
|
|
||||||
destroy_and_report_unsolved_field_constraints(Env) ->
|
destroy_and_report_unsolved_field_constraints(Env) ->
|
||||||
{FieldCs, CreateCs} =
|
{FieldCs, OtherCs} =
|
||||||
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
lists:partition(fun(#field_constraint{}) -> true; (_) -> false end,
|
||||||
get_field_constraints()),
|
get_field_constraints()),
|
||||||
|
{CreateCs, ContractCs} =
|
||||||
|
lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end,
|
||||||
|
OtherCs),
|
||||||
Unknown = solve_known_record_types(Env, FieldCs),
|
Unknown = solve_known_record_types(Env, FieldCs),
|
||||||
if Unknown == [] -> ok;
|
if Unknown == [] -> ok;
|
||||||
true ->
|
true ->
|
||||||
@@ -1458,6 +1570,7 @@ destroy_and_report_unsolved_field_constraints(Env) ->
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
check_record_create_constraints(Env, CreateCs),
|
check_record_create_constraints(Env, CreateCs),
|
||||||
|
check_is_contract_constraints(Env, ContractCs),
|
||||||
destroy_field_constraints(),
|
destroy_field_constraints(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
@@ -1583,6 +1696,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) ->
|
|||||||
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
|
{field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)};
|
||||||
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) ->
|
||||||
{constr_t, Ann, Con, unfold_types_in_type(Env, 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) ->
|
unfold_types_in_type(Env, T, Options) when is_tuple(T) ->
|
||||||
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options));
|
||||||
unfold_types_in_type(Env, [H|T], Options) ->
|
unfold_types_in_type(Env, [H|T], Options) ->
|
||||||
@@ -1638,6 +1753,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) ->
|
|||||||
true;
|
true;
|
||||||
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) ->
|
||||||
true;
|
true;
|
||||||
|
unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) ->
|
||||||
|
true;
|
||||||
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) ->
|
||||||
unify(Env, Named1, Named2, When) andalso
|
unify(Env, Named1, Named2, When) andalso
|
||||||
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When);
|
||||||
@@ -1655,26 +1772,8 @@ unify1(Env, {app_t, _, T, []}, B, When) ->
|
|||||||
unify1(Env, A, {app_t, _, T, []}, When) ->
|
unify1(Env, A, {app_t, _, T, []}, When) ->
|
||||||
unify(Env, A, T, When);
|
unify(Env, A, T, When);
|
||||||
unify1(_Env, A, B, When) ->
|
unify1(_Env, A, B, When) ->
|
||||||
Ok =
|
cannot_unify(A, B, When),
|
||||||
case get_option(permissive_address_literals, false) of
|
false.
|
||||||
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.
|
|
||||||
|
|
||||||
dereference(T = {uvar, _, R}) ->
|
dereference(T = {uvar, _, R}) ->
|
||||||
case ets_lookup(type_vars, R) of
|
case ets_lookup(type_vars, R) of
|
||||||
@@ -1703,6 +1802,7 @@ occurs_check1(_, {con, _, _}) -> false;
|
|||||||
occurs_check1(_, {qid, _, _}) -> false;
|
occurs_check1(_, {qid, _, _}) -> false;
|
||||||
occurs_check1(_, {qcon, _, _}) -> false;
|
occurs_check1(_, {qcon, _, _}) -> false;
|
||||||
occurs_check1(_, {tvar, _, _}) -> false;
|
occurs_check1(_, {tvar, _, _}) -> false;
|
||||||
|
occurs_check1(_, {bytes_t, _, _}) -> false;
|
||||||
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
|
occurs_check1(R, {fun_t, _, Named, Args, Res}) ->
|
||||||
occurs_check(R, [Res, Named | Args]);
|
occurs_check(R, [Res, Named | Args]);
|
||||||
occurs_check1(R, {app_t, _, T, Ts}) ->
|
occurs_check1(R, {app_t, _, T, Ts}) ->
|
||||||
@@ -1785,7 +1885,7 @@ create_type_errors() ->
|
|||||||
ets_new(type_errors, [bag]).
|
ets_new(type_errors, [bag]).
|
||||||
|
|
||||||
destroy_and_report_type_errors(Env) ->
|
destroy_and_report_type_errors(Env) ->
|
||||||
Errors = ets_tab2list(type_errors),
|
Errors = lists:reverse(ets_tab2list(type_errors)),
|
||||||
%% io:format("Type errors now: ~p\n", [Errors]),
|
%% io:format("Type errors now: ~p\n", [Errors]),
|
||||||
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
|
PPErrors = [ pp_error(unqualify(Env, Err)) || Err <- Errors ],
|
||||||
ets_delete(type_errors),
|
ets_delete(type_errors),
|
||||||
@@ -1819,6 +1919,11 @@ pp_error({undefined_field, Id}) ->
|
|||||||
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]);
|
||||||
pp_error({not_a_record_type, Type, Why}) ->
|
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)]);
|
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}) ->
|
pp_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||||
Plural = [ $s || length(Nonlinear) > 1 ],
|
Plural = [ $s || length(Nonlinear) > 1 ],
|
||||||
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||||
@@ -1896,6 +2001,21 @@ pp_error({include, {string, Pos, Name}}) ->
|
|||||||
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
pp_error({namespace, _Pos, {con, Pos, Name}, _Def}) ->
|
||||||
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n",
|
||||||
[Name, pp_loc(Pos)]);
|
[Name, pp_loc(Pos)]);
|
||||||
|
pp_error({repeated_arg, Fun, Arg}) ->
|
||||||
|
io_lib:format("Repeated argument ~s to function ~s (at ~s).\n",
|
||||||
|
[Arg, pp(Fun), pp_loc(Fun)]);
|
||||||
|
pp_error({stateful_not_allowed, Id, Fun}) ->
|
||||||
|
io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||||
|
[pp(Id), pp_loc(Id), pp(Fun)]);
|
||||||
|
pp_error({value_arg_not_allowed, Value, Fun}) ->
|
||||||
|
io_lib:format("Cannot pass non-zero value argument ~s (at ~s)\nin the definition of non-stateful function ~s.\n",
|
||||||
|
[pp_expr("", Value), pp_loc(Value), pp(Fun)]);
|
||||||
|
pp_error({init_depends_on_state, Which, [_Init | Chain]}) ->
|
||||||
|
WhichCalls = fun("put") -> ""; ("state") -> ""; (_) -> ", which calls" end,
|
||||||
|
io_lib:format("The init function should return the initial state as its result and cannot ~s the state,\nbut it calls\n~s",
|
||||||
|
[if Which == put -> "write"; true -> "read" end,
|
||||||
|
[ io_lib:format(" - ~s (at ~s)~s\n", [Fun, pp_loc(Ann), WhichCalls(Fun)])
|
||||||
|
|| {[_, Fun], Ann} <- Chain]]);
|
||||||
pp_error(Err) ->
|
pp_error(Err) ->
|
||||||
io_lib:format("Unknown error: ~p\n", [Err]).
|
io_lib:format("Unknown error: ~p\n", [Err]).
|
||||||
|
|
||||||
@@ -2043,6 +2163,8 @@ pp({qid, _, Name}) ->
|
|||||||
string:join(Name, ".");
|
string:join(Name, ".");
|
||||||
pp({con, _, Name}) ->
|
pp({con, _, Name}) ->
|
||||||
Name;
|
Name;
|
||||||
|
pp({qcon, _, Name}) ->
|
||||||
|
string:join(Name, ".");
|
||||||
pp({uvar, _, Ref}) ->
|
pp({uvar, _, Ref}) ->
|
||||||
%% Show some unique representation
|
%% Show some unique representation
|
||||||
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ];
|
||||||
@@ -2050,6 +2172,8 @@ pp({tvar, _, Name}) ->
|
|||||||
Name;
|
Name;
|
||||||
pp({tuple_t, _, Cpts}) ->
|
pp({tuple_t, _, Cpts}) ->
|
||||||
["(", pp(Cpts), ")"];
|
["(", pp(Cpts), ")"];
|
||||||
|
pp({bytes_t, _, Len}) ->
|
||||||
|
["bytes(", integer_to_list(Len), ")"];
|
||||||
pp({app_t, _, T, []}) ->
|
pp({app_t, _, T, []}) ->
|
||||||
pp(T);
|
pp(T);
|
||||||
pp({app_t, _, Type, Args}) ->
|
pp({app_t, _, Type, Args}) ->
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+58
-20
@@ -102,13 +102,6 @@ contract_to_icode([{letfun, Attrib, Name, Args, _What, Body={typed,_,_,T}}|Rest]
|
|||||||
QName = aeso_icode:qualify(Name, Icode),
|
QName = aeso_icode:qualify(Name, Icode),
|
||||||
NewIcode = ast_fun_to_icode(ast_id(QName), FunAttrs, FunArgs, FunBody, TypeRep, 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) ->
|
|
||||||
%% OBS! This code ignores the letrec structure of the source,
|
|
||||||
%% because the back end treats ALL declarations as recursive! We
|
|
||||||
%% need to decide whether to (a) modify the back end to respect
|
|
||||||
%% the letrec structure, or (b) (preferably) modify the front end
|
|
||||||
%% just to parse a list of (mutually recursive) definitions.
|
|
||||||
contract_to_icode(Defs++Rest, Icode);
|
|
||||||
contract_to_icode([], Icode) -> Icode;
|
contract_to_icode([], Icode) -> Icode;
|
||||||
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
|
contract_to_icode([{fun_decl, _, _, _} | Code], Icode) ->
|
||||||
contract_to_icode(Code, Icode);
|
contract_to_icode(Code, Icode);
|
||||||
@@ -152,6 +145,7 @@ ast_body(?qid_app(["Chain", "block_hash"], [Height], _, _), Icode) ->
|
|||||||
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
|
ast_body(?qid_app(["Call", "gas_left"], [], _, _), _Icode) ->
|
||||||
prim_gas_left;
|
prim_gas_left;
|
||||||
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
|
ast_body({qid, _, ["Contract", "address"]}, _Icode) -> prim_contract_address;
|
||||||
|
ast_body({qid, _, ["Contract", "creator"]}, _Icode) -> prim_contract_creator;
|
||||||
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
|
ast_body({qid, _, ["Contract", "balance"]}, _Icode) -> #prim_balance{ address = prim_contract_address };
|
||||||
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
|
ast_body({qid, _, ["Call", "origin"]}, _Icode) -> prim_call_origin;
|
||||||
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
|
ast_body({qid, _, ["Call", "caller"]}, _Icode) -> prim_caller;
|
||||||
@@ -224,6 +218,17 @@ ast_body(?qid_app(["Oracle", "get_answer"], [Oracle, Q], [_, ?query_t(_, RType)]
|
|||||||
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
|
prim_call(?PRIM_CALL_ORACLE_GET_ANSWER, #integer{value = 0},
|
||||||
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
|
[ast_body(Oracle, Icode), ast_body(Q, Icode)], [word, word], aeso_icode:option_typerep(ast_type(RType, Icode)));
|
||||||
|
|
||||||
|
ast_body(?qid_app(["Oracle", "check"], [Oracle], [?oracle_t(Q, R)], _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ORACLE_CHECK, #integer{value = 0},
|
||||||
|
[ast_body(Oracle, Icode), ast_type_value(Q, Icode), ast_type_value(R, Icode)],
|
||||||
|
[word, typerep, typerep], word);
|
||||||
|
|
||||||
|
ast_body(?qid_app(["Oracle", "check_query"], [Oracle, Query], [_, ?query_t(Q, R)], _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ORACLE_CHECK_QUERY, #integer{value = 0},
|
||||||
|
[ast_body(Oracle, Icode), ast_body(Query, Icode),
|
||||||
|
ast_type_value(Q, Icode), ast_type_value(R, Icode)],
|
||||||
|
[word, typerep, typerep], word);
|
||||||
|
|
||||||
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
|
ast_body({qid, _, ["Oracle", "register"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.register'});
|
||||||
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
|
ast_body({qid, _, ["Oracle", "query"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.query'});
|
||||||
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
|
ast_body({qid, _, ["Oracle", "extend"]}, _Icode) -> gen_error({underapplied_primitive, 'Oracle.extend'});
|
||||||
@@ -346,6 +351,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
|
|||||||
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
|
||||||
[word, word, sign_t()], word);
|
[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) ->
|
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
|
||||||
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
|
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
|
||||||
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
|
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
|
||||||
@@ -405,6 +415,12 @@ ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
|
|||||||
|
|
||||||
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
|
ast_body(?qid_app(["Address", "to_str"], [Addr], _, _), Icode) ->
|
||||||
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
|
builtin_call(addr_to_str, [ast_body(Addr, Icode)]);
|
||||||
|
ast_body(?qid_app(["Address", "is_oracle"], [Addr], _, _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ADDR_IS_ORACLE, #integer{value = 0},
|
||||||
|
[ast_body(Addr, Icode)], [word], word);
|
||||||
|
ast_body(?qid_app(["Address", "is_contract"], [Addr], _, _), Icode) ->
|
||||||
|
prim_call(?PRIM_CALL_ADDR_IS_CONTRACT, #integer{value = 0},
|
||||||
|
[ast_body(Addr, Icode)], [word], word);
|
||||||
|
|
||||||
%% Other terms
|
%% Other terms
|
||||||
ast_body({id, _, Name}, _Icode) ->
|
ast_body({id, _, Name}, _Icode) ->
|
||||||
@@ -416,14 +432,17 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints
|
|||||||
#integer{value = Value};
|
#integer{value = Value};
|
||||||
ast_body({int, _, Value}, _Icode) ->
|
ast_body({int, _, Value}, _Icode) ->
|
||||||
#integer{value = Value};
|
#integer{value = Value};
|
||||||
ast_body({hash, _, Hash}, _Icode) ->
|
ast_body({bytes, _, Bin}, _Icode) ->
|
||||||
case Hash of
|
case aeb_memory:binary_to_words(Bin) of
|
||||||
<<Value:32/unit:8>> -> %% address
|
[Word] -> #integer{value = Word};
|
||||||
#integer{value = Value};
|
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
|
||||||
<<Hi:32/unit:8, Lo:32/unit:8>> -> %% signature
|
|
||||||
#tuple{cpts = [#integer{value = Hi},
|
|
||||||
#integer{value = Lo}]}
|
|
||||||
end;
|
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) ->
|
ast_body({string,_,Bin}, _Icode) ->
|
||||||
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
|
Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)],
|
||||||
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
||||||
@@ -504,6 +523,8 @@ ast_body({switch,_,A,Cases}, Icode) ->
|
|||||||
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
|
ast_body({block,As,[{letval,_,Pat,_,E}|Rest]}, Icode) ->
|
||||||
#switch{expr=ast_body(E, Icode),
|
#switch{expr=ast_body(E, Icode),
|
||||||
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
|
cases=[{ast_body(Pat, Icode),ast_body({block,As,Rest}, Icode)}]};
|
||||||
|
ast_body({block, As, [{letfun, Ann, F, Args, _Type, Expr} | Rest]}, Icode) ->
|
||||||
|
ast_body({block, As, [{letval, Ann, F, unused, {lam, Ann, Args, Expr}} | Rest]}, Icode);
|
||||||
ast_body({block,_,[]}, _Icode) ->
|
ast_body({block,_,[]}, _Icode) ->
|
||||||
#tuple{cpts=[]};
|
#tuple{cpts=[]};
|
||||||
ast_body({block,_,[E]}, Icode) ->
|
ast_body({block,_,[E]}, Icode) ->
|
||||||
@@ -571,15 +592,29 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
|
|||||||
_ when not Monomorphic ->
|
_ when not Monomorphic ->
|
||||||
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
|
gen_error({cant_compare_polymorphic_type, Ann, Op, Type});
|
||||||
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
|
word -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)};
|
||||||
string ->
|
OtherType ->
|
||||||
Neg = case Op of
|
Neg = case Op of
|
||||||
'==' -> fun(X) -> X end;
|
'==' -> fun(X) -> X end;
|
||||||
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
|
'!=' -> fun(X) -> #unop{ op = '!', rand = X } end;
|
||||||
_ -> gen_error({cant_compare, Ann, Op, Type})
|
_ -> gen_error({cant_compare, Ann, Op, Type})
|
||||||
end,
|
end,
|
||||||
Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
|
Args = [ast_body(A, Icode), ast_body(B, Icode)],
|
||||||
args = [ast_body(A, Icode), ast_body(B, Icode)] });
|
Builtin =
|
||||||
_ -> gen_error({cant_compare, Ann, Op, Type})
|
case OtherType of
|
||||||
|
string ->
|
||||||
|
#funcall{ function = #var_ref{name = {builtin, str_equal}},
|
||||||
|
args = Args };
|
||||||
|
{tuple, Types} ->
|
||||||
|
case lists:usort(Types) of
|
||||||
|
[word] ->
|
||||||
|
#funcall{ function = #var_ref{name = {builtin, str_equal_p}},
|
||||||
|
args = [ #integer{value = 32 * length(Types)} | Args] };
|
||||||
|
_ -> gen_error({cant_compare, Ann, Op, Type})
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
gen_error({cant_compare, Ann, Op, Type})
|
||||||
|
end,
|
||||||
|
Neg(Builtin)
|
||||||
end;
|
end;
|
||||||
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} },
|
||||||
@@ -688,6 +723,8 @@ ast_typerep({qid, _, Name}, Icode) ->
|
|||||||
lookup_type_id(Name, [], Icode);
|
lookup_type_id(Name, [], Icode);
|
||||||
ast_typerep({con, _, _}, _) ->
|
ast_typerep({con, _, _}, _) ->
|
||||||
word; %% Contract type
|
word; %% Contract type
|
||||||
|
ast_typerep({bytes_t, _, Len}, _) ->
|
||||||
|
bytes_t(Len);
|
||||||
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
|
ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) ->
|
||||||
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
|
ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ],
|
||||||
lookup_type_id(Name, ArgReps, Icode);
|
lookup_type_id(Name, ArgReps, Icode);
|
||||||
@@ -715,8 +752,9 @@ ast_typerep({variant_t, Cons}, Icode) ->
|
|||||||
ttl_t(Icode) ->
|
ttl_t(Icode) ->
|
||||||
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
ast_typerep({qid, [], ["Chain", "ttl"]}, Icode).
|
||||||
|
|
||||||
sign_t() ->
|
sign_t() -> bytes_t(64).
|
||||||
{tuple, [word, word]}.
|
bytes_t(Len) when Len =< 32 -> word;
|
||||||
|
bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}.
|
||||||
|
|
||||||
get_signature_arg(Args0) ->
|
get_signature_arg(Args0) ->
|
||||||
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
|
NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0],
|
||||||
|
|||||||
+18
-17
@@ -104,13 +104,12 @@ from_string(ContractString, Options) ->
|
|||||||
%% General programming errors in the compiler just signal error.
|
%% General programming errors in the compiler just signal error.
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
|
-spec string_to_icode(string(), [option()]) -> map().
|
||||||
string_to_icode(ContractString, Options0) ->
|
string_to_icode(ContractString, Options) ->
|
||||||
{InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0),
|
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
pp_sophia_code(Ast, Options),
|
pp_sophia_code(Ast, Options),
|
||||||
pp_ast(Ast, Options),
|
pp_ast(Ast, Options),
|
||||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
|
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
||||||
pp_typed_ast(TypedAst, Options),
|
pp_typed_ast(TypedAst, Options),
|
||||||
Icode = ast_to_icode(TypedAst, Options),
|
Icode = ast_to_icode(TypedAst, Options),
|
||||||
pp_icode(Icode, Options),
|
pp_icode(Icode, Options),
|
||||||
@@ -151,11 +150,11 @@ check_call(Source, FunName, Args, Options) ->
|
|||||||
|
|
||||||
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
|
check_call(ContractString0, FunName, Args, Options, PatchFun) ->
|
||||||
try
|
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),
|
#{} = string_to_icode(ContractString0, Options),
|
||||||
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
||||||
#{typed_ast := TypedAst,
|
#{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),
|
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||||
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
|
||||||
RetVMType = case RetType of
|
RetVMType = case RetType of
|
||||||
@@ -189,7 +188,7 @@ insert_call_function(Code, FunName, Args, Options) ->
|
|||||||
[ Code,
|
[ Code,
|
||||||
"\n\n",
|
"\n\n",
|
||||||
lists:duplicate(Ind, " "),
|
lists:duplicate(Ind, " "),
|
||||||
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-spec insert_init_function(string(), options()) -> string().
|
-spec insert_init_function(string(), options()) -> string().
|
||||||
@@ -257,19 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
|
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||||
|
|
||||||
%% TODO: somewhere else
|
%% TODO: somewhere else
|
||||||
-spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
-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, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||||
translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(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(N);
|
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||||
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
|
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, _, "int"}, N) -> {int, [], N};
|
||||||
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, 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(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||||
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
|
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||||
{hash, [], <<Hi:256, Lo:256>>};
|
{bytes, [], <<Val:Len/unit:8>>};
|
||||||
|
translate_vm_value({tuple, _}, {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(string, {id, _, "string"}, S) -> {string, [], S};
|
||||||
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
||||||
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
|
{list, [], [translate_vm_value(VmType, Type, X) || X <- List]};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -343,6 +343,8 @@ assemble_expr(Funs, Stack, _Tail, #prim_put{ state = State }) ->
|
|||||||
%% Environment primitives
|
%% Environment primitives
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_contract_address) ->
|
||||||
[i(?ADDRESS)];
|
[i(?ADDRESS)];
|
||||||
|
assemble_expr(_Funs, _Stack, _Tail, prim_contract_creator) ->
|
||||||
|
[i(?CREATOR)];
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_call_origin) ->
|
||||||
[i(?ORIGIN)];
|
[i(?ORIGIN)];
|
||||||
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
assemble_expr(_Funs, _Stack, _Tail, prim_caller) ->
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
%%%-------------------------------------------------------------------
|
|
||||||
%%% @author Ulf Norell
|
|
||||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
|
||||||
%%% @doc
|
|
||||||
%%% Fate backend for Sophia compiler
|
|
||||||
%%% @end
|
|
||||||
%%% Created : 11 Jan 2019
|
|
||||||
%%%
|
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
-module(aeso_icode_to_fate).
|
|
||||||
|
|
||||||
-include("aeso_icode.hrl").
|
|
||||||
|
|
||||||
-export([compile/2]).
|
|
||||||
|
|
||||||
%% -- Preamble ---------------------------------------------------------------
|
|
||||||
|
|
||||||
-define(TODO(What), error({todo, ?FILE, ?LINE, ?FUNCTION_NAME, What})).
|
|
||||||
|
|
||||||
-define(i(__X__), {immediate, __X__}).
|
|
||||||
-define(a, {stack, 0}).
|
|
||||||
|
|
||||||
-record(env, { args = [], stack = [], tailpos = true }).
|
|
||||||
|
|
||||||
%% -- Debugging --------------------------------------------------------------
|
|
||||||
|
|
||||||
%% debug(Options, Fmt) -> debug(Options, Fmt, []).
|
|
||||||
debug(Options, Fmt, Args) ->
|
|
||||||
case proplists:get_value(debug, Options, true) of
|
|
||||||
true -> io:format(Fmt, Args);
|
|
||||||
false -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% -- Main -------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% @doc Main entry point.
|
|
||||||
compile(ICode, Options) ->
|
|
||||||
#{ contract_name := _ContractName,
|
|
||||||
state_type := _StateType,
|
|
||||||
functions := Functions } = ICode,
|
|
||||||
SFuns = functions_to_scode(Functions, Options),
|
|
||||||
SFuns1 = optimize_scode(SFuns, Options),
|
|
||||||
to_basic_blocks(SFuns1, Options).
|
|
||||||
|
|
||||||
functions_to_scode(Functions, Options) ->
|
|
||||||
maps:from_list(
|
|
||||||
[ {list_to_binary(Name), function_to_scode(Name, Args, Body, Type, Options)}
|
|
||||||
|| {Name, _Ann, Args, Body, Type} <- Functions, Name /= "init" ]). %% TODO: skip init for now
|
|
||||||
|
|
||||||
function_to_scode(Name, Args, Body, Type, Options) ->
|
|
||||||
debug(Options, "Compiling ~p ~p : ~p ->\n ~p\n", [Name, Args, Type, Body]),
|
|
||||||
ArgTypes = [ icode_type_to_fate(T) || {_, T} <- Args ],
|
|
||||||
ResType = icode_type_to_fate(Type),
|
|
||||||
SCode = to_scode(init_env(Args), Body),
|
|
||||||
debug(Options, " scode: ~p\n", [SCode]),
|
|
||||||
{{ArgTypes, ResType}, SCode}.
|
|
||||||
|
|
||||||
%% -- Types ------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% TODO: the Fate types don't seem to be specified anywhere...
|
|
||||||
icode_type_to_fate(word) -> integer;
|
|
||||||
icode_type_to_fate(string) -> string;
|
|
||||||
icode_type_to_fate({tuple, Types}) ->
|
|
||||||
{tuple, lists:map(fun icode_type_to_fate/1, Types)};
|
|
||||||
icode_type_to_fate({list, Type}) ->
|
|
||||||
{list, icode_type_to_fate(Type)};
|
|
||||||
icode_type_to_fate(typerep) -> typerep;
|
|
||||||
icode_type_to_fate(Type) -> ?TODO(Type).
|
|
||||||
|
|
||||||
%% -- Phase I ----------------------------------------------------------------
|
|
||||||
%% Icode to structured assembly
|
|
||||||
|
|
||||||
%% -- Environment functions --
|
|
||||||
|
|
||||||
init_env(Args) ->
|
|
||||||
#env{ args = Args, stack = [], tailpos = true }.
|
|
||||||
|
|
||||||
push_env(Type, Env) ->
|
|
||||||
Env#env{ stack = [{"_", Type} | Env#env.stack] }.
|
|
||||||
|
|
||||||
notail(Env) -> Env#env{ tailpos = false }.
|
|
||||||
|
|
||||||
lookup_var(#env{ args = Args, stack = S }, X) ->
|
|
||||||
case {keyfind_index(X, 1, S), keyfind_index(X, 1, Args)} of
|
|
||||||
{false, false} -> false;
|
|
||||||
{false, Arg} -> {arg, Arg};
|
|
||||||
{Local, _} -> {stack, Local}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% -- The compiler --
|
|
||||||
|
|
||||||
to_scode(_Env, #integer{ value = N }) ->
|
|
||||||
[aeb_fate_code:push(?i(N))]; %% Doesn't exist (yet), translated by desugaring
|
|
||||||
|
|
||||||
to_scode(Env, #var_ref{name = X}) ->
|
|
||||||
case lookup_var(Env, X) of
|
|
||||||
false -> error({unbound_variable, X, Env});
|
|
||||||
{stack, N} -> [aeb_fate_code:dup(?i(N))];
|
|
||||||
{arg, N} -> [aeb_fate_code:push({arg, N})]
|
|
||||||
end;
|
|
||||||
to_scode(Env, #binop{ op = Op, left = A, right = B }) ->
|
|
||||||
[ to_scode(notail(Env), B)
|
|
||||||
, to_scode(push_env(binop_type_r(Op), Env), A)
|
|
||||||
, binop_to_scode(Op) ];
|
|
||||||
|
|
||||||
to_scode(Env, #ifte{decision = Dec, then = Then, else = Else}) ->
|
|
||||||
[ to_scode(notail(Env), Dec)
|
|
||||||
, {ifte, to_scode(Env, Then), to_scode(Env, Else)} ];
|
|
||||||
|
|
||||||
to_scode(_Env, Icode) -> ?TODO(Icode).
|
|
||||||
|
|
||||||
%% -- Operators --
|
|
||||||
|
|
||||||
binop_types('+') -> {word, word};
|
|
||||||
binop_types('-') -> {word, word};
|
|
||||||
binop_types('==') -> {word, word};
|
|
||||||
binop_types(Op) -> ?TODO(Op).
|
|
||||||
|
|
||||||
%% binop_type_l(Op) -> element(1, binop_types(Op)).
|
|
||||||
binop_type_r(Op) -> element(2, binop_types(Op)).
|
|
||||||
|
|
||||||
binop_to_scode('+') -> add_a_a_a(); %% Optimization introduces other variants
|
|
||||||
binop_to_scode('-') -> sub_a_a_a();
|
|
||||||
binop_to_scode('==') -> eq_a_a_a().
|
|
||||||
% binop_to_scode(Op) -> ?TODO(Op).
|
|
||||||
|
|
||||||
add_a_a_a() -> aeb_fate_code:add(?a, ?a, ?a).
|
|
||||||
sub_a_a_a() -> aeb_fate_code:sub(?a, ?a, ?a).
|
|
||||||
eq_a_a_a() -> aeb_fate_code:eq(?a, ?a, ?a).
|
|
||||||
|
|
||||||
%% -- Phase II ---------------------------------------------------------------
|
|
||||||
%% Optimize
|
|
||||||
|
|
||||||
optimize_scode(Funs, Options) ->
|
|
||||||
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
|
|
||||||
Funs).
|
|
||||||
|
|
||||||
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
|
||||||
|
|
||||||
flatten_s({ifte, Then, Else}) -> {ifte, flatten(Then), flatten(Else)};
|
|
||||||
flatten_s(I) -> I.
|
|
||||||
|
|
||||||
optimize_fun(_Funs, Name, {{Args, Res}, Code}, Options) ->
|
|
||||||
Code0 = flatten(Code),
|
|
||||||
debug(Options, "Optimizing ~s\n", [Name]),
|
|
||||||
debug(Options, " original : ~p\n", [Code0]),
|
|
||||||
Code1 = simplify(Code0),
|
|
||||||
debug(Options, " simplified: ~p\n", [Code1]),
|
|
||||||
Code2 = desugar(Code1),
|
|
||||||
debug(Options, " desugared : ~p\n", [Code2]),
|
|
||||||
{{Args, Res}, Code2}.
|
|
||||||
|
|
||||||
simplify([]) -> [];
|
|
||||||
simplify([I | Code]) ->
|
|
||||||
simpl_top(simpl_s(I), simplify(Code)).
|
|
||||||
|
|
||||||
simpl_s({ifte, Then, Else}) ->
|
|
||||||
{ifte, simplify(Then), simplify(Else)};
|
|
||||||
simpl_s(I) -> I.
|
|
||||||
|
|
||||||
%% add_i 0 --> nop
|
|
||||||
simpl_top({'ADD', _, ?i(0), _}, Code) -> Code;
|
|
||||||
%% push n, add_a --> add_i n
|
|
||||||
simpl_top({'PUSH', ?a, ?i(N)},
|
|
||||||
[{'ADD', ?a, ?a, ?a} | Code]) ->
|
|
||||||
simpl_top( aeb_fate_code:add(?a, ?i(N), ?a), Code);
|
|
||||||
%% push n, add_i m --> add_i (n + m)
|
|
||||||
simpl_top({'PUSH', ?a, ?i(N)}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
|
|
||||||
simpl_top(aeb_fate_code:push(?i(N + M)), Code);
|
|
||||||
%% add_i n, add_i m --> add_i (n + m)
|
|
||||||
simpl_top({'ADD', ?a, ?i(N), ?a}, [{'ADD', ?a, ?i(M), ?a} | Code]) ->
|
|
||||||
simpl_top({'ADD', ?a, ?i(N + M), ?a}, Code);
|
|
||||||
|
|
||||||
simpl_top(I, Code) -> [I | Code].
|
|
||||||
|
|
||||||
%% Desugar and specialize
|
|
||||||
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_code:inc()];
|
|
||||||
desugar({ifte, Then, Else}) -> [{ifte, desugar(Then), desugar(Else)}];
|
|
||||||
desugar(Code) when is_list(Code) ->
|
|
||||||
lists:flatmap(fun desugar/1, Code);
|
|
||||||
desugar(I) -> [I].
|
|
||||||
|
|
||||||
%% -- Phase III --------------------------------------------------------------
|
|
||||||
%% Constructing basic blocks
|
|
||||||
|
|
||||||
to_basic_blocks(Funs, Options) ->
|
|
||||||
maps:from_list([ {Name, {{Args, Res},
|
|
||||||
bb(Name, Code ++ [aeb_fate_code:return()], Options)}}
|
|
||||||
|| {Name, {{Args, Res}, Code}} <- maps:to_list(Funs) ]).
|
|
||||||
|
|
||||||
bb(Name, Code, Options) ->
|
|
||||||
Blocks0 = blocks(Code),
|
|
||||||
Blocks = optimize_blocks(Blocks0),
|
|
||||||
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
|
|
||||||
BBs = [ set_labels(Labels, B) || B <- Blocks ],
|
|
||||||
debug(Options, "Final code for ~s:\n ~p\n", [Name, BBs]),
|
|
||||||
maps:from_list(BBs).
|
|
||||||
|
|
||||||
%% -- Break up scode into basic blocks --
|
|
||||||
|
|
||||||
blocks(Code) ->
|
|
||||||
Top = make_ref(),
|
|
||||||
blocks([{Top, Code}], []).
|
|
||||||
|
|
||||||
blocks([], Acc) ->
|
|
||||||
lists:reverse(Acc);
|
|
||||||
blocks([{Ref, Code} | Blocks], Acc) ->
|
|
||||||
block(Ref, Code, [], Blocks, Acc).
|
|
||||||
|
|
||||||
block(Ref, [], CodeAcc, Blocks, BlockAcc) ->
|
|
||||||
blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]);
|
|
||||||
block(Ref, [{ifte, Then, Else} | Code], Acc, Blocks, BlockAcc) ->
|
|
||||||
ThenLbl = make_ref(),
|
|
||||||
RestLbl = make_ref(),
|
|
||||||
block(Ref, Else ++ [{jump, RestLbl}],
|
|
||||||
[{jumpif, ThenLbl} | Acc],
|
|
||||||
[{ThenLbl, Then ++ [{jump, RestLbl}]},
|
|
||||||
{RestLbl, Code} | Blocks],
|
|
||||||
BlockAcc);
|
|
||||||
block(Ref, [I | Code], Acc, Blocks, BlockAcc) ->
|
|
||||||
block(Ref, Code, [I | Acc], Blocks, BlockAcc).
|
|
||||||
|
|
||||||
%% -- Reorder, inline, and remove dead blocks --
|
|
||||||
|
|
||||||
optimize_blocks(Blocks) ->
|
|
||||||
%% We need to look at the last instruction a lot, so reverse all blocks.
|
|
||||||
Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end,
|
|
||||||
RBlocks = Rev(Blocks),
|
|
||||||
RBlockMap = maps:from_list(RBlocks),
|
|
||||||
RBlocks1 = reorder_blocks(RBlocks, []),
|
|
||||||
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
|
||||||
RBlocks3 = remove_dead_blocks(RBlocks2),
|
|
||||||
Rev(RBlocks3).
|
|
||||||
|
|
||||||
%% Choose the next block based on the final jump.
|
|
||||||
reorder_blocks([], Acc) ->
|
|
||||||
lists:reverse(Acc);
|
|
||||||
reorder_blocks([{Ref, Code} | Blocks], Acc) ->
|
|
||||||
reorder_blocks(Ref, Code, Blocks, Acc).
|
|
||||||
|
|
||||||
reorder_blocks(Ref, Code, Blocks, Acc) ->
|
|
||||||
Acc1 = [{Ref, Code} | Acc],
|
|
||||||
case Code of
|
|
||||||
['RETURN'|_] -> reorder_blocks(Blocks, Acc1);
|
|
||||||
[{'RETURNR', _}|_] -> reorder_blocks(Blocks, Acc1);
|
|
||||||
[{jump, L}|_] ->
|
|
||||||
NotL = fun({L1, _}) -> L1 /= L end,
|
|
||||||
case lists:splitwith(NotL, Blocks) of
|
|
||||||
{Blocks1, [{L, Code1} | Blocks2]} ->
|
|
||||||
reorder_blocks(L, Code1, Blocks1 ++ Blocks2, Acc1);
|
|
||||||
{_, []} -> reorder_blocks(Blocks, Acc1)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Inline short blocks (≤ 2 instructions)
|
|
||||||
inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
|
|
||||||
case maps:get(L, BlockMap, nocode) of
|
|
||||||
Dest when length(Dest) < 3 ->
|
|
||||||
%% Remove Ref to avoid infinite loops
|
|
||||||
inline_block(maps:remove(Ref, BlockMap), L, Dest) ++ Code;
|
|
||||||
_ -> Code0
|
|
||||||
end;
|
|
||||||
inline_block(_, _, Code) -> Code.
|
|
||||||
|
|
||||||
%% Remove unused blocks
|
|
||||||
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
|
|
||||||
BlockMap = maps:from_list(Blocks),
|
|
||||||
LiveBlocks = chase_labels([Top], BlockMap, #{}),
|
|
||||||
[ B || B = {L, _} <- Blocks, maps:is_key(L, LiveBlocks) ].
|
|
||||||
|
|
||||||
chase_labels([], _, Live) -> Live;
|
|
||||||
chase_labels([L | Ls], Map, Live) ->
|
|
||||||
Code = maps:get(L, Map),
|
|
||||||
Jump = fun({jump, A}) -> [A || not maps:is_key(A, Live)];
|
|
||||||
({jumpif, A}) -> [A || not maps:is_key(A, Live)];
|
|
||||||
(_) -> [] end,
|
|
||||||
New = lists:flatmap(Jump, Code),
|
|
||||||
chase_labels(New ++ Ls, Map, Live#{ L => true }).
|
|
||||||
|
|
||||||
|
|
||||||
%% -- Translate label refs to indices --
|
|
||||||
|
|
||||||
set_labels(Labels, {Ref, Code}) when is_reference(Ref) ->
|
|
||||||
{maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]};
|
|
||||||
set_labels(Labels, {jump, Ref}) -> aeb_fate_code:jump(maps:get(Ref, Labels));
|
|
||||||
set_labels(Labels, {jumpif, Ref}) -> aeb_fate_code:jumpif(?a, maps:get(Ref, Labels));
|
|
||||||
set_labels(_, I) -> I.
|
|
||||||
|
|
||||||
%% -- Helpers ----------------------------------------------------------------
|
|
||||||
|
|
||||||
with_ixs(Xs) ->
|
|
||||||
lists:zip(lists:seq(0, length(Xs) - 1), Xs).
|
|
||||||
|
|
||||||
keyfind_index(X, J, Xs) ->
|
|
||||||
case [ I || {I, E} <- with_ixs(Xs), X == element(J, E) ] of
|
|
||||||
[I | _] -> I;
|
|
||||||
[] -> false
|
|
||||||
end.
|
|
||||||
|
|
||||||
+32
-12
@@ -87,7 +87,7 @@ constructors() ->
|
|||||||
sep1(constructor(), tok('|')).
|
sep1(constructor(), tok('|')).
|
||||||
|
|
||||||
constructor() -> %% TODO: format for Con() vs Con
|
constructor() -> %% TODO: format for Con() vs Con
|
||||||
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
|
choice(?RULE(con(), {constr_t, get_ann(_1), _1, []}),
|
||||||
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
|
?RULE(con(), con_args(), {constr_t, get_ann(_1), _1, _2})).
|
||||||
|
|
||||||
con_args() -> paren_list(con_arg()).
|
con_args() -> paren_list(con_arg()).
|
||||||
@@ -99,9 +99,7 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
|
|||||||
%% -- Let declarations -------------------------------------------------------
|
%% -- Let declarations -------------------------------------------------------
|
||||||
|
|
||||||
letdecl() ->
|
letdecl() ->
|
||||||
choice(
|
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
|
||||||
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)),
|
|
||||||
?RULE(keyword('let'), tok(rec), sep1(letdef(), tok('and')), {letrec, _1, _3})).
|
|
||||||
|
|
||||||
letdef() -> choice(valdef(), fundef()).
|
letdef() -> choice(valdef(), fundef()).
|
||||||
|
|
||||||
@@ -136,11 +134,15 @@ type200() ->
|
|||||||
type300() -> type400().
|
type300() -> type400().
|
||||||
|
|
||||||
type400() ->
|
type400() ->
|
||||||
?RULE(typeAtom(), optional(type_args()),
|
choice(
|
||||||
|
[?RULE(typeAtom(), optional(type_args()),
|
||||||
case _2 of
|
case _2 of
|
||||||
none -> _1;
|
none -> _1;
|
||||||
{ok, Args} -> {app_t, get_ann(_1), _1, Args}
|
{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() ->
|
typeAtom() ->
|
||||||
?LAZY_P(choice(
|
?LAZY_P(choice(
|
||||||
@@ -203,8 +205,8 @@ exprAtom() ->
|
|||||||
?LAZY_P(begin
|
?LAZY_P(begin
|
||||||
Expr = ?LAZY_P(expr()),
|
Expr = ?LAZY_P(expr()),
|
||||||
choice(
|
choice(
|
||||||
[ id(), con(), token(qid), token(qcon)
|
[ id_or_addr(), con(), token(qid), token(qcon)
|
||||||
, token(hash), token(string), token(char)
|
, token(bytes), token(string), token(char)
|
||||||
, token(int)
|
, token(int)
|
||||||
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
, ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int)))
|
||||||
, {bool, keyword(true), true}
|
, {bool, keyword(true), true}
|
||||||
@@ -326,6 +328,26 @@ token(Tag) ->
|
|||||||
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
{Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val}
|
||||||
end).
|
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 ----------------------------------------------------------------
|
%% -- Helpers ----------------------------------------------------------------
|
||||||
|
|
||||||
keyword(K) -> ann(tok(K)).
|
keyword(K) -> ann(tok(K)).
|
||||||
@@ -417,7 +439,7 @@ build_if(Ann, Cond, Then, [{elif, Ann1, Cond1, Then1} | Elses]) ->
|
|||||||
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
build_if(Ann, Cond, Then, [{else, _Ann, Else}]) ->
|
||||||
{'if', Ann, Cond, Then, Else};
|
{'if', Ann, Cond, Then, Else};
|
||||||
build_if(Ann, Cond, Then, []) ->
|
build_if(Ann, Cond, Then, []) ->
|
||||||
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
|
{'if', Ann, Cond, Then, {tuple, [{origin, system}], []}}.
|
||||||
|
|
||||||
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
||||||
else_branches(Stmts, [Elif | Acc]);
|
else_branches(Stmts, [Elif | Acc]);
|
||||||
@@ -433,7 +455,6 @@ fun_t(Domains, Type) ->
|
|||||||
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
|
lists:foldr(fun({Dom, Ann}, T) -> {fun_t, Ann, [], Dom, T} end,
|
||||||
Type, Domains).
|
Type, Domains).
|
||||||
|
|
||||||
tuple_e(Ann, []) -> {unit, Ann};
|
|
||||||
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
|
tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple
|
||||||
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}.
|
||||||
|
|
||||||
@@ -454,10 +475,9 @@ parse_pattern({record, Ann, Fs}) ->
|
|||||||
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
|
{record, Ann, lists:map(fun parse_field_pattern/1, Fs)};
|
||||||
parse_pattern(E = {con, _, _}) -> E;
|
parse_pattern(E = {con, _, _}) -> E;
|
||||||
parse_pattern(E = {id, _, _}) -> E;
|
parse_pattern(E = {id, _, _}) -> E;
|
||||||
parse_pattern(E = {unit, _}) -> E;
|
|
||||||
parse_pattern(E = {int, _, _}) -> E;
|
parse_pattern(E = {int, _, _}) -> E;
|
||||||
parse_pattern(E = {bool, _, _}) -> E;
|
parse_pattern(E = {bool, _, _}) -> E;
|
||||||
parse_pattern(E = {hash, _, _}) -> E;
|
parse_pattern(E = {bytes, _, _}) -> E;
|
||||||
parse_pattern(E = {string, _, _}) -> E;
|
parse_pattern(E = {string, _, _}) -> E;
|
||||||
parse_pattern(E = {char, _, _}) -> E;
|
parse_pattern(E = {char, _, _}) -> E;
|
||||||
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
parse_pattern(E) -> bad_expr_err("Not a valid pattern", E).
|
||||||
|
|||||||
+14
-8
@@ -160,8 +160,7 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
|
|||||||
text(atom_to_list(Mod));
|
text(atom_to_list(Mod));
|
||||||
(_) -> empty() end,
|
(_) -> empty() end,
|
||||||
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
|
hsep(lists:map(Mod, Attrs) ++ [letdecl("function", D)]);
|
||||||
decl(D = {letval, _, _, _, _}) -> letdecl("let", D);
|
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
|
||||||
decl(D = {letrec, _, _}) -> letdecl("let", D).
|
|
||||||
|
|
||||||
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
||||||
expr(E, Options) ->
|
expr(E, Options) ->
|
||||||
@@ -184,9 +183,7 @@ name({typed, _, Name, _}) -> name(Name).
|
|||||||
letdecl(Let, {letval, _, F, T, E}) ->
|
letdecl(Let, {letval, _, F, T, E}) ->
|
||||||
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
|
block_expr(0, hsep([text(Let), typed(name(F), T), text("=")]), E);
|
||||||
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
letdecl(Let, {letfun, _, F, Args, T, E}) ->
|
||||||
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E);
|
block_expr(0, hsep([text(Let), typed(beside(name(F), args(Args)), T), text("=")]), E).
|
||||||
letdecl(Let, {letrec, _, [D | Ds]}) ->
|
|
||||||
hsep(text(Let), above([ letdecl("rec", D) | [ letdecl("and", D1) || D1 <- Ds ] ])).
|
|
||||||
|
|
||||||
-spec args([aeso_syntax:arg()]) -> doc().
|
-spec args([aeso_syntax:arg()]) -> doc().
|
||||||
args(Args) ->
|
args(Args) ->
|
||||||
@@ -236,6 +233,8 @@ 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({bytes_t, _, Len}) ->
|
||||||
|
text(lists:concat(["bytes(", Len, ")"]));
|
||||||
type({named_arg_t, _, Name, Type, _Default}) ->
|
type({named_arg_t, _, Name, Type, _Default}) ->
|
||||||
%% Drop the default value
|
%% Drop the default value
|
||||||
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
%% follow(hsep(typed(name(Name), Type), text("=")), expr(Default));
|
||||||
@@ -319,9 +318,17 @@ expr_p(_, E = {int, _, N}) ->
|
|||||||
end,
|
end,
|
||||||
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(_, {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(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
|
||||||
expr_p(_, {unit, _}) -> text("()");
|
expr_p(_, {Type, _, Bin})
|
||||||
|
when Type == account_pubkey;
|
||||||
|
Type == contract_pubkey;
|
||||||
|
Type == oracle_pubkey;
|
||||||
|
Type == oracle_query_id ->
|
||||||
|
text(binary_to_list(aeser_api_encoder:encode(Type, Bin)));
|
||||||
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
|
expr_p(_, {string, _, S}) -> term(binary_to_list(S));
|
||||||
expr_p(_, {char, _, C}) ->
|
expr_p(_, {char, _, C}) ->
|
||||||
case C of
|
case C of
|
||||||
@@ -425,7 +432,6 @@ statements(Stmts) ->
|
|||||||
|
|
||||||
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
|
statement(S = {letval, _, _, _, _}) -> letdecl("let", S);
|
||||||
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
|
statement(S = {letfun, _, _, _, _, _}) -> letdecl("let", S);
|
||||||
statement(S = {letrec, _, _}) -> letdecl("let", S);
|
|
||||||
statement(E) -> expr(E).
|
statement(E) -> expr(E).
|
||||||
|
|
||||||
get_elifs(Expr) -> get_elifs(Expr, []).
|
get_elifs(Expr) -> get_elifs(Expr, []).
|
||||||
|
|||||||
+8
-10
@@ -20,7 +20,7 @@ lexer() ->
|
|||||||
CON = [UPPER, "[a-zA-Z0-9_]*"],
|
CON = [UPPER, "[a-zA-Z0-9_]*"],
|
||||||
INT = [DIGIT, "+"],
|
INT = [DIGIT, "+"],
|
||||||
HEX = ["0x", HEXDIGIT, "+"],
|
HEX = ["0x", HEXDIGIT, "+"],
|
||||||
HASH = ["#", HEXDIGIT, "+"],
|
BYTES = ["#", HEXDIGIT, "+"],
|
||||||
WS = "[\\000-\\ ]+",
|
WS = "[\\000-\\ ]+",
|
||||||
ID = [LOWER, "[a-zA-Z0-9_']*"],
|
ID = [LOWER, "[a-zA-Z0-9_']*"],
|
||||||
TVAR = ["'", ID],
|
TVAR = ["'", ID],
|
||||||
@@ -36,8 +36,8 @@ lexer() ->
|
|||||||
, {"\\*/", pop(skip())}
|
, {"\\*/", pop(skip())}
|
||||||
, {"[^/*]+|[/*]", skip()} ],
|
, {"[^/*]+|[/*]", skip()} ],
|
||||||
|
|
||||||
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||||
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "namespace"],
|
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"],
|
||||||
KW = string:join(Keywords, "|"),
|
KW = string:join(Keywords, "|"),
|
||||||
|
|
||||||
Rules =
|
Rules =
|
||||||
@@ -54,7 +54,7 @@ lexer() ->
|
|||||||
, {STRING, token(string, fun parse_string/1)}
|
, {STRING, token(string, fun parse_string/1)}
|
||||||
, {HEX, token(hex, fun parse_hex/1)}
|
, {HEX, token(hex, fun parse_hex/1)}
|
||||||
, {INT, token(int, fun list_to_integer/1)}
|
, {INT, token(int, fun list_to_integer/1)}
|
||||||
, {HASH, token(hash, fun parse_hash/1)}
|
, {BYTES, token(bytes, fun parse_bytes/1)}
|
||||||
|
|
||||||
%% Identifiers (qualified first!)
|
%% Identifiers (qualified first!)
|
||||||
, {QID, token(qid, fun(S) -> string:tokens(S, ".") end)}
|
, {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_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||||
|
|
||||||
parse_hash("#" ++ Chars) ->
|
parse_bytes("#" ++ Chars) ->
|
||||||
N = list_to_integer(Chars, 16),
|
N = list_to_integer(Chars, 16),
|
||||||
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
|
Digits = (length(Chars) + 1) div 2,
|
||||||
true -> <<N:64/unit:8>>; %% signature
|
<<N:Digits/unit:8>>.
|
||||||
false -> <<N:32/unit:8>> %% address
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -25,7 +25,7 @@
|
|||||||
-type ann_origin() :: system | user.
|
-type ann_origin() :: system | user.
|
||||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||||
|
|
||||||
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
|
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()} | stateful | private].
|
||||||
|
|
||||||
-type name() :: string().
|
-type name() :: string().
|
||||||
-type id() :: {id, ann(), name()}.
|
-type id() :: {id, ann(), name()}.
|
||||||
@@ -43,8 +43,7 @@
|
|||||||
|
|
||||||
-type letbind()
|
-type letbind()
|
||||||
:: {letval, ann(), id(), type(), expr()}
|
:: {letval, ann(), id(), type(), expr()}
|
||||||
| {letfun, ann(), id(), [arg()], type(), expr()}
|
| {letfun, ann(), id(), [arg()], type(), expr()}.
|
||||||
| {letrec, ann(), [letbind()]}.
|
|
||||||
|
|
||||||
-type arg() :: {arg, ann(), id(), type()}.
|
-type arg() :: {arg, ann(), id(), type()}.
|
||||||
|
|
||||||
@@ -60,6 +59,7 @@
|
|||||||
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
-type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()}
|
||||||
| {app_t, ann(), type(), [type()]}
|
| {app_t, ann(), type(), [type()]}
|
||||||
| {tuple_t, ann(), [type()]}
|
| {tuple_t, ann(), [type()]}
|
||||||
|
| {bytes_t, ann(), integer()}
|
||||||
| id() | qid()
|
| id() | qid()
|
||||||
| con() | qcon() %% contracts
|
| con() | qcon() %% contracts
|
||||||
| tvar().
|
| tvar().
|
||||||
@@ -70,7 +70,10 @@
|
|||||||
:: {int, ann(), integer()}
|
:: {int, ann(), integer()}
|
||||||
| {bool, ann(), true | false}
|
| {bool, ann(), true | false}
|
||||||
| {hash, ann(), binary()}
|
| {hash, ann(), binary()}
|
||||||
| {unit, ann()}
|
| {account_pubkey, binary()}
|
||||||
|
| {contract_pubkey, binary()}
|
||||||
|
| {oracle_pubkey, binary()}
|
||||||
|
| {oracle_query_id, binary()}
|
||||||
| {string, ann(), binary()}
|
| {string, ann(), binary()}
|
||||||
| {char, ann(), integer()}.
|
| {char, ann(), integer()}.
|
||||||
|
|
||||||
|
|||||||
+18
-22
@@ -39,11 +39,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
||||||
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
||||||
Top = Fun(K, X),
|
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
|
Rec = case X of
|
||||||
%% lists (bound things in head scope over tail)
|
%% lists (bound things in head scope over tail)
|
||||||
[A | As] -> Scoped(Same(A), Same(As));
|
[A | As] -> Scoped(Same(A), Same(As));
|
||||||
@@ -55,7 +50,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
{fun_decl, _, _, T} -> Type(T);
|
{fun_decl, _, _, T} -> Type(T);
|
||||||
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
|
{letval, _, F, T, E} -> Sum([BindExpr(F), Type(T), Expr(E)]);
|
||||||
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
|
{letfun, _, F, Xs, T, E} -> Sum([BindExpr(F), Type(T), Scoped(BindExpr(Xs), Expr(E))]);
|
||||||
{letrec, _, Ds} -> Plus(Bound(Ds), Decl(Ds));
|
|
||||||
%% typedef()
|
%% typedef()
|
||||||
{alias_t, T} -> Type(T);
|
{alias_t, T} -> Type(T);
|
||||||
{record_t, Fs} -> Type(Fs);
|
{record_t, Fs} -> Type(Fs);
|
||||||
@@ -104,29 +98,31 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
|||||||
%% Name dependencies
|
%% Name dependencies
|
||||||
|
|
||||||
used_ids(E) ->
|
used_ids(E) ->
|
||||||
[ X || {term, [X]} <- used(E) ].
|
[ X || {{term, [X]}, _} <- used(E) ].
|
||||||
|
|
||||||
used_types(T) ->
|
used_types(T) ->
|
||||||
[ X || {type, [X]} <- used(T) ].
|
[ X || {{type, [X]}, _} <- used(T) ].
|
||||||
|
|
||||||
-type entity() :: {term, [string()]}
|
-type entity() :: {term, [string()]}
|
||||||
| {type, [string()]}
|
| {type, [string()]}
|
||||||
| {namespace, [string()]}.
|
| {namespace, [string()]}.
|
||||||
|
|
||||||
-spec entity_alg() -> alg([entity()]).
|
-spec entity_alg() -> alg(#{entity() => aeso_syntax:ann()}).
|
||||||
entity_alg() ->
|
entity_alg() ->
|
||||||
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
IsBound = fun({K, _}) -> lists:member(K, [bound_term, bound_type]) end,
|
||||||
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
Unbind = fun(bound_term) -> term; (bound_type) -> type end,
|
||||||
|
Remove = fun(Keys, Map) -> lists:foldl(fun maps:remove/2, Map, Keys) end,
|
||||||
Scoped = fun(Xs, Ys) ->
|
Scoped = fun(Xs, Ys) ->
|
||||||
{Bound, Others} = lists:partition(IsBound, Ys),
|
Bound = [E || E <- maps:keys(Ys), IsBound(E)],
|
||||||
|
Others = Remove(Bound, Ys),
|
||||||
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
||||||
lists:umerge(Xs -- Bound1, Others)
|
maps:merge(Remove(Bound1, Xs), Others)
|
||||||
end,
|
end,
|
||||||
#alg{ zero = []
|
#alg{ zero = #{}
|
||||||
, plus = fun lists:umerge/2
|
, plus = fun maps:merge/2
|
||||||
, scoped = Scoped }.
|
, scoped = Scoped }.
|
||||||
|
|
||||||
-spec used(_) -> [entity()].
|
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
|
||||||
used(D) ->
|
used(D) ->
|
||||||
Kind = fun(expr) -> term;
|
Kind = fun(expr) -> term;
|
||||||
(bind_expr) -> bound_term;
|
(bind_expr) -> bound_term;
|
||||||
@@ -134,14 +130,14 @@ used(D) ->
|
|||||||
(bind_type) -> bound_type
|
(bind_type) -> bound_type
|
||||||
end,
|
end,
|
||||||
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
NS = fun(Xs) -> {namespace, lists:droplast(Xs)} end,
|
||||||
NotBound = fun({Tag, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
NotBound = fun({{Tag, _}, _}) -> not lists:member(Tag, [bound_term, bound_type]) end,
|
||||||
Xs =
|
Xs =
|
||||||
fold(entity_alg(),
|
maps:to_list(fold(entity_alg(),
|
||||||
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
|
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
||||||
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
||||||
(K, {con, _, X}) -> [{Kind(K), [X]}];
|
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
||||||
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
||||||
(_, _) -> []
|
(_, _) -> #{}
|
||||||
end, decl, D),
|
end, decl, D)),
|
||||||
lists:filter(NotBound, Xs).
|
lists:filter(NotBound, Xs).
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{application, aesophia,
|
{application, aesophia,
|
||||||
[{description, "Contract Language for aeternity"},
|
[{description, "Contract Language for aeternity"},
|
||||||
{vsn, "2.1.0"},
|
{vsn, "3.0.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
-module(aesophia).
|
|
||||||
|
|
||||||
-export([main/1]).
|
|
||||||
|
|
||||||
-define(OPT_SPEC,
|
|
||||||
[ {src_file, undefined, undefined, string, "Sophia source code file"}
|
|
||||||
, {version, $V, "version", undefined, "Print compiler version"}
|
|
||||||
, {verbose, $v, "verbose", undefined, "Verbose output"}
|
|
||||||
, {help, $h, "help", undefined, "Show this message"}
|
|
||||||
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
|
|
||||||
|
|
||||||
usage() ->
|
|
||||||
getopt:usage(?OPT_SPEC, "aesophia").
|
|
||||||
|
|
||||||
main(Args) ->
|
|
||||||
case getopt:parse(?OPT_SPEC, Args) of
|
|
||||||
{ok, {Opts, []}} ->
|
|
||||||
case Opts of
|
|
||||||
[version] ->
|
|
||||||
print_vsn();
|
|
||||||
[help] ->
|
|
||||||
usage();
|
|
||||||
_ ->
|
|
||||||
compile(Opts)
|
|
||||||
end;
|
|
||||||
|
|
||||||
{ok, {_, NonOpts}} ->
|
|
||||||
io:format("Can't understand ~p\n\n", [NonOpts]),
|
|
||||||
usage();
|
|
||||||
|
|
||||||
{error, {Reason, Data}} ->
|
|
||||||
io:format("Error: ~s ~p\n\n", [Reason, Data]),
|
|
||||||
usage()
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
compile(Opts) ->
|
|
||||||
case proplists:get_value(src_file, Opts, undefined) of
|
|
||||||
undefined ->
|
|
||||||
io:format("Error: no input source file\n\n"),
|
|
||||||
usage();
|
|
||||||
File ->
|
|
||||||
compile(File, Opts)
|
|
||||||
end.
|
|
||||||
|
|
||||||
compile(File, Opts) ->
|
|
||||||
Verbose = proplists:get_value(verbose, Opts, false),
|
|
||||||
OutFile = proplists:get_value(outfile, Opts, undefined),
|
|
||||||
|
|
||||||
try
|
|
||||||
Res = aeso_compiler:file(File, [pp_ast || Verbose]),
|
|
||||||
write_outfile(OutFile, Res),
|
|
||||||
io:format("\nCompiled successfully!\n")
|
|
||||||
catch
|
|
||||||
%% The compiler errors.
|
|
||||||
error:{type_errors, Errors} ->
|
|
||||||
io:format("\n~s\n", [string:join(["** Type errors\n" | Errors], "\n")]);
|
|
||||||
error:{parse_errors, Errors} ->
|
|
||||||
io:format("\n~s\n", [string:join(["** Parse errors\n" | Errors], "\n")]);
|
|
||||||
error:{code_errors, Errors} ->
|
|
||||||
ErrorStrings = [ io_lib:format("~p", [E]) || E <- Errors ],
|
|
||||||
io:format("\n~s\n", [string:join(["** Code errors\n" | ErrorStrings], "\n")]);
|
|
||||||
%% General programming errors in the compiler.
|
|
||||||
error:Error ->
|
|
||||||
Where = hd(erlang:get_stacktrace()),
|
|
||||||
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error,Where]),
|
|
||||||
io:format("\n~s\n", [ErrorString])
|
|
||||||
end.
|
|
||||||
|
|
||||||
write_outfile(undefined, _) -> ok;
|
|
||||||
write_outfile(Out, ResMap) ->
|
|
||||||
%% Lazy approach
|
|
||||||
file:write_file(Out, term_to_binary(ResMap)),
|
|
||||||
io:format("Output written to: ~s\n", [Out]).
|
|
||||||
|
|
||||||
print_vsn() ->
|
|
||||||
{ok, Vsn} = aeso_compiler:version(),
|
|
||||||
io:format("Compiler version: ~s\n", [Vsn]).
|
|
||||||
+19
-4
@@ -4,6 +4,9 @@
|
|||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
|
|
||||||
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
-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) ->
|
sandbox(Code) ->
|
||||||
Parent = self(),
|
Parent = self(),
|
||||||
@@ -97,7 +100,15 @@ calldata_test() ->
|
|||||||
Map = #{ <<"a">> => 4 },
|
Map = #{ <<"a">> => 4 },
|
||||||
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||||
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
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.
|
ok.
|
||||||
|
|
||||||
calldata_init_test() ->
|
calldata_init_test() ->
|
||||||
@@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) ->
|
|||||||
|
|
||||||
parameterized_contract(ExtraCode, FunName, Types) ->
|
parameterized_contract(ExtraCode, FunName, Types) ->
|
||||||
lists:flatten(
|
lists:flatten(
|
||||||
["contract Dummy =\n",
|
["contract Remote =\n"
|
||||||
|
" function bla : () => ()\n\n"
|
||||||
|
"contract Dummy =\n",
|
||||||
ExtraCode, "\n",
|
ExtraCode, "\n",
|
||||||
" type an_alias('a) = (string, 'a)\n"
|
" type an_alias('a) = (string, 'a)\n"
|
||||||
" record r = {x : an_alias(int), y : variant}\n"
|
" record r = {x : an_alias(int), y : variant}\n"
|
||||||
@@ -139,13 +152,15 @@ oracle_test() ->
|
|||||||
" function question(o, q : oracle_query(list(string), option(int))) =\n"
|
" function question(o, q : oracle_query(list(string), option(int))) =\n"
|
||||||
" Oracle.get_question(o, q)\n",
|
" Oracle.get_question(o, q)\n",
|
||||||
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
|
||||||
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
|
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
|
||||||
|
"oq_1111111111111111111111111111113AFEFpt5"], []),
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
permissive_literals_fail_test() ->
|
permissive_literals_fail_test() ->
|
||||||
Contract =
|
Contract =
|
||||||
"contract OracleTest =\n"
|
"contract OracleTest =\n"
|
||||||
" function haxx(o : oracle(list(string), option(int))) =\n"
|
" stateful function haxx(o : oracle(list(string), option(int))) =\n"
|
||||||
" Chain.spend(o, 1000000)\n",
|
" Chain.spend(o, 1000000)\n",
|
||||||
{error, <<"Type errors\nCannot unify", _/binary>>} =
|
{error, <<"Type errors\nCannot unify", _/binary>>} =
|
||||||
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
-module(aeso_compiler_tests).
|
-module(aeso_compiler_tests).
|
||||||
|
|
||||||
|
-compile([export_all, nowarn_export_all]).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
@@ -16,10 +18,14 @@
|
|||||||
simple_compile_test_() ->
|
simple_compile_test_() ->
|
||||||
[ {"Testing the " ++ ContractName ++ " contract",
|
[ {"Testing the " ++ ContractName ++ " contract",
|
||||||
fun() ->
|
fun() ->
|
||||||
#{byte_code := ByteCode,
|
case compile(ContractName) of
|
||||||
contract_source := _,
|
#{byte_code := ByteCode,
|
||||||
type_info := _} = compile(ContractName),
|
contract_source := _,
|
||||||
?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() ] ++
|
end} || ContractName <- compilable_contracts() ] ++
|
||||||
[ {"Testing error messages of " ++ ContractName,
|
[ {"Testing error messages of " ++ ContractName,
|
||||||
fun() ->
|
fun() ->
|
||||||
@@ -80,6 +86,7 @@ compilable_contracts() ->
|
|||||||
"dutch_auction",
|
"dutch_auction",
|
||||||
"environment",
|
"environment",
|
||||||
"factorial",
|
"factorial",
|
||||||
|
"functions",
|
||||||
"fundme",
|
"fundme",
|
||||||
"identity",
|
"identity",
|
||||||
"maps",
|
"maps",
|
||||||
@@ -98,7 +105,11 @@ compilable_contracts() ->
|
|||||||
"state_handling",
|
"state_handling",
|
||||||
"events",
|
"events",
|
||||||
"include",
|
"include",
|
||||||
"basic_auth"
|
"basic_auth",
|
||||||
|
"bitcoin_auth",
|
||||||
|
"address_literals",
|
||||||
|
"bytes_equality",
|
||||||
|
"address_chain"
|
||||||
].
|
].
|
||||||
|
|
||||||
%% Contracts that should produce type errors
|
%% Contracts that should produce type errors
|
||||||
@@ -180,6 +191,8 @@ failing_contracts() ->
|
|||||||
" - r' (at line 5, column 10)">>,
|
" - r' (at line 5, column 10)">>,
|
||||||
<<"Repeated name x in pattern\n"
|
<<"Repeated name x in pattern\n"
|
||||||
" x :: x (at line 26, column 7)">>,
|
" x :: x (at line 26, column 7)">>,
|
||||||
|
<<"Repeated argument x to function repeated_arg (at line 44, column 12).">>,
|
||||||
|
<<"Repeated argument y to function repeated_arg (at line 44, column 12).">>,
|
||||||
<<"No record type with fields y, z (at line 14, column 22)">>,
|
<<"No record type with fields y, z (at line 14, column 22)">>,
|
||||||
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
<<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
|
||||||
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
|
<<"Record type r2 does not have field y (at line 15, column 22)">>]}
|
||||||
@@ -218,4 +231,102 @@ failing_contracts() ->
|
|||||||
, {"bad_include_and_ns",
|
, {"bad_include_and_ns",
|
||||||
[<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>,
|
[<<"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.">>]}
|
<<"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)">>]}
|
||||||
|
, {"stateful",
|
||||||
|
[<<"Cannot reference stateful function Chain.spend (at line 13, column 33)\nin the definition of non-stateful function fail1.">>,
|
||||||
|
<<"Cannot reference stateful function local_spend (at line 14, column 33)\nin the definition of non-stateful function fail2.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 16, column 15)\nin the definition of non-stateful function fail3.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 20, column 31)\nin the definition of non-stateful function fail4.">>,
|
||||||
|
<<"Cannot reference stateful function Chain.spend (at line 35, column 53)\nin the definition of non-stateful function fail5.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 48, column 55)\nin the definition of non-stateful function fail6.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 49, column 54)\nin the definition of non-stateful function fail7.">>,
|
||||||
|
<<"Cannot pass non-zero value argument 1000 (at line 52, column 17)\nin the definition of non-stateful function fail8.">>]}
|
||||||
|
, {"bad_init_state_access",
|
||||||
|
[<<"The init function should return the initial state as its result and cannot write the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - set_state (at line 11, column 5), which calls\n"
|
||||||
|
" - roundabout (at line 8, column 36), which calls\n"
|
||||||
|
" - put (at line 7, column 37)">>,
|
||||||
|
<<"The init function should return the initial state as its result and cannot read the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - new_state (at line 12, column 5), which calls\n"
|
||||||
|
" - state (at line 5, column 27)">>,
|
||||||
|
<<"The init function should return the initial state as its result and cannot read the state,\n"
|
||||||
|
"but it calls\n"
|
||||||
|
" - state (at line 13, column 13)">>]}
|
||||||
].
|
].
|
||||||
|
|||||||
@@ -41,14 +41,14 @@ all_tokens() ->
|
|||||||
%% Operators
|
%% Operators
|
||||||
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
|
lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++
|
||||||
%% Keywords
|
%% Keywords
|
||||||
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
|
lists:map(Lit, [contract, type, 'let', switch]) ++
|
||||||
%% Comment token (not an actual token), just for tests
|
%% Comment token (not an actual token), just for tests
|
||||||
[{comment, 0, "// *Comment!\"\n"},
|
[{comment, 0, "// *Comment!\"\n"},
|
||||||
{comment, 0, "/* bla /* bla bla */*/"}] ++
|
{comment, 0, "/* bla /* bla bla */*/"}] ++
|
||||||
%% Literals
|
%% Literals
|
||||||
[ Lit(true), Lit(false)
|
[ Lit(true), Lit(false)
|
||||||
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
, Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo")
|
||||||
, Tok(hash, Hash)
|
, Tok(bytes, Hash)
|
||||||
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
, Tok(int, 1234567890), Tok(hex, 9876543210)
|
||||||
, Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>)
|
, 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({string, _, S}) -> fmt(binary_to_list(S));
|
||||||
show_token({int, _, N}) -> fmt(N);
|
show_token({int, _, N}) -> fmt(N);
|
||||||
show_token({hex, _, N}) -> fmt("0x~.16b", 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({comment, _, S}) -> S;
|
||||||
show_token({_, _, _}) -> "TODO".
|
show_token({_, _, _}) -> "TODO".
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
contract Remote =
|
||||||
|
function main : (int) => ()
|
||||||
|
|
||||||
|
contract AddrChain =
|
||||||
|
type o_type = oracle(string, map(string, int))
|
||||||
|
type oq_type = oracle_query(string, map(string, int))
|
||||||
|
|
||||||
|
function is_o(a : address) =
|
||||||
|
Address.is_oracle(a)
|
||||||
|
|
||||||
|
function is_c(a : address) =
|
||||||
|
Address.is_contract(a)
|
||||||
|
|
||||||
|
// function get_o(a : address) : option(o_type) =
|
||||||
|
// Address.get_oracle(a)
|
||||||
|
|
||||||
|
// function get_c(a : address) : option(Remote) =
|
||||||
|
// Address.get_contract(a)
|
||||||
|
|
||||||
|
function check_o(o : o_type) =
|
||||||
|
Oracle.check(o)
|
||||||
|
|
||||||
|
function check_oq(o : o_type, oq : oq_type) =
|
||||||
|
Oracle.check_query(o, oq)
|
||||||
|
|
||||||
|
// function h_to_i(h : hash) : int =
|
||||||
|
// Hash.to_int(h)
|
||||||
|
|
||||||
|
// function a_to_i(a : address) : int =
|
||||||
|
// Address.to_int(a) mod 10 ^ 16
|
||||||
|
|
||||||
|
function c_creator() : address =
|
||||||
|
Contract.creator
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -36,11 +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 mutual() =
|
|
||||||
let rec recFun(x : int) = mutFun(x)
|
|
||||||
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)
|
|
||||||
recFun(0)
|
|
||||||
|
|
||||||
let hash : address = #01ab0fff11
|
let hash : address = #01ab0fff11
|
||||||
let b = false
|
let b = false
|
||||||
let qcon = Mod.Con
|
let qcon = Mod.Con
|
||||||
|
|||||||
@@ -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,13 @@
|
|||||||
|
contract BadInit =
|
||||||
|
|
||||||
|
type state = int
|
||||||
|
|
||||||
|
function new_state(n) = state + n
|
||||||
|
|
||||||
|
stateful function roundabout(n) = put(n)
|
||||||
|
stateful function set_state(n) = roundabout(n)
|
||||||
|
|
||||||
|
stateful function init() =
|
||||||
|
set_state(4)
|
||||||
|
new_state(0)
|
||||||
|
state + state
|
||||||
@@ -4,7 +4,7 @@ contract BasicAuth =
|
|||||||
|
|
||||||
function init() = { nonce = 1, owner = Call.caller }
|
function init() = { nonce = 1, owner = Call.caller }
|
||||||
|
|
||||||
function authorize(n : int, s : signature) : bool =
|
stateful function authorize(n : int, s : signature) : bool =
|
||||||
require(n >= state.nonce, "Nonce too low")
|
require(n >= state.nonce, "Nonce too low")
|
||||||
require(n =< state.nonce, "Nonce too high")
|
require(n =< state.nonce, "Nonce too high")
|
||||||
put(state{ nonce = n + 1 })
|
put(state{ nonce = n + 1 })
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
contract BitcoinAuth =
|
||||||
|
record state = { nonce : int, owner : bytes(64) }
|
||||||
|
|
||||||
|
function init(owner' : bytes(64)) = { nonce = 1, owner = owner' }
|
||||||
|
|
||||||
|
stateful 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)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
contract BytesEquality =
|
||||||
|
|
||||||
|
function eq16(a : bytes(16), b) = a == b
|
||||||
|
function ne16(a : bytes(16), b) = a != b
|
||||||
|
|
||||||
|
function eq32(a : bytes(32), b) = a == b
|
||||||
|
function ne32(a : bytes(32), b) = a != b
|
||||||
|
|
||||||
|
function eq47(a : bytes(47), b) = a == b
|
||||||
|
function ne47(a : bytes(47), b) = a != b
|
||||||
|
|
||||||
|
function eq64(a : bytes(64), b) = a == b
|
||||||
|
function ne64(a : bytes(64), b) = a != b
|
||||||
|
|
||||||
|
function eq65(a : bytes(65), b) = a == b
|
||||||
|
function ne65(a : bytes(65), b) = a != b
|
||||||
|
|
||||||
@@ -5,5 +5,5 @@ contract Counter =
|
|||||||
|
|
||||||
function init(val) = { value = val }
|
function init(val) = { value = val }
|
||||||
function get() = state.value
|
function get() = state.value
|
||||||
function tick() = put(state{ value = state.value + 1 })
|
stateful function tick() = put(state{ value = state.value + 1 })
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ contract DutchAuction =
|
|||||||
sold : bool }
|
sold : bool }
|
||||||
|
|
||||||
// Add to work around current lack of predefined functions
|
// Add to work around current lack of predefined functions
|
||||||
private function spend(to, amount) =
|
private stateful function spend(to, amount) =
|
||||||
let total = Contract.balance
|
let total = Contract.balance
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
total - amount
|
total - amount
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ contract Environment =
|
|||||||
|
|
||||||
function init(remote) = {remote = remote}
|
function init(remote) = {remote = remote}
|
||||||
|
|
||||||
function set_remote(remote) = put({remote = remote})
|
stateful function set_remote(remote) = put({remote = remote})
|
||||||
|
|
||||||
// -- Information about the this contract ---
|
// -- Information about the this contract ---
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ contract Environment =
|
|||||||
|
|
||||||
// Value
|
// Value
|
||||||
function call_value() : int = Call.value
|
function call_value() : int = Call.value
|
||||||
function nested_value(value : int) : int =
|
stateful function nested_value(value : int) : int =
|
||||||
state.remote.call_value(value = value / 2)
|
state.remote.call_value(value = value / 2)
|
||||||
|
|
||||||
// Gas price
|
// Gas price
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ contract Factorial =
|
|||||||
|
|
||||||
function init(worker) = {worker = worker}
|
function init(worker) = {worker = worker}
|
||||||
|
|
||||||
function set_worker(worker) = put(state{worker = worker})
|
stateful function set_worker(worker) = put(state{worker = worker})
|
||||||
|
|
||||||
function fac(x : int) : int =
|
function fac(x : int) : int =
|
||||||
if(x == 0) 1
|
if(x == 0) 1
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
contract Functions =
|
||||||
|
private function curry(f : ('a, 'b) => 'c) =
|
||||||
|
(x) => (y) => f(x, y)
|
||||||
|
private function map(f : 'a => 'b, xs : list('a)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => []
|
||||||
|
x :: xs => f(x) :: map(f, xs)
|
||||||
|
private function map'() = map
|
||||||
|
private function plus(x, y) = x + y
|
||||||
|
function test1(xs : list(int)) = map(curry(plus)(5), xs)
|
||||||
|
function test2(xs : list(int)) = map'()(((x) => (y) => ((x, y) => x + y)(x, y))(100), xs)
|
||||||
|
function test3(xs : list(int)) =
|
||||||
|
let m(f, xs) = map(f, xs)
|
||||||
|
m((x) => x + 1, xs)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ contract FundMe =
|
|||||||
private function require(b : bool, err : string) =
|
private function require(b : bool, err : string) =
|
||||||
if(!b) abort(err)
|
if(!b) abort(err)
|
||||||
|
|
||||||
private function spend(args : spend_args) =
|
private stateful function spend(args : spend_args) =
|
||||||
Chain.spend(args.recipient, args.amount)
|
Chain.spend(args.recipient, args.amount)
|
||||||
|
|
||||||
public function init(beneficiary, deadline, goal) : state =
|
public function init(beneficiary, deadline, goal) : state =
|
||||||
@@ -53,7 +53,7 @@ contract FundMe =
|
|||||||
require(state.total >= state.goal, "Project was not funded")
|
require(state.total >= state.goal, "Project was not funded")
|
||||||
spend({recipient = state.beneficiary,
|
spend({recipient = state.beneficiary,
|
||||||
amount = Contract.balance })
|
amount = Contract.balance })
|
||||||
put(state{ beneficiary = #0 })
|
put(state{ beneficiary = ak_11111111111111111111111111111111273Yts })
|
||||||
|
|
||||||
private stateful function withdraw_contributor() =
|
private stateful function withdraw_contributor() =
|
||||||
if(state.total >= state.goal)
|
if(state.total >= state.goal)
|
||||||
|
|||||||
+12
-12
@@ -17,8 +17,8 @@ contract Maps =
|
|||||||
{ ["one"] = {x = 1, y = 2},
|
{ ["one"] = {x = 1, y = 2},
|
||||||
["two"] = {x = 3, y = 4},
|
["two"] = {x = 3, y = 4},
|
||||||
["three"] = {x = 5, y = 6} }
|
["three"] = {x = 5, y = 6} }
|
||||||
function map_state_i() = put(state{ map_i = map_i() })
|
stateful function map_state_i() = put(state{ map_i = map_i() })
|
||||||
function map_state_s() = put(state{ map_s = map_s() })
|
stateful function map_state_s() = put(state{ map_s = map_s() })
|
||||||
|
|
||||||
// m[k]
|
// m[k]
|
||||||
function get_i(k, m : map(int, pt)) = m[k]
|
function get_i(k, m : map(int, pt)) = m[k]
|
||||||
@@ -35,20 +35,20 @@ contract Maps =
|
|||||||
// m{[k] = v}
|
// m{[k] = v}
|
||||||
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
|
function set_i(k, p, m : map(int, pt)) = m{ [k] = p }
|
||||||
function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
|
function set_s(k, p, m : map(string, pt)) = m{ [k] = p }
|
||||||
function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
|
stateful function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
|
||||||
function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
|
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
|
||||||
|
|
||||||
// m{f[k].x = v}
|
// m{f[k].x = v}
|
||||||
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
|
function setx_i(k, x, m : map(int, pt)) = m{ [k].x = x }
|
||||||
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
|
function setx_s(k, x, m : map(string, pt)) = m{ [k].x = x }
|
||||||
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
stateful function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
||||||
function setx_state_s(k, x) = put(state{ map_s[k].x = x })
|
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x })
|
||||||
|
|
||||||
// m{[k] @ x = v }
|
// m{[k] @ x = v }
|
||||||
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
|
function addx_i(k, d, m : map(int, pt)) = m{ [k].x @ x = x + d }
|
||||||
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
|
function addx_s(k, d, m : map(string, pt)) = m{ [k].x @ x = x + d }
|
||||||
function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
|
stateful function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
|
||||||
function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
|
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
|
||||||
|
|
||||||
// m{[k = def] @ x = v }
|
// m{[k = def] @ x = v }
|
||||||
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
|
function addx_def_i(k, v, d, m : map(int, pt)) = m{ [k = v].x @ x = x + d }
|
||||||
@@ -77,8 +77,8 @@ contract Maps =
|
|||||||
// Map.delete
|
// Map.delete
|
||||||
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
|
function delete_i(k, m : map(int, pt)) = Map.delete(k, m)
|
||||||
function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
|
function delete_s(k, m : map(string, pt)) = Map.delete(k, m)
|
||||||
function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
|
stateful function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
|
||||||
function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
|
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
|
||||||
|
|
||||||
// Map.size
|
// Map.size
|
||||||
function size_i(m : map(int, pt)) = Map.size(m)
|
function size_i(m : map(int, pt)) = Map.size(m)
|
||||||
@@ -95,6 +95,6 @@ contract Maps =
|
|||||||
// Map.from_list
|
// Map.from_list
|
||||||
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
|
function fromlist_i(xs : list((int, pt))) = Map.from_list(xs)
|
||||||
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
|
function fromlist_s(xs : list((string, pt))) = Map.from_list(xs)
|
||||||
function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
||||||
function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -9,22 +9,22 @@ contract Oracles =
|
|||||||
type oracle_id = oracle(query_t, answer_t)
|
type oracle_id = oracle(query_t, answer_t)
|
||||||
type query_id = oracle_query(query_t, answer_t)
|
type query_id = oracle_query(query_t, answer_t)
|
||||||
|
|
||||||
function registerOracle(acct : address,
|
stateful function registerOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle_id =
|
ttl : ttl) : oracle_id =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function registerIntIntOracle(acct : address,
|
stateful function registerIntIntOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle(int, int) =
|
ttl : ttl) : oracle(int, int) =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function registerStringStringOracle(acct : address,
|
stateful function registerStringStringOracle(acct : address,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle(string, string) =
|
ttl : ttl) : oracle(string, string) =
|
||||||
Oracle.register(acct, qfee, ttl)
|
Oracle.register(acct, qfee, ttl)
|
||||||
|
|
||||||
function signedRegisterOracle(acct : address,
|
stateful function signedRegisterOracle(acct : address,
|
||||||
sign : signature,
|
sign : signature,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
ttl : ttl) : oracle_id =
|
ttl : ttl) : oracle_id =
|
||||||
@@ -33,7 +33,7 @@ contract Oracles =
|
|||||||
function queryFee(o : oracle_id) : fee =
|
function queryFee(o : oracle_id) : fee =
|
||||||
Oracle.query_fee(o)
|
Oracle.query_fee(o)
|
||||||
|
|
||||||
function createQuery(o : oracle_id,
|
stateful function createQuery(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -42,7 +42,7 @@ contract Oracles =
|
|||||||
Oracle.query(o, q, qfee, qttl, rttl)
|
Oracle.query(o, q, qfee, qttl, rttl)
|
||||||
|
|
||||||
// Do not use in production!
|
// Do not use in production!
|
||||||
function unsafeCreateQuery(o : oracle_id,
|
stateful function unsafeCreateQuery(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -50,7 +50,7 @@ contract Oracles =
|
|||||||
Oracle.query(o, q, qfee, qttl, rttl)
|
Oracle.query(o, q, qfee, qttl, rttl)
|
||||||
|
|
||||||
// Do not use in production!
|
// Do not use in production!
|
||||||
function unsafeCreateQueryThenErr(o : oracle_id,
|
stateful function unsafeCreateQueryThenErr(o : oracle_id,
|
||||||
q : query_t,
|
q : query_t,
|
||||||
qfee : fee,
|
qfee : fee,
|
||||||
qttl : ttl,
|
qttl : ttl,
|
||||||
@@ -59,21 +59,21 @@ contract Oracles =
|
|||||||
require(qfee >= 100000000000000000, "causing a late error")
|
require(qfee >= 100000000000000000, "causing a late error")
|
||||||
res
|
res
|
||||||
|
|
||||||
function extendOracle(o : oracle_id,
|
stateful function extendOracle(o : oracle_id,
|
||||||
ttl : ttl) : () =
|
ttl : ttl) : () =
|
||||||
Oracle.extend(o, ttl)
|
Oracle.extend(o, ttl)
|
||||||
|
|
||||||
function signedExtendOracle(o : oracle_id,
|
stateful function signedExtendOracle(o : oracle_id,
|
||||||
sign : signature, // Signed oracle address
|
sign : signature, // Signed oracle address
|
||||||
ttl : ttl) : () =
|
ttl : ttl) : () =
|
||||||
Oracle.extend(o, signature = sign, ttl)
|
Oracle.extend(o, signature = sign, ttl)
|
||||||
|
|
||||||
function respond(o : oracle_id,
|
stateful function respond(o : oracle_id,
|
||||||
q : query_id,
|
q : query_id,
|
||||||
r : answer_t) : () =
|
r : answer_t) : () =
|
||||||
Oracle.respond(o, q, r)
|
Oracle.respond(o, q, r)
|
||||||
|
|
||||||
function signedRespond(o : oracle_id,
|
stateful function signedRespond(o : oracle_id,
|
||||||
q : query_id,
|
q : query_id,
|
||||||
sign : signature,
|
sign : signature,
|
||||||
r : answer_t) : () =
|
r : answer_t) : () =
|
||||||
@@ -96,13 +96,13 @@ contract Oracles =
|
|||||||
datatype complexQuestion = Why(int) | How(string)
|
datatype complexQuestion = Why(int) | How(string)
|
||||||
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
|
datatype complexAnswer = NoAnswer | Answer(complexQuestion, string, int)
|
||||||
|
|
||||||
function complexOracle(question) =
|
stateful function complexOracle(question) =
|
||||||
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
let o = Oracle.register(Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
||||||
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||||
Oracle.respond(o, q, Answer(question, "magic", 1337))
|
Oracle.respond(o, q, Answer(question, "magic", 1337))
|
||||||
Oracle.get_answer(o, q)
|
Oracle.get_answer(o, q)
|
||||||
|
|
||||||
function signedComplexOracle(question, sig) =
|
stateful function signedComplexOracle(question, sig) =
|
||||||
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
let o = Oracle.register(signature = sig, Contract.address, 0, FixedTTL(1000)) : oracle(complexQuestion, complexAnswer)
|
||||||
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
let q = Oracle.query(o, question, 0, RelativeTTL(100), RelativeTTL(100))
|
||||||
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
|
Oracle.respond(o, q, Answer(question, "magic", 1337), signature = sig)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ contract Remote3 =
|
|||||||
|
|
||||||
contract RemoteCall =
|
contract RemoteCall =
|
||||||
|
|
||||||
function call(r : Remote1, x : int) : int =
|
stateful function call(r : Remote1, x : int) : int =
|
||||||
r.main(gas = 10000, value = 10, x)
|
r.main(gas = 10000, value = 10, x)
|
||||||
|
|
||||||
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
function staged_call(r1 : Remote1, r2 : Remote2, x : int) =
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
contract Oracles =
|
|
||||||
|
|
||||||
function registerOracle :
|
|
||||||
(address,
|
|
||||||
int,
|
|
||||||
Chain.ttl) => oracle(string, int)
|
|
||||||
|
|
||||||
function createQuery :
|
|
||||||
(oracle(string, int),
|
|
||||||
string,
|
|
||||||
int,
|
|
||||||
Chain.ttl,
|
|
||||||
Chain.ttl) => oracle_query(string, int)
|
|
||||||
|
|
||||||
function unsafeCreateQuery :
|
|
||||||
(oracle(string, int),
|
|
||||||
string,
|
|
||||||
int,
|
|
||||||
Chain.ttl,
|
|
||||||
Chain.ttl) => oracle_query(string, int)
|
|
||||||
|
|
||||||
function respond :
|
|
||||||
(oracle(string, int),
|
|
||||||
oracle_query(string, int),
|
|
||||||
int) => ()
|
|
||||||
|
|
||||||
contract OraclesErr =
|
|
||||||
|
|
||||||
function unsafeCreateQueryThenErr :
|
|
||||||
(oracle(string, int),
|
|
||||||
string,
|
|
||||||
int,
|
|
||||||
Chain.ttl,
|
|
||||||
Chain.ttl) => oracle_query(string, int)
|
|
||||||
|
|
||||||
contract RemoteOracles =
|
|
||||||
|
|
||||||
public function callRegisterOracle(
|
|
||||||
r : Oracles,
|
|
||||||
acct : address,
|
|
||||||
qfee : int,
|
|
||||||
ttl : Chain.ttl) : oracle(string, int) =
|
|
||||||
r.registerOracle(acct, qfee, ttl)
|
|
||||||
|
|
||||||
public function callCreateQuery(
|
|
||||||
r : Oracles,
|
|
||||||
value : int,
|
|
||||||
o : oracle(string, int),
|
|
||||||
q : string,
|
|
||||||
qfee : int,
|
|
||||||
qttl : Chain.ttl,
|
|
||||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
|
||||||
require(value =< Call.value, "insufficient value")
|
|
||||||
r.createQuery(value = value, o, q, qfee, qttl, rttl)
|
|
||||||
|
|
||||||
// Do not use in production!
|
|
||||||
public function callUnsafeCreateQuery(
|
|
||||||
r : Oracles,
|
|
||||||
value : int,
|
|
||||||
o : oracle(string, int),
|
|
||||||
q : string,
|
|
||||||
qfee : int,
|
|
||||||
qttl : Chain.ttl,
|
|
||||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
|
||||||
r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
|
|
||||||
|
|
||||||
// Do not use in production!
|
|
||||||
public function callUnsafeCreateQueryThenErr(
|
|
||||||
r : OraclesErr,
|
|
||||||
value : int,
|
|
||||||
o : oracle(string, int),
|
|
||||||
q : string,
|
|
||||||
qfee : int,
|
|
||||||
qttl : Chain.ttl,
|
|
||||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
|
||||||
r.unsafeCreateQueryThenErr(value = value, o, q, qfee, qttl, rttl)
|
|
||||||
|
|
||||||
// Do not use in production!
|
|
||||||
public function callUnsafeCreateQueryAndThenErr(
|
|
||||||
r : Oracles,
|
|
||||||
value : int,
|
|
||||||
o : oracle(string, int),
|
|
||||||
q : string,
|
|
||||||
qfee : int,
|
|
||||||
qttl : Chain.ttl,
|
|
||||||
rttl : Chain.ttl) : oracle_query(string, int) =
|
|
||||||
let x = r.unsafeCreateQuery(value = value, o, q, qfee, qttl, rttl)
|
|
||||||
switch(0) 1 => ()
|
|
||||||
x // Never reached.
|
|
||||||
|
|
||||||
public function callRespond(
|
|
||||||
r : Oracles,
|
|
||||||
o : oracle(string, int),
|
|
||||||
q : oracle_query(string, int),
|
|
||||||
qr : int) =
|
|
||||||
r.respond(o, q, qr)
|
|
||||||
|
|
||||||
private function require(b : bool, err : string) =
|
|
||||||
if(!b) abort(err)
|
|
||||||
@@ -24,5 +24,5 @@ contract SimpleStorage =
|
|||||||
|
|
||||||
function get() : int = state.data
|
function get() : int = state.data
|
||||||
|
|
||||||
function set(value : int) =
|
stateful function set(value : int) =
|
||||||
put(state{data = value})
|
put(state{data = value})
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ contract SpendContract =
|
|||||||
|
|
||||||
contract SpendTest =
|
contract SpendTest =
|
||||||
|
|
||||||
function spend(to, amount) =
|
stateful function spend(to, amount) =
|
||||||
let total = Contract.balance
|
let total = Contract.balance
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
total - amount
|
total - amount
|
||||||
|
|
||||||
function withdraw(amount) : int =
|
stateful function withdraw(amount) : int =
|
||||||
spend(Call.caller, amount)
|
spend(Call.caller, amount)
|
||||||
|
|
||||||
function withdraw_from(account, amount) =
|
stateful function withdraw_from(account, amount) =
|
||||||
account.withdraw(amount)
|
account.withdraw(amount)
|
||||||
withdraw(amount)
|
withdraw(amount)
|
||||||
|
|
||||||
function spend_from(from, to, amount) =
|
stateful function spend_from(from, to, amount) =
|
||||||
from.withdraw(amount)
|
from.withdraw(amount)
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
Chain.balance(to)
|
Chain.balance(to)
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ contract StateHandling =
|
|||||||
function read_s() = state.s
|
function read_s() = state.s
|
||||||
function read_m() = state.m
|
function read_m() = state.m
|
||||||
|
|
||||||
function update(new_state : state) = put(new_state)
|
stateful function update(new_state : state) = put(new_state)
|
||||||
function update_i(new_i) = put(state{ i = new_i })
|
stateful function update_i(new_i) = put(state{ i = new_i })
|
||||||
function update_s(new_s) = put(state{ s = new_s })
|
stateful function update_s(new_s) = put(state{ s = new_s })
|
||||||
function update_m(new_m) = put(state{ m = new_m })
|
stateful function update_m(new_m) = put(state{ m = new_m })
|
||||||
|
|
||||||
function pass_it(r : Remote) = r.look_at(state)
|
function pass_it(r : Remote) = r.look_at(state)
|
||||||
function nop(r : Remote) = put(state{ i = state.i })
|
stateful function nop(r : Remote) = put(state{ i = state.i })
|
||||||
function return_it_s(r : Remote, big : bool) =
|
function return_it_s(r : Remote, big : bool) =
|
||||||
let x = r.return_s(big)
|
let x = r.return_s(big)
|
||||||
String.length(x)
|
String.length(x)
|
||||||
@@ -50,10 +50,10 @@ contract StateHandling =
|
|||||||
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
|
function pass_update_s(r : Remote, s) = r.fun_update_s(state, s)
|
||||||
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
|
function pass_update_m(r : Remote, m) = r.fun_update_m(state, m)
|
||||||
|
|
||||||
function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
|
stateful function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
|
||||||
function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
|
stateful function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
|
||||||
function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
|
stateful function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
|
||||||
function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
|
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
|
||||||
|
|
||||||
// remote called
|
// remote called
|
||||||
function look_at(s : state) = ()
|
function look_at(s : state) = ()
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
contract Remote =
|
||||||
|
stateful function remote_spend : (address, int) => ()
|
||||||
|
function remote_pure : int => int
|
||||||
|
|
||||||
|
contract Stateful =
|
||||||
|
|
||||||
|
private function pure(x) = x + 1
|
||||||
|
private stateful function local_spend(a) =
|
||||||
|
Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// Non-stateful functions cannot mention stateful functions
|
||||||
|
function fail1(a : address) = Chain.spend(a, 1000)
|
||||||
|
function fail2(a : address) = local_spend(a)
|
||||||
|
function fail3(a : address) =
|
||||||
|
let foo = Chain.spend
|
||||||
|
foo(a, 1000)
|
||||||
|
|
||||||
|
// Private functions must also be annotated
|
||||||
|
private function fail4(a) = Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// If annotated, stateful functions are allowed
|
||||||
|
stateful function ok1(a : address) = Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// And pure functions are always allowed
|
||||||
|
stateful function ok2(a : address) = pure(5)
|
||||||
|
stateful function ok3(a : address) =
|
||||||
|
let foo = pure
|
||||||
|
foo(5)
|
||||||
|
|
||||||
|
// No error here (fail4 is annotated as not stateful)
|
||||||
|
function ok4(a : address) = fail4(a)
|
||||||
|
|
||||||
|
// Lamdbas are checked at the construction site
|
||||||
|
private function fail5() : address => () = (a) => Chain.spend(a, 1000)
|
||||||
|
|
||||||
|
// .. so you can pass a stateful lambda to a non-stateful higher-order
|
||||||
|
// function:
|
||||||
|
private function apply(f : 'a => 'b, x) = f(x)
|
||||||
|
stateful function ok5(a : address) =
|
||||||
|
apply((val) => Chain.spend(a, val), 1000)
|
||||||
|
|
||||||
|
// It doesn't matter if remote calls are stateful or not
|
||||||
|
function ok6(r : Remote) = r.remote_spend(Contract.address, 1000)
|
||||||
|
function ok7(r : Remote) = r.remote_pure(5)
|
||||||
|
|
||||||
|
// But you can't send any tokens if not stateful
|
||||||
|
function fail6(r : Remote) = r.remote_spend(value = 1000, Contract.address, 1000)
|
||||||
|
function fail7(r : Remote) = r.remote_pure(value = 1000, 5)
|
||||||
|
function fail8(r : Remote) =
|
||||||
|
let foo = r.remote_pure
|
||||||
|
foo(value = 1000, 5)
|
||||||
|
function ok8(r : Remote) = r.remote_spend(Contract.address, 1000, value = 0)
|
||||||
|
|
||||||
@@ -40,3 +40,5 @@ contract Test =
|
|||||||
|
|
||||||
function type_error(r, x) =
|
function type_error(r, x) =
|
||||||
set_x(set_x(x, r), x)
|
set_x(set_x(x, r), x)
|
||||||
|
|
||||||
|
function repeated_arg(x : int, y, x : string, y : bool) : string = x
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ contract VariantTypes =
|
|||||||
|
|
||||||
function require(b) = if(!b) abort("required")
|
function require(b) = if(!b) abort("required")
|
||||||
|
|
||||||
function start(bal : int) =
|
stateful function start(bal : int) =
|
||||||
switch(state)
|
switch(state)
|
||||||
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
|
Stopped => put(Started({owner = Call.caller, balance = bal, color = Grey(0)}))
|
||||||
|
|
||||||
function stop() =
|
stateful function stop() =
|
||||||
switch(state)
|
switch(state)
|
||||||
Started(st) =>
|
Started(st) =>
|
||||||
require(Call.caller == st.owner)
|
require(Call.caller == st.owner)
|
||||||
@@ -23,7 +23,7 @@ contract VariantTypes =
|
|||||||
st.balance
|
st.balance
|
||||||
|
|
||||||
function get_color() = switch(state) Started(st) => st.color
|
function get_color() = switch(state) Started(st) => st.color
|
||||||
function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
||||||
|
|
||||||
function get_state() = state
|
function get_state() = state
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user