Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd6dac2257 | |||
| b2a0c9d8be | |||
| 078af45c92 |
@@ -1,61 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
### Added
|
|
||||||
### Changed
|
|
||||||
### 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
|
|
||||||
### Added
|
|
||||||
- Stubs (not yet wired up) for compilation to FATE
|
|
||||||
- Add functions specific for Calldata decoding
|
|
||||||
- Support for `Auth.tx_hash`, not available in AEVM until Fortuna release
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Improvements to the ACI generator
|
|
||||||
|
|
||||||
## [2.0.0] - 2019-03-11
|
|
||||||
### Added
|
|
||||||
- Add `Crypto.ecverify` to the compiler.
|
|
||||||
- Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
|
|
||||||
`String.sha256` to the compiler.
|
|
||||||
- Add the `bits` type for working with bit fields in Sophia.
|
|
||||||
- Add Namespaces to Sophia in order to simplify using library contracts, etc.
|
|
||||||
- Add a missig type check on the `init` function - detects programmer errors earlier.
|
|
||||||
- Add the ACI (Aeternity Contract Interface) generator.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Use native bit shift operations in builtin functions, reducing gas cost.
|
|
||||||
- Improve type checking of `record` fields - generates more understandable error messages.
|
|
||||||
- Improved, more coherent, error messages.
|
|
||||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
|
||||||
pass a (stubbed) contract string.
|
|
||||||
|
|
||||||
[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.0.0]: https://github.com/aeternity/aesophia/tag/v2.0.0
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# About this release
|
||||||
|
|
||||||
|
This is the `aesophia` compiler version 2.0.0. The main changes compared to version 1.2.0 are:
|
||||||
|
|
||||||
|
* Add `Crypto.ecverify` to the compiler.
|
||||||
|
* Add `Crypto.sha3`, `Crypto.blake2`, `Crypto.sha256`, `String.blake2` and
|
||||||
|
`String.sha256` to the compiler.
|
||||||
|
* Add the `bits` type for working with bit fields in Sophia.
|
||||||
|
* Use native bit shift operations in builtin functions, reducing gas cost.
|
||||||
|
* Add Namespaces to Sophia in order to simplify using library contracts, etc.
|
||||||
|
* Simplify calldata creation - instead of passing a compiled contract, simply
|
||||||
|
pass a (stubbed) contract string.
|
||||||
|
* Add a missig type check on the `init` function - detects programmer errors earlier.
|
||||||
|
* Improve type checking of `record` fields - generates more understandable error messages.
|
||||||
|
* Improved, more coherent, error messages.
|
||||||
|
* Add the ACI (Aeternity Contract Interface) generator.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
-record(pmap, {key_t :: aeso_sophia:type(),
|
||||||
|
val_t :: aeso_sophia:type(),
|
||||||
|
parent :: none | non_neg_integer(),
|
||||||
|
size = 0 :: non_neg_integer(),
|
||||||
|
data :: #{aeso_heap:binary_value() => aeso_heap:binary_value() | tombstone}
|
||||||
|
| stored}).
|
||||||
|
|
||||||
|
-record(maps, { maps = #{} :: #{ non_neg_integer() => #pmap{} }
|
||||||
|
, next_id = 0 :: non_neg_integer() }).
|
||||||
|
|
||||||
|
-record(heap, { maps :: #maps{},
|
||||||
|
offset :: aeso_heap:offset(),
|
||||||
|
heap :: binary() | #{non_neg_integer() => non_neg_integer()} }).
|
||||||
|
|
||||||
+17
-2
@@ -3,19 +3,34 @@
|
|||||||
{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, "2f4e188"}}}
|
{ref, "9041423"}}}
|
||||||
, {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, "3.0.0"},
|
{relx, [{release, {aesophia, "2.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,"2f4e1888c241a7347ffec855ab6761c2c2972f37"}},
|
{ref,"9041423906247a7267a5a94530307b19c4490e8c"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
{ref,"816bf994ffb5cee218c3f22dc5fea296c9e0882e"}},
|
{ref,"6dce265753af4e651f77746e77ea125145c85dd3"}},
|
||||||
1},
|
1},
|
||||||
{<<"base58">>,
|
{<<"base58">>,
|
||||||
{git,"https://github.com/aeternity/erl-base58.git",
|
{git,"https://github.com/aeternity/erl-base58.git",
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||||
|
%%% @doc
|
||||||
|
%%% Encode and decode data and function calls according to
|
||||||
|
%%% Sophia-AEVM-ABI.
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 25 Jan 2018
|
||||||
|
%%%
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
-module(aeso_abi).
|
||||||
|
-define(HASH_SIZE, 32).
|
||||||
|
|
||||||
|
-export([ old_create_calldata/3
|
||||||
|
, create_calldata/4
|
||||||
|
, check_calldata/2
|
||||||
|
, function_type_info/3
|
||||||
|
, function_type_hash/3
|
||||||
|
, arg_typerep_from_function/2
|
||||||
|
, type_hash_from_function_name/2
|
||||||
|
, typereps_from_type_hash/2
|
||||||
|
, function_name_from_type_hash/2
|
||||||
|
, get_function_hash_from_calldata/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-type hash() :: <<_:256>>. %% 256 = ?HASH_SIZE * 8.
|
||||||
|
-type function_name() :: binary(). %% String
|
||||||
|
-type typerep() :: aeso_sophia:type().
|
||||||
|
-type function_type_info() :: { FunctionHash :: hash()
|
||||||
|
, FunctionName :: function_name()
|
||||||
|
, ArgType :: binary() %% binary typerep
|
||||||
|
, OutType :: binary() %% binary typerep
|
||||||
|
}.
|
||||||
|
-type type_info() :: [function_type_info()].
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% API
|
||||||
|
%%%===================================================================
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Handle calldata
|
||||||
|
|
||||||
|
create_calldata(FunName, Args, ArgTypes0, RetType) ->
|
||||||
|
ArgTypes = {tuple, ArgTypes0},
|
||||||
|
<<TypeHashInt:?HASH_SIZE/unit:8>> =
|
||||||
|
function_type_hash(list_to_binary(FunName), ArgTypes, RetType),
|
||||||
|
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
|
||||||
|
{ok, Data, {tuple, [word, ArgTypes]}, RetType}.
|
||||||
|
|
||||||
|
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
|
||||||
|
FunBin = list_to_binary(FunName),
|
||||||
|
case type_hash_from_function_name(FunBin, TypeInfo) of
|
||||||
|
{ok, <<TypeHashInt:?HASH_SIZE/unit:8>>} -> {ok, TypeInfo, TypeHashInt};
|
||||||
|
{ok, _} -> {error, bad_type_hash};
|
||||||
|
{error, _} = Err -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec check_calldata(binary(), type_info()) ->
|
||||||
|
{'ok', typerep(), typerep()} | {'error', atom()}.
|
||||||
|
check_calldata(CallData, TypeInfo) ->
|
||||||
|
%% The first element of the CallData should be the function name
|
||||||
|
case get_function_hash_from_calldata(CallData) of
|
||||||
|
{ok, Hash} ->
|
||||||
|
case typereps_from_type_hash(Hash, TypeInfo) of
|
||||||
|
{ok, ArgType, OutType} ->
|
||||||
|
try aeso_heap:from_binary({tuple, [word, ArgType]}, CallData) of
|
||||||
|
{ok, _Something} ->
|
||||||
|
{ok, {tuple, [word, ArgType]}, OutType};
|
||||||
|
{error, _} ->
|
||||||
|
{error, bad_call_data}
|
||||||
|
catch
|
||||||
|
_T:_E ->
|
||||||
|
{error, bad_call_data}
|
||||||
|
end;
|
||||||
|
{error, _} ->
|
||||||
|
{error, unknown_function}
|
||||||
|
end;
|
||||||
|
{error, _What} ->
|
||||||
|
{error, bad_call_data}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec get_function_hash_from_calldata(CallData::binary()) ->
|
||||||
|
{ok, binary()} | {error, term()}.
|
||||||
|
get_function_hash_from_calldata(CallData) ->
|
||||||
|
case aeso_heap:from_binary({tuple, [word]}, CallData) of
|
||||||
|
{ok, {HashInt}} -> {ok, <<HashInt:?HASH_SIZE/unit:8>>};
|
||||||
|
{error, _} = Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Handle type info from contract meta data
|
||||||
|
|
||||||
|
-spec function_type_info(function_name(), [typerep()], typerep()) ->
|
||||||
|
function_type_info().
|
||||||
|
function_type_info(Name, ArgTypes, OutType) ->
|
||||||
|
ArgType = {tuple, ArgTypes},
|
||||||
|
{ function_type_hash(Name, ArgType, OutType)
|
||||||
|
, Name
|
||||||
|
, aeso_heap:to_binary(ArgType)
|
||||||
|
, aeso_heap:to_binary(OutType)
|
||||||
|
}.
|
||||||
|
|
||||||
|
-spec function_type_hash(function_name(), typerep(), typerep()) -> hash().
|
||||||
|
function_type_hash(Name, ArgType, OutType) when is_binary(Name) ->
|
||||||
|
Bin = iolist_to_binary([ Name
|
||||||
|
, aeso_heap:to_binary(ArgType)
|
||||||
|
, aeso_heap:to_binary(OutType)
|
||||||
|
]),
|
||||||
|
%% Calculate a 256 bit digest BLAKE2b hash value of a binary
|
||||||
|
{ok, Hash} = eblake2:blake2b(?HASH_SIZE, Bin),
|
||||||
|
Hash.
|
||||||
|
|
||||||
|
-spec arg_typerep_from_function(function_name(), type_info()) ->
|
||||||
|
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||||
|
arg_typerep_from_function(Function, TypeInfo) ->
|
||||||
|
case lists:keyfind(Function, 2, TypeInfo) of
|
||||||
|
{_TypeHash, Function, ArgTypeBin,_OutTypeBin} ->
|
||||||
|
case aeso_heap:from_binary(typerep, ArgTypeBin) of
|
||||||
|
{ok, ArgType} -> {ok, ArgType};
|
||||||
|
{error,_} -> {error, bad_type_data}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{error, unknown_function}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec typereps_from_type_hash(hash(), type_info()) ->
|
||||||
|
{'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
|
||||||
|
typereps_from_type_hash(TypeHash, TypeInfo) ->
|
||||||
|
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||||
|
{TypeHash,_Function, ArgTypeBin, OutTypeBin} ->
|
||||||
|
case {aeso_heap:from_binary(typerep, ArgTypeBin),
|
||||||
|
aeso_heap:from_binary(typerep, OutTypeBin)} of
|
||||||
|
{{ok, ArgType}, {ok, OutType}} -> {ok, ArgType, OutType};
|
||||||
|
{_, _} -> {error, bad_type_data}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{error, unknown_function}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec function_name_from_type_hash(hash(), type_info()) ->
|
||||||
|
{'ok', function_name()}
|
||||||
|
| {'error', 'unknown_function'}.
|
||||||
|
function_name_from_type_hash(TypeHash, TypeInfo) ->
|
||||||
|
case lists:keyfind(TypeHash, 1, TypeInfo) of
|
||||||
|
{TypeHash, Function,_ArgTypeBin,_OutTypeBin} ->
|
||||||
|
{ok, Function};
|
||||||
|
false ->
|
||||||
|
{error, unknown_function}
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec type_hash_from_function_name(function_name(), type_info()) ->
|
||||||
|
{'ok', hash()}
|
||||||
|
| {'error', 'unknown_function'}.
|
||||||
|
type_hash_from_function_name(Name, TypeInfo) ->
|
||||||
|
case lists:keyfind(Name, 2, TypeInfo) of
|
||||||
|
{TypeHash, Name,_ArgTypeBin,_OutTypeBin} ->
|
||||||
|
{ok, TypeHash};
|
||||||
|
false ->
|
||||||
|
{error, unknown_function}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% -- Old calldata creation. Kept for backwards compatibility. ---------------
|
||||||
|
|
||||||
|
old_create_calldata(Contract, Function, Argument) when is_map(Contract) ->
|
||||||
|
case aeso_constants:string(Argument) of
|
||||||
|
{ok, {tuple, _, _} = Tuple} ->
|
||||||
|
old_encode_call(Contract, Function, Tuple);
|
||||||
|
{ok, {unit, _} = Tuple} ->
|
||||||
|
old_encode_call(Contract, Function, Tuple);
|
||||||
|
{ok, ParsedArgument} ->
|
||||||
|
%% The Sophia compiler does not parse a singleton tuple (42) as a tuple,
|
||||||
|
%% Wrap it in a tuple.
|
||||||
|
old_encode_call(Contract, Function, {tuple, [], [ParsedArgument]});
|
||||||
|
{error, _} ->
|
||||||
|
{error, argument_syntax_error}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Call takes one arument.
|
||||||
|
%% Use a tuple to pass multiple arguments.
|
||||||
|
old_encode_call(Contract, Function, ArgumentAst) ->
|
||||||
|
Argument = old_ast_to_erlang(ArgumentAst),
|
||||||
|
case get_type_info_and_hash(Contract, Function) of
|
||||||
|
{ok, TypeInfo, TypeHashInt} ->
|
||||||
|
Data = aeso_heap:to_binary({TypeHashInt, Argument}),
|
||||||
|
case check_calldata(Data, TypeInfo) of
|
||||||
|
{ok, CallDataType, OutType} ->
|
||||||
|
{ok, Data, CallDataType, OutType};
|
||||||
|
{error, _} = Err ->
|
||||||
|
Err
|
||||||
|
end;
|
||||||
|
{error, _} = Err -> Err
|
||||||
|
end.
|
||||||
|
|
||||||
|
old_ast_to_erlang({int, _, N}) -> N;
|
||||||
|
old_ast_to_erlang({hash, _, <<N:?HASH_SIZE/unit:8>>}) -> N;
|
||||||
|
old_ast_to_erlang({hash, _, <<Hi:256, Lo:256>>}) -> {Hi, Lo}; %% signature
|
||||||
|
old_ast_to_erlang({bool, _, true}) -> 1;
|
||||||
|
old_ast_to_erlang({bool, _, false}) -> 0;
|
||||||
|
old_ast_to_erlang({string, _, Bin}) -> Bin;
|
||||||
|
old_ast_to_erlang({unit, _}) -> {};
|
||||||
|
old_ast_to_erlang({con, _, "None"}) -> none;
|
||||||
|
old_ast_to_erlang({app, _, {con, _, "Some"}, [A]}) -> {some, old_ast_to_erlang(A)};
|
||||||
|
old_ast_to_erlang({tuple, _, Elems}) ->
|
||||||
|
list_to_tuple(lists:map(fun old_ast_to_erlang/1, Elems));
|
||||||
|
old_ast_to_erlang({list, _, Elems}) ->
|
||||||
|
lists:map(fun old_ast_to_erlang/1, Elems);
|
||||||
|
old_ast_to_erlang({map, _, Elems}) ->
|
||||||
|
maps:from_list([ {old_ast_to_erlang(element(1, Elem)), old_ast_to_erlang(element(2, Elem))}
|
||||||
|
|| Elem <- Elems ]).
|
||||||
|
|
||||||
+47
-167
@@ -1,30 +1,26 @@
|
|||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
%%% @author Robert Virding
|
%%% @author Robert Virding
|
||||||
%%% @copyright (C) 2019, Aeternity Anstalt
|
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% ACI interface
|
%%% ACI interface
|
||||||
%%% @end
|
%%% @end
|
||||||
%%% Created : 12 Jan 2019
|
%%% Created : 12 Dec 2017
|
||||||
%%%-------------------------------------------------------------------
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
-module(aeso_aci).
|
-module(aeso_aci).
|
||||||
|
|
||||||
-export([encode/1,encode/2,decode/1]).
|
-export([encode/1,encode/2,decode/1]).
|
||||||
-export([encode_type/1,encode_stmt/1,encode_expr/1]).
|
|
||||||
|
|
||||||
%% Define records for the various typed syntactic forms. These make
|
%% Define records for the various typed syntactic forms. These make
|
||||||
%% the code easier but don't seem to exist elsewhere.
|
%% the code easier but don't seem to exist elsewhere.
|
||||||
|
|
||||||
%% Top-level
|
|
||||||
-record(contract, {ann,con,decls}).
|
-record(contract, {ann,con,decls}).
|
||||||
%% -record(namespace, {ann,con,decls}).
|
%% -record(namespace, {ann,con,decls}).
|
||||||
-record(letfun, {ann,id,args,type,body}).
|
-record(letfun, {ann,id,args,type,body}).
|
||||||
-record(type_def, {ann,id,vars,typedef}).
|
-record(type_def, {ann,id,vars,typedef}).
|
||||||
|
|
||||||
%% 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}).
|
||||||
@@ -32,7 +28,6 @@
|
|||||||
-record(constr_t, {ann,con,args}).
|
-record(constr_t, {ann,con,args}).
|
||||||
-record(fun_t, {ann,named,args,type}).
|
-record(fun_t, {ann,named,args,type}).
|
||||||
|
|
||||||
%% Tokens
|
|
||||||
-record(arg, {ann,id,type}).
|
-record(arg, {ann,id,type}).
|
||||||
-record(id, {ann,name}).
|
-record(id, {ann,name}).
|
||||||
-record(con, {ann,name}).
|
-record(con, {ann,name}).
|
||||||
@@ -40,16 +35,6 @@
|
|||||||
-record(qcon, {ann,names}).
|
-record(qcon, {ann,names}).
|
||||||
-record(tvar, {ann,name}).
|
-record(tvar, {ann,name}).
|
||||||
|
|
||||||
%% Expressions
|
|
||||||
-record(bool, {ann,bool}).
|
|
||||||
-record(int, {ann,value}).
|
|
||||||
-record(string, {ann,bin}).
|
|
||||||
-record(bytes, {ann,bin}).
|
|
||||||
-record(tuple, {ann,args}).
|
|
||||||
-record(list, {ann,args}).
|
|
||||||
-record(app, {ann,func,args}).
|
|
||||||
-record(typed, {ann,expr,type}).
|
|
||||||
|
|
||||||
%% encode(ContractString) -> {ok,JSON} | {error,String}.
|
%% encode(ContractString) -> {ok,JSON} | {error,String}.
|
||||||
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
|
%% encode(ContractString, Options) -> {ok,JSON} | {error,String}.
|
||||||
%% Build a JSON structure with lists and tuples, not maps, as this
|
%% Build a JSON structure with lists and tuples, not maps, as this
|
||||||
@@ -62,7 +47,7 @@ encode(ContractString, Options) when is_binary(ContractString) ->
|
|||||||
encode(ContractString, Options) ->
|
encode(ContractString, Options) ->
|
||||||
try
|
try
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
%% io:format("~p\n", [Ast]),
|
%%io:format("~p\n", [Ast]),
|
||||||
%% aeso_ast:pp(Ast),
|
%% aeso_ast:pp(Ast),
|
||||||
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
||||||
%% io:format("~p\n", [TypedAst]),
|
%% io:format("~p\n", [TypedAst]),
|
||||||
@@ -74,7 +59,7 @@ encode(ContractString, Options) ->
|
|||||||
T <- sort_decls(contract_types(Contract)) ],
|
T <- sort_decls(contract_types(Contract)) ],
|
||||||
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
|
Fdefs = [ encode_func(F) || F <- sort_decls(contract_funcs(Contract)),
|
||||||
not is_private_func(F) ],
|
not is_private_func(F) ],
|
||||||
Jmap = [{<<"contract">>, [{<<"name">>, encode_name(Cname)},
|
Jmap = [{<<"contract">>, [{<<"name">>, list_to_binary(Cname)},
|
||||||
{<<"type_defs">>, Tdefs},
|
{<<"type_defs">>, Tdefs},
|
||||||
{<<"functions">>, Fdefs}]}],
|
{<<"functions">>, Fdefs}]}],
|
||||||
%% io:format("~p\n", [Jmap]),
|
%% io:format("~p\n", [Jmap]),
|
||||||
@@ -95,137 +80,74 @@ join_errors(Prefix, Errors, Pfun) ->
|
|||||||
Ess = [ Pfun(E) || E <- Errors ],
|
Ess = [ Pfun(E) || E <- Errors ],
|
||||||
list_to_binary(string:join([Prefix|Ess], "\n")).
|
list_to_binary(string:join([Prefix|Ess], "\n")).
|
||||||
|
|
||||||
%% encode_func(Function) -> JSON
|
|
||||||
%% Encode a function definition. Currently we are only interested in
|
|
||||||
%% the interface and type.
|
|
||||||
|
|
||||||
encode_func(Fdef) ->
|
encode_func(Fdef) ->
|
||||||
Name = function_name(Fdef),
|
Name = function_name(Fdef),
|
||||||
Args = function_args(Fdef),
|
Args = function_args(Fdef),
|
||||||
Type = function_type(Fdef),
|
Type = function_type(Fdef),
|
||||||
[{<<"name">>, encode_name(Name)},
|
[{<<"name">>, list_to_binary(Name)},
|
||||||
{<<"arguments">>, encode_args(Args)},
|
{<<"arguments">>, encode_args(Args)},
|
||||||
{<<"returns">>, encode_type(Type)},
|
{<<"type">>, list_to_binary(encode_type(Type))},
|
||||||
{<<"stateful">>, is_stateful_func(Fdef)}].
|
{<<"stateful">>, is_stateful_func(Fdef)}].
|
||||||
|
|
||||||
%% encode_args(Args) -> [JSON].
|
|
||||||
%% encode_arg(Args) -> JSON.
|
|
||||||
|
|
||||||
encode_args(Args) ->
|
encode_args(Args) ->
|
||||||
[ encode_arg(A) || A <- Args ].
|
[ encode_arg(A) || A <- Args ].
|
||||||
|
|
||||||
encode_arg(#arg{id=Id,type=T}) ->
|
encode_arg(#arg{id=Id,type=T}) ->
|
||||||
[{<<"name">>,encode_type(Id)},
|
[{<<"name">>,list_to_binary(encode_type(Id))},
|
||||||
{<<"type">>,[encode_type(T)]}].
|
{<<"type">>,list_to_binary(encode_type(T))}].
|
||||||
|
|
||||||
%% encode_types(Types) -> [JSON].
|
|
||||||
%% encode_type(Type) -> JSON.
|
|
||||||
|
|
||||||
encode_types(Types) ->
|
encode_types(Types) ->
|
||||||
[ encode_type(T) || T <- Types ].
|
[ encode_type(T) || T <- Types ].
|
||||||
|
|
||||||
encode_type(#tvar{name=N}) -> encode_name(N);
|
encode_type(#tvar{name=N}) -> N;
|
||||||
encode_type(#id{name=N}) -> encode_name(N);
|
encode_type(#id{name=N}) -> N;
|
||||||
encode_type(#con{name=N}) -> encode_name(N);
|
encode_type(#con{name=N}) -> N;
|
||||||
encode_type(#qid{names=Ns}) ->
|
encode_type(#qid{names=Ns}) ->
|
||||||
encode_name(lists:join(".", Ns));
|
lists:join(".", Ns);
|
||||||
encode_type(#qcon{names=Ns}) ->
|
encode_type(#qcon{names=Ns}) ->
|
||||||
encode_name(lists:join(".", Ns)); %?
|
lists:join(".", Ns); %?
|
||||||
encode_type(#tuple_t{args=As}) ->
|
encode_type(#tuple_t{args=As}) ->
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
[{<<"tuple">>,Eas}];
|
[$(,lists:join(",", 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_types(Fs),
|
||||||
[{<<"record">>,Efs}];
|
[${,lists:join(",", Efs),$}];
|
||||||
encode_type(#app_t{id=Id,fields=Fs}) ->
|
encode_type(#app_t{id=Id,fields=Fs}) ->
|
||||||
Name = encode_type(Id),
|
Name = encode_type(Id),
|
||||||
Efs = encode_types(Fs),
|
Efs = encode_types(Fs),
|
||||||
[{Name,Efs}];
|
[Name,"(",lists:join(",", Efs),")"];
|
||||||
|
encode_type(#field_t{id=Id,type=T}) ->
|
||||||
|
[encode_type(Id)," : ",encode_type(T)];
|
||||||
encode_type(#variant_t{cons=Cs}) ->
|
encode_type(#variant_t{cons=Cs}) ->
|
||||||
Ecs = encode_types(Cs),
|
Ecs = encode_types(Cs),
|
||||||
[{<<"variant">>,Ecs}];
|
lists:join(" | ", Ecs);
|
||||||
encode_type(#constr_t{con=C,args=As}) ->
|
encode_type(#constr_t{con=C,args=As}) ->
|
||||||
Ec = encode_type(C),
|
Ec = encode_type(C),
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
[{Ec,Eas}];
|
[Ec,$(,lists:join(", ", Eas),$)];
|
||||||
encode_type(#fun_t{args=As,type=T}) ->
|
encode_type(#fun_t{args=As,type=T}) ->
|
||||||
Eas = encode_types(As),
|
Eas = encode_types(As),
|
||||||
Et = encode_type(T),
|
Et = encode_type(T),
|
||||||
[{<<"function">>,[{<<"arguments">>,Eas},{<<"returns">>,Et}]}].
|
[$(,lists:join(", ", Eas),") => ",Et].
|
||||||
|
|
||||||
encode_name(Name) ->
|
|
||||||
list_to_binary(Name).
|
|
||||||
|
|
||||||
%% encode_fields(Fields) -> [JSON].
|
|
||||||
%% encode_field(Field) -> JSON.
|
|
||||||
%% Encode a record field.
|
|
||||||
|
|
||||||
encode_fields(Fs) ->
|
|
||||||
[ encode_field(F) || F <- Fs ].
|
|
||||||
|
|
||||||
encode_field(#field_t{id=Id,type=T}) ->
|
|
||||||
[{<<"name">>,encode_type(Id)},
|
|
||||||
{<<"type">>,[encode_type(T)]}].
|
|
||||||
|
|
||||||
%% encode_typedef(TypeDef) -> JSON.
|
|
||||||
|
|
||||||
encode_typedef(Type) ->
|
encode_typedef(Type) ->
|
||||||
Name = typedef_name(Type),
|
Name = typedef_name(Type),
|
||||||
Vars = typedef_vars(Type),
|
Vars = typedef_vars(Type),
|
||||||
Def = typedef_def(Type),
|
Def = typedef_def(Type),
|
||||||
[{<<"name">>, encode_name(Name)},
|
[{<<"name">>, list_to_binary(Name)},
|
||||||
{<<"vars">>, encode_tvars(Vars)},
|
{<<"vars">>, encode_tvars(Vars)},
|
||||||
{<<"typedef">>, encode_alias(Def)}].
|
{<<"typedef">>, list_to_binary(encode_alias(Def))}].
|
||||||
|
|
||||||
encode_tvars(Vars) ->
|
encode_tvars(Vars) ->
|
||||||
[ encode_tvar(V) || V <- Vars ].
|
[ encode_tvar(V) || V <- Vars ].
|
||||||
|
|
||||||
encode_tvar(#tvar{name=N}) ->
|
encode_tvar(#tvar{name=N}) ->
|
||||||
[{<<"name">>, encode_name(N)}].
|
[{<<"name">>, list_to_binary(N)}].
|
||||||
|
|
||||||
encode_alias(#alias_t{type=T}) ->
|
encode_alias(#alias_t{type=T}) ->
|
||||||
encode_type(T);
|
encode_type(T);
|
||||||
encode_alias(A) -> encode_type(A).
|
encode_alias(A) -> encode_type(A).
|
||||||
|
|
||||||
%% encode_stmt(Stmt) -> JSON.
|
|
||||||
|
|
||||||
encode_stmt(E) ->
|
|
||||||
encode_expr(E).
|
|
||||||
|
|
||||||
%% encode_exprs(Exprs) -> [JSON].
|
|
||||||
%% encode_expr(Expr) -> JSON.
|
|
||||||
|
|
||||||
encode_exprs(Es) ->
|
|
||||||
[ encode_expr(E) || E <- Es ].
|
|
||||||
|
|
||||||
encode_expr(#id{name=N}) -> encode_name(N);
|
|
||||||
encode_expr(#con{name=N}) -> encode_name(N);
|
|
||||||
encode_expr(#qid{names=Ns}) ->
|
|
||||||
encode_name(lists:join(".", Ns));
|
|
||||||
encode_expr(#qcon{names=Ns}) ->
|
|
||||||
encode_name(lists:join(".", Ns)); %?
|
|
||||||
encode_expr(#typed{expr=E}) ->
|
|
||||||
encode_expr(E);
|
|
||||||
encode_expr(#bool{bool=B}) -> B;
|
|
||||||
encode_expr(#int{value=V}) -> V;
|
|
||||||
encode_expr(#string{bin=B}) -> B;
|
|
||||||
encode_expr(#bytes{bin=B}) -> B;
|
|
||||||
encode_expr(#tuple{args=As}) ->
|
|
||||||
Eas = encode_exprs(As),
|
|
||||||
[{<<"tuple">>,Eas}];
|
|
||||||
encode_expr(#list{args=As}) ->
|
|
||||||
Eas = encode_exprs(As),
|
|
||||||
[{<<"list">>,Eas}];
|
|
||||||
encode_expr(#app{func=F,args=As}) ->
|
|
||||||
Ef = encode_expr(F),
|
|
||||||
Eas = encode_exprs(As),
|
|
||||||
[{<<"apply">>,[{<<"function">>,Ef},
|
|
||||||
{<<"arguments">>,Eas}]}];
|
|
||||||
encode_expr({Op,_Ann}) ->
|
|
||||||
list_to_binary(atom_to_list(Op)).
|
|
||||||
|
|
||||||
%% decode(JSON) -> ContractString.
|
%% decode(JSON) -> ContractString.
|
||||||
%% Decode a JSON string and generate a suitable contract string which
|
%% Decode a JSON string and generate a suitable contract string which
|
||||||
%% can be included in a contract definition. We decode into a map
|
%% can be included in a contract definition. We decode into a map
|
||||||
@@ -238,81 +160,42 @@ decode(Json) ->
|
|||||||
list_to_binary(decode_contract(C)).
|
list_to_binary(decode_contract(C)).
|
||||||
|
|
||||||
decode_contract(#{<<"name">> := Name,
|
decode_contract(#{<<"name">> := Name,
|
||||||
<<"type_defs">> := Ts,
|
<<"type_defs">> := _Ts,
|
||||||
<<"functions">> := Fs}) ->
|
<<"functions">> := Fs}) ->
|
||||||
["contract"," ",io_lib:format("~s", [Name])," =\n",
|
["contract"," ",io_lib:format("~s", [Name])," =\n",
|
||||||
decode_tdefs(Ts),
|
[], %Don't include types yet.
|
||||||
|
%% decode_tdefs(Ts),
|
||||||
decode_funcs(Fs)].
|
decode_funcs(Fs)].
|
||||||
|
|
||||||
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
decode_funcs(Fs) -> [ decode_func(F) || F <- Fs ].
|
||||||
|
|
||||||
decode_func(#{<<"name">> := <<"init">>}) -> [];
|
decode_func(#{<<"name">> := <<"init">>}) -> [];
|
||||||
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"returns">> := T}) ->
|
decode_func(#{<<"name">> := Name,<<"arguments">> := As,<<"type">> := T}) ->
|
||||||
[" function"," ",io_lib:format("~s", [Name])," : ",
|
[" function"," ",io_lib:format("~s", [Name])," : ",
|
||||||
decode_args(As)," => ",decode_type(T),$\n].
|
decode_args(As)," => ",decode_type(T),$\n].
|
||||||
|
|
||||||
|
decode_type(T) -> io_lib:format("~s", [T]).
|
||||||
|
|
||||||
decode_args(As) ->
|
decode_args(As) ->
|
||||||
Das = [ decode_arg(A) || A <- As ],
|
Das = [ decode_arg(A) || A <- As ],
|
||||||
[$(,lists:join(", ", Das),$)].
|
[$(,lists:join(", ", Das),$)].
|
||||||
|
|
||||||
decode_arg(#{<<"type">> := [T]}) -> decode_type(T).
|
decode_arg(#{<<"type">> := T}) -> decode_type(T).
|
||||||
|
|
||||||
decode_types(Ets) ->
|
|
||||||
[ decode_type(Et) || Et <- Ets ].
|
|
||||||
|
|
||||||
decode_type(#{<<"tuple">> := Ets}) ->
|
|
||||||
Ts = decode_types(Ets),
|
|
||||||
[$(,lists:join(",", Ts),$)];
|
|
||||||
decode_type(#{<<"record">> := Efs}) ->
|
|
||||||
Fs = decode_fields(Efs),
|
|
||||||
[${,lists:join(",", Fs),$}];
|
|
||||||
decode_type(#{<<"list">> := [Et]}) ->
|
|
||||||
T = decode_type(Et),
|
|
||||||
["list",$(,T,$)];
|
|
||||||
decode_type(#{<<"map">> := Ets}) ->
|
|
||||||
Ts = decode_types(Ets),
|
|
||||||
["map",$(,lists:join(",", Ts),$)];
|
|
||||||
decode_type(#{<<"variant">> := Ets}) ->
|
|
||||||
Ts = decode_types(Ets),
|
|
||||||
lists:join(" | ", Ts);
|
|
||||||
decode_type(Econs) when is_map(Econs) -> %General constructor
|
|
||||||
[{Ec,Ets}] = maps:to_list(Econs),
|
|
||||||
C = decode_name(Ec),
|
|
||||||
Ts = decode_types(Ets),
|
|
||||||
[C,$(,lists:join(",", Ts),$)];
|
|
||||||
decode_type(T) -> %Just raw names.
|
|
||||||
decode_name(T).
|
|
||||||
|
|
||||||
decode_name(En) ->
|
|
||||||
binary_to_list(En).
|
|
||||||
|
|
||||||
decode_fields(Efs) ->
|
|
||||||
[ decode_field(Ef) || Ef <- Efs ].
|
|
||||||
|
|
||||||
decode_field(#{<<"name">> := En,<<"type">> := [Et]}) ->
|
|
||||||
Name = decode_name(En),
|
|
||||||
Type = decode_type(Et),
|
|
||||||
[Name," : ",Type].
|
|
||||||
|
|
||||||
%% decode_tdefs(Json) -> [TypeString].
|
|
||||||
%% Here we are only interested in the type definitions and ignore the
|
|
||||||
%% aliases. We find them as they always have variants.
|
|
||||||
|
|
||||||
decode_tdefs(Ts) -> [ decode_tdef(T) ||
|
|
||||||
#{<<"typedef">> := #{<<"variant">> := _}} = T <- Ts
|
|
||||||
].
|
|
||||||
|
|
||||||
decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
|
||||||
[" datatype"," ",decode_name(Name),decode_tvars(Vs),
|
|
||||||
" = ",decode_type(T),$\n].
|
|
||||||
|
|
||||||
decode_tvars([]) -> []; %No tvars, no parentheses
|
|
||||||
decode_tvars(Vs) ->
|
|
||||||
Dvs = [ decode_tvar(V) || V <- Vs ],
|
|
||||||
[$(,lists:join(", ", Dvs),$)].
|
|
||||||
|
|
||||||
decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
|
||||||
|
|
||||||
|
%% To keep dialyzer happy and quiet.
|
||||||
|
%% decode_tdefs(Ts) -> [ decode_tdef(T) || T <- Ts ].
|
||||||
|
%%
|
||||||
|
%% decode_tdef(#{<<"name">> := Name,<<"vars">> := Vs,<<"typedef">> := T}) ->
|
||||||
|
%% [" type"," ",io_lib:format("~s", [Name]),decode_tvars(Vs),
|
||||||
|
%% " = ",decode_type(T),$\n].
|
||||||
|
%%
|
||||||
|
%% decode_tvars([]) -> []; %No tvars, no parentheses
|
||||||
|
%% decode_tvars(Vs) ->
|
||||||
|
%% Dvs = [ decode_tvar(V) || V <- Vs ],
|
||||||
|
%% [$(,lists:join(", ", Dvs),$)].
|
||||||
|
%%
|
||||||
|
%% decode_tvar(#{<<"name">> := N}) -> io_lib:format("~s", [N]).
|
||||||
|
%%
|
||||||
%% #contract{Ann, Con, [Declarations]}.
|
%% #contract{Ann, Con, [Declarations]}.
|
||||||
|
|
||||||
contract_name(#contract{con=#con{name=N}}) -> N.
|
contract_name(#contract{con=#con{name=N}}) -> N.
|
||||||
@@ -359,9 +242,6 @@ typedef_vars(#type_def{vars=Vars}) -> Vars.
|
|||||||
|
|
||||||
typedef_def(#type_def{typedef=Def}) -> Def.
|
typedef_def(#type_def{typedef=Def}) -> Def.
|
||||||
|
|
||||||
%% parse(ContractString, Options) -> {ok,AST}.
|
|
||||||
%% Signal errors, the sophia compiler way. Sigh!
|
|
||||||
|
|
||||||
parse(Text, Options) ->
|
parse(Text, Options) ->
|
||||||
%% Try and return something sensible here!
|
%% Try and return something sensible here!
|
||||||
case aeso_parser:string(Text, Options) of
|
case aeso_parser:string(Text, Options) of
|
||||||
@@ -384,7 +264,7 @@ parse(Text, Options) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
parse_error(Pos, ErrorString) ->
|
parse_error(Pos, ErrorString) ->
|
||||||
%% io:format("Error ~p ~p\n", [Pos,ErrorString]),
|
io:format("Error ~p ~p\n", [Pos,ErrorString]),
|
||||||
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
Error = io_lib:format("~s: ~s", [pos_error(Pos), ErrorString]),
|
||||||
error({parse_errors, [Error]}).
|
error({parse_errors, [Error]}).
|
||||||
|
|
||||||
|
|||||||
+64
-188
@@ -56,12 +56,7 @@
|
|||||||
, fields :: [aeso_syntax:id()]
|
, fields :: [aeso_syntax:id()]
|
||||||
, context :: why_record() }).
|
, context :: why_record() }).
|
||||||
|
|
||||||
-record(is_contract_constraint,
|
-type field_constraint() :: #field_constraint{} | #record_create_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()
|
||||||
@@ -98,14 +93,11 @@
|
|||||||
-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{}.
|
||||||
@@ -199,7 +191,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", {type_sig, [stateful | Ann], [], [State], Unit}}], Env),
|
{"put", {fun_t, 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(
|
||||||
@@ -349,7 +341,6 @@ 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, []},
|
||||||
@@ -359,12 +350,11 @@ 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,
|
||||||
StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end,
|
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
||||||
TVar = fun(X) -> {tvar, Ann, "'" ++ X} end,
|
|
||||||
SignId = {id, Ann, "signature"},
|
SignId = {id, Ann, "signature"},
|
||||||
SignDef = {bytes, Ann, <<0:64/unit:8>>},
|
SignDef = {tuple, Ann, [{int, Ann, 0}, {int, Ann, 0}]},
|
||||||
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, [stateful|Ann], [Signature], Ts, T} end,
|
SignFun = fun(Ts, T) -> {type_sig, 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"]),
|
||||||
@@ -382,10 +372,8 @@ global_env() ->
|
|||||||
%% Abort
|
%% Abort
|
||||||
{"abort", Fun1(String, A)}])
|
{"abort", Fun1(String, A)}])
|
||||||
, types = MkDefs(
|
, types = MkDefs(
|
||||||
[{"int", 0}, {"bool", 0}, {"char", 0}, {"string", 0}, {"address", 0},
|
[{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0},
|
||||||
{"hash", {[], {alias_t, Bytes(32)}}},
|
{"hash", 0}, {"signature", 0}, {"bits", 0},
|
||||||
{"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}
|
||||||
]) },
|
]) },
|
||||||
@@ -393,7 +381,7 @@ global_env() ->
|
|||||||
ChainScope = #scope
|
ChainScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
%% Spend transaction.
|
%% Spend transaction.
|
||||||
[{"spend", StateFun([Address, Int], Unit)},
|
[{"spend", Fun([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)},
|
||||||
@@ -407,7 +395,7 @@ global_env() ->
|
|||||||
ContractScope = #scope
|
ContractScope = #scope
|
||||||
{ funs = MkDefs(
|
{ funs = MkDefs(
|
||||||
[{"address", Address},
|
[{"address", Address},
|
||||||
{"creator", Address},
|
%% {"owner", Int}, %% Not in EVM
|
||||||
{"balance", Int}]) },
|
{"balance", Int}]) },
|
||||||
|
|
||||||
CallScope = #scope
|
CallScope = #scope
|
||||||
@@ -423,13 +411,11 @@ 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", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))},
|
{"query", Fun([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(
|
||||||
@@ -453,7 +439,6 @@ 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)}]) },
|
||||||
@@ -487,9 +472,7 @@ 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
|
||||||
@@ -514,7 +497,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}.
|
|||||||
infer(Contracts) ->
|
infer(Contracts) ->
|
||||||
infer(Contracts, []).
|
infer(Contracts, []).
|
||||||
|
|
||||||
-type option() :: return_env.
|
-type option() :: permissive_address_literals | return_env.
|
||||||
|
|
||||||
-spec init_env(list(option())) -> env().
|
-spec init_env(list(option())) -> env().
|
||||||
init_env(_Options) -> global_env().
|
init_env(_Options) -> global_env().
|
||||||
@@ -605,8 +588,6 @@ 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}.
|
||||||
|
|
||||||
@@ -694,9 +675,6 @@ 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)),
|
||||||
@@ -803,7 +781,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, Defs1} = infer_letrec(Env, Defs),
|
{TypeSigs, {letrec, _, Defs1}} = infer_letrec(Env, {letrec, [], 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).
|
||||||
|
|
||||||
@@ -845,7 +823,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, Defs) ->
|
infer_letrec(Env, {letrec, Attrs, 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],
|
||||||
@@ -865,12 +843,9 @@ infer_letrec(Env, 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, NewDefs}.
|
{TypeSigs, {letrec, Attrs, NewDefs}}.
|
||||||
|
|
||||||
infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Body}) ->
|
infer_letfun(Env, {letfun, Attrib, {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),
|
||||||
@@ -881,13 +856,6 @@ infer_letfun(Env0, {letfun, Attrib, Fun = {id, NameAttrib, Name}, Args, What, Bo
|
|||||||
{{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)]).
|
||||||
|
|
||||||
@@ -911,7 +879,6 @@ 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);
|
||||||
@@ -920,61 +887,6 @@ 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}),
|
||||||
@@ -984,35 +896,26 @@ 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={bytes, As, Bin}) ->
|
infer_expr(_Env, Body={hash, As, Hash}) ->
|
||||||
{typed, As, Body, {bytes_t, As, byte_size(Bin)}};
|
case byte_size(Hash) of
|
||||||
infer_expr(_Env, Body={account_pubkey, As, _}) ->
|
32 -> {typed, As, Body, {id, As, "address"}};
|
||||||
{typed, As, Body, {id, As, "address"}};
|
64 -> {typed, As, Body, {id, As, "signature"}}
|
||||||
infer_expr(_Env, Body={oracle_pubkey, As, _}) ->
|
end;
|
||||||
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 = {Tag, As, _}) when Tag == id; Tag == qid ->
|
infer_expr(Env, Id = {id, As, _}) ->
|
||||||
{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 = {Tag, As, _}) when Tag == con; Tag == qcon ->
|
infer_expr(Env, Id = {qid, As, _}) ->
|
||||||
|
{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],
|
||||||
@@ -1066,7 +969,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 } || not Env#env.in_pattern ] ++
|
context = Attrs } ] ++
|
||||||
[begin
|
[begin
|
||||||
[{proj, _, FieldName}] = LV,
|
[{proj, _, FieldName}] = LV,
|
||||||
#field_constraint{
|
#field_constraint{
|
||||||
@@ -1135,7 +1038,6 @@ 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,
|
||||||
@@ -1196,7 +1098,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#env{ in_pattern = true }),
|
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env),
|
||||||
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}),
|
||||||
@@ -1205,11 +1107,12 @@ 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, Ann, _, _, _, _}|Rest], BlockType) ->
|
infer_block(Env, Attrs, [Def={letfun, _, _, _, _, _}|Rest], BlockType) ->
|
||||||
{{Name, TypeSig}, LetFun} = infer_letfun(Env, Def),
|
NewDef = infer_letfun(Env, Def),
|
||||||
FunT = freshen_type(typesig_to_fun_t(TypeSig)),
|
[NewDef|infer_block(Env, Attrs, Rest, BlockType)];
|
||||||
NewE = bind_var({id, Ann, Name}, FunT, Env),
|
infer_block(Env, Attrs, [Def={letrec, _, _}|Rest], BlockType) ->
|
||||||
[LetFun|infer_block(NewE, Attrs, Rest, BlockType)];
|
NewDef = infer_letrec(Env, Def),
|
||||||
|
[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}, _}} =
|
||||||
@@ -1254,8 +1157,6 @@ 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, _, _}) ->
|
||||||
@@ -1447,16 +1348,6 @@ 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
|
||||||
@@ -1555,12 +1446,9 @@ 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, OtherCs} =
|
{FieldCs, CreateCs} =
|
||||||
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 ->
|
||||||
@@ -1570,7 +1458,6 @@ 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.
|
||||||
|
|
||||||
@@ -1696,8 +1583,6 @@ 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) ->
|
||||||
@@ -1753,8 +1638,6 @@ 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);
|
||||||
@@ -1772,8 +1655,26 @@ 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) ->
|
||||||
cannot_unify(A, B, When),
|
Ok =
|
||||||
false.
|
case get_option(permissive_address_literals, false) of
|
||||||
|
true ->
|
||||||
|
Kind = fun({qcon, _, _}) -> con;
|
||||||
|
({con, _, _}) -> con;
|
||||||
|
({id, _, "address"}) -> addr;
|
||||||
|
({id, _, "hash"}) -> hash;
|
||||||
|
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
|
||||||
|
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
|
||||||
|
(_) -> other end,
|
||||||
|
%% If permissive_address_literals we allow unifying adresses
|
||||||
|
%% with contract types or oracles/oracle queries
|
||||||
|
case lists:usort([Kind(A), Kind(B)]) of
|
||||||
|
[addr, K] -> K /= other;
|
||||||
|
_ -> false
|
||||||
|
end;
|
||||||
|
false -> false
|
||||||
|
end,
|
||||||
|
[ cannot_unify(A, B, When) || not Ok ],
|
||||||
|
Ok.
|
||||||
|
|
||||||
dereference(T = {uvar, _, R}) ->
|
dereference(T = {uvar, _, R}) ->
|
||||||
case ets_lookup(type_vars, R) of
|
case ets_lookup(type_vars, R) of
|
||||||
@@ -1802,7 +1703,6 @@ 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}) ->
|
||||||
@@ -1885,7 +1785,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 = lists:reverse(ets_tab2list(type_errors)),
|
Errors = 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),
|
||||||
@@ -1919,11 +1819,6 @@ 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",
|
||||||
@@ -2001,21 +1896,6 @@ 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]).
|
||||||
|
|
||||||
@@ -2163,8 +2043,6 @@ 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)) ];
|
||||||
@@ -2172,8 +2050,6 @@ 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
+24
-62
@@ -102,6 +102,13 @@ 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);
|
||||||
@@ -145,7 +152,6 @@ 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;
|
||||||
@@ -218,17 +224,6 @@ 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'});
|
||||||
@@ -351,11 +346,6 @@ 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) ->
|
||||||
@@ -415,12 +405,6 @@ 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) ->
|
||||||
@@ -432,19 +416,16 @@ 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({bytes, _, Bin}, _Icode) ->
|
ast_body({hash, _, Hash}, _Icode) ->
|
||||||
case aeb_memory:binary_to_words(Bin) of
|
case Hash of
|
||||||
[Word] -> #integer{value = Word};
|
<<Value:32/unit:8>> -> %% address
|
||||||
Words -> #tuple{cpts = [#integer{value = W} || W <- Words]}
|
#integer{value = Value};
|
||||||
|
<<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) | aeso_memory:binary_to_words(Bin)],
|
||||||
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
#tuple{cpts = [#integer{value=X} || X <- Cpts]};
|
||||||
ast_body({tuple,_,Args}, Icode) ->
|
ast_body({tuple,_,Args}, Icode) ->
|
||||||
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
|
#tuple{cpts = [ast_body(A, Icode) || A <- Args]};
|
||||||
@@ -468,7 +449,7 @@ ast_body({app, _, {typed, _, {proj, _, {typed, _, Addr, {con, _, Contract}}, {id
|
|||||||
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
|
Gas = proplists:get_value("gas", ArgOpts ++ Defaults),
|
||||||
Value = proplists:get_value("value", ArgOpts ++ Defaults),
|
Value = proplists:get_value("value", ArgOpts ++ Defaults),
|
||||||
OutType = ast_typerep(OutT, Icode),
|
OutType = ast_typerep(OutT, Icode),
|
||||||
<<TypeHash:256>> = aeb_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
|
<<TypeHash:256>> = aeso_abi:function_type_hash(list_to_binary(FunName), ArgType, OutType),
|
||||||
%% The function is represented by its type hash (which includes the name)
|
%% The function is represented by its type hash (which includes the name)
|
||||||
Fun = #integer{value = TypeHash},
|
Fun = #integer{value = TypeHash},
|
||||||
#prim_call_contract{
|
#prim_call_contract{
|
||||||
@@ -523,8 +504,6 @@ 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) ->
|
||||||
@@ -592,29 +571,15 @@ 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)};
|
||||||
OtherType ->
|
string ->
|
||||||
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,
|
||||||
Args = [ast_body(A, Icode), ast_body(B, Icode)],
|
Neg(#funcall{ function = #var_ref{name = {builtin, str_equal}},
|
||||||
Builtin =
|
args = [ast_body(A, Icode), ast_body(B, Icode)] });
|
||||||
case OtherType of
|
_ -> gen_error({cant_compare, Ann, Op, Type})
|
||||||
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} },
|
||||||
@@ -685,7 +650,7 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
|
|||||||
true ->
|
true ->
|
||||||
PrimBin = binary:encode_unsigned(Prim),
|
PrimBin = binary:encode_unsigned(Prim),
|
||||||
ArgType = {tuple, ArgTypes},
|
ArgType = {tuple, ArgTypes},
|
||||||
<<TH:256>> = aeb_abi:function_type_hash(PrimBin, ArgType, OutType),
|
<<TH:256>> = aeso_abi:function_type_hash(PrimBin, ArgType, OutType),
|
||||||
TH;
|
TH;
|
||||||
false ->
|
false ->
|
||||||
0
|
0
|
||||||
@@ -714,7 +679,7 @@ make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
|
|||||||
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
|
ast_typerep(Def, Icode#{ type_vars := maps:merge(TypeEnv, TypeEnv1) })
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec ast_typerep(aeso_syntax:type()) -> aeb_aevm_data:type().
|
-spec ast_typerep(aeso_syntax:type()) -> aeso_sophia:type().
|
||||||
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
|
ast_typerep(Type) -> ast_typerep(Type, aeso_icode:new([])).
|
||||||
|
|
||||||
ast_typerep({id, _, Name}, Icode) ->
|
ast_typerep({id, _, Name}, Icode) ->
|
||||||
@@ -723,8 +688,6 @@ 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);
|
||||||
@@ -752,9 +715,8 @@ 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() -> bytes_t(64).
|
sign_t() ->
|
||||||
bytes_t(Len) when Len =< 32 -> word;
|
{tuple, [word, 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],
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ operand(T) -> T.
|
|||||||
str_to_icode(String) when is_list(String) ->
|
str_to_icode(String) when is_list(String) ->
|
||||||
str_to_icode(list_to_binary(String));
|
str_to_icode(list_to_binary(String));
|
||||||
str_to_icode(BinStr) ->
|
str_to_icode(BinStr) ->
|
||||||
Cpts = [size(BinStr) | aeb_memory:binary_to_words(BinStr)],
|
Cpts = [size(BinStr) | aeso_memory:binary_to_words(BinStr)],
|
||||||
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
|
#tuple{ cpts = [ #integer{value = X} || X <- Cpts ] }.
|
||||||
|
|
||||||
check_event_type(Icode) ->
|
check_event_type(Icode) ->
|
||||||
|
|||||||
+26
-27
@@ -104,12 +104,13 @@ 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()]) -> map().
|
-spec string_to_icode(string(), [option() | permissive_address_literals]) -> map().
|
||||||
string_to_icode(ContractString, Options) ->
|
string_to_icode(ContractString, Options0) ->
|
||||||
|
{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]),
|
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]),
|
||||||
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),
|
||||||
@@ -150,11 +151,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
|
%% First check the contract without the __call function and no permissive literals
|
||||||
#{} = 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, Options),
|
icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | 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
|
||||||
@@ -188,7 +189,7 @@ insert_call_function(Code, FunName, Args, Options) ->
|
|||||||
[ Code,
|
[ Code,
|
||||||
"\n\n",
|
"\n\n",
|
||||||
lists:duplicate(Ind, " "),
|
lists:duplicate(Ind, " "),
|
||||||
"stateful function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-spec insert_init_function(string(), options()) -> string().
|
-spec insert_init_function(string(), options()) -> string().
|
||||||
@@ -207,7 +208,7 @@ last_contract_indent(Decls) ->
|
|||||||
_ -> 0
|
_ -> 0
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
-spec to_sophia_value(string(), string(), ok | error | revert, aeso_sophia:data()) ->
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||||
@@ -217,7 +218,7 @@ to_sophia_value(ContractString, Fun, ResType, Data) ->
|
|||||||
to_sophia_value(_, _, error, Err, _Options) ->
|
to_sophia_value(_, _, error, Err, _Options) ->
|
||||||
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||||
to_sophia_value(_, _, revert, Data, _Options) ->
|
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||||
case aeb_heap:from_binary(string, Data) of
|
case aeso_heap:from_binary(string, Data) of
|
||||||
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end;
|
end;
|
||||||
@@ -229,7 +230,7 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
case aeb_heap:from_binary(VmType, Data) of
|
case aeso_heap:from_binary(VmType, Data) of
|
||||||
{ok, VmValue} ->
|
{ok, VmValue} ->
|
||||||
try
|
try
|
||||||
{ok, translate_vm_value(VmType, Type, VmValue)}
|
{ok, translate_vm_value(VmType, Type, VmValue)}
|
||||||
@@ -256,21 +257,19 @@ 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(Type, N) -> {Type, [], <<N:256>>}.
|
address_literal(N) -> {hash, [], <<N:256>>}. % TODO
|
||||||
|
|
||||||
%% 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(aeso_sophia:type(), aeso_syntax:type(), aeso_sophia:data()) -> aeso_syntax:expr().
|
||||||
translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
translate_vm_value(word, {id, _, "address"}, 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"}, _}, N) -> address_literal(N);
|
||||||
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N);
|
||||||
translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <<N:256>>};
|
||||||
translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N};
|
translate_vm_value(word, {id, _, "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(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) ->
|
||||||
{bytes, [], <<Val:Len/unit:8>>};
|
{hash, [], <<Hi:256, Lo:256>>};
|
||||||
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]};
|
||||||
@@ -314,12 +313,12 @@ translate_vm_value(_VmType, _Type, _Data) ->
|
|||||||
throw(cannot_translate_to_sophia).
|
throw(cannot_translate_to_sophia).
|
||||||
|
|
||||||
-spec create_calldata(string(), string(), [string()]) ->
|
-spec create_calldata(string(), string(), [string()]) ->
|
||||||
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
|
||||||
| {error, term()}.
|
| {error, term()}.
|
||||||
create_calldata(Code, Fun, Args) ->
|
create_calldata(Code, Fun, Args) ->
|
||||||
case check_call(Code, Fun, Args, []) of
|
case check_call(Code, Fun, Args, []) of
|
||||||
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||||
aeb_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
aeso_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@@ -337,7 +336,7 @@ decode_calldata(ContractString, FunName, Calldata) ->
|
|||||||
Type0 = {tuple_t, [], ArgTypes},
|
Type0 = {tuple_t, [], ArgTypes},
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
case aeso_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
||||||
{ok, {_, VmValue}} ->
|
{ok, {_, VmValue}} ->
|
||||||
try
|
try
|
||||||
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
|
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
|
||||||
@@ -398,7 +397,7 @@ get_decode_type(FunName, [_ | Contracts]) ->
|
|||||||
get_decode_type(FunName, Contracts).
|
get_decode_type(FunName, Contracts).
|
||||||
|
|
||||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||||
%% consumed by aeb_heap:to_binary().
|
%% consumed by aeso_heap:to_binary().
|
||||||
icode_to_term(word, {integer, N}) -> N;
|
icode_to_term(word, {integer, N}) -> N;
|
||||||
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
icode_to_term(string, {tuple, [{integer, Len} | Words]}) ->
|
||||||
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
<<Str:Len/binary, _/binary>> = << <<W:256>> || {integer, W} <- Words >>,
|
||||||
@@ -446,7 +445,7 @@ to_bytecode([], _) -> [].
|
|||||||
|
|
||||||
extract_type_info(#{functions := Functions} =_Icode) ->
|
extract_type_info(#{functions := Functions} =_Icode) ->
|
||||||
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
ArgTypesOnly = fun(As) -> [ T || {_, T} <- As ] end,
|
||||||
TypeInfo = [aeb_abi:function_type_info(list_to_binary(lists:last(Name)),
|
TypeInfo = [aeso_abi:function_type_info(list_to_binary(lists:last(Name)),
|
||||||
ArgTypesOnly(Args), TypeRep)
|
ArgTypesOnly(Args), TypeRep)
|
||||||
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
|| {Name, Attrs, Args,_Body, TypeRep} <- Functions,
|
||||||
not is_tuple(Name),
|
not is_tuple(Name),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,301 @@
|
|||||||
|
-module(aeso_heap).
|
||||||
|
|
||||||
|
-export([ to_binary/1
|
||||||
|
, to_binary/2
|
||||||
|
, from_heap/3
|
||||||
|
, from_binary/2
|
||||||
|
, from_binary/3
|
||||||
|
, maps_with_next_id/1
|
||||||
|
, set_next_id/2
|
||||||
|
, heap_fragment/3
|
||||||
|
, heap_value/3
|
||||||
|
, heap_value/4
|
||||||
|
, heap_value_pointer/1
|
||||||
|
, heap_value_maps/1
|
||||||
|
, heap_value_offset/1
|
||||||
|
, heap_value_heap/1
|
||||||
|
, heap_fragment_maps/1
|
||||||
|
, heap_fragment_offset/1
|
||||||
|
, heap_fragment_heap/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export_type([binary_value/0, heap_value/0, offset/0, heap_fragment/0]).
|
||||||
|
|
||||||
|
-include("aeso_icode.hrl").
|
||||||
|
-include_lib("aesophia/include/aeso_heap.hrl").
|
||||||
|
|
||||||
|
-type word() :: non_neg_integer().
|
||||||
|
-type pointer() :: word().
|
||||||
|
-opaque heap_fragment() :: #heap{}.
|
||||||
|
-type offset() :: non_neg_integer().
|
||||||
|
-type binary_value() :: binary().
|
||||||
|
-type heap_value() :: {pointer(), heap_fragment()}.
|
||||||
|
|
||||||
|
|
||||||
|
-spec maps_with_next_id(heap_fragment()) -> #maps{}.
|
||||||
|
%% Create just a maps value, don't keep rest of Heap
|
||||||
|
maps_with_next_id(#heap{maps = #maps{next_id = N}}) ->
|
||||||
|
#maps{ next_id = N }.
|
||||||
|
|
||||||
|
-spec set_next_id(heap_fragment(), non_neg_integer()) -> heap_fragment().
|
||||||
|
set_next_id(Heap, N) ->
|
||||||
|
Heap#heap{ maps = Heap#heap.maps#maps{ next_id = N } }.
|
||||||
|
|
||||||
|
%% -- data type heap_fragment
|
||||||
|
|
||||||
|
-spec heap_fragment(binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
||||||
|
heap_fragment(Heap) ->
|
||||||
|
heap_fragment(#maps{ next_id = 0 }, 0, Heap).
|
||||||
|
|
||||||
|
-spec heap_fragment(#maps{}, offset(),
|
||||||
|
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_fragment().
|
||||||
|
heap_fragment(Maps, Offset, Heap) ->
|
||||||
|
#heap{maps = Maps, offset = Offset, heap = Heap}.
|
||||||
|
|
||||||
|
-spec heap_fragment_maps(heap_fragment()) -> #maps{}.
|
||||||
|
heap_fragment_maps(#heap{maps = Maps}) ->
|
||||||
|
Maps.
|
||||||
|
|
||||||
|
-spec heap_fragment_offset(heap_fragment()) -> offset().
|
||||||
|
heap_fragment_offset(#heap{offset = Offs}) ->
|
||||||
|
Offs.
|
||||||
|
|
||||||
|
-spec heap_fragment_heap(heap_fragment()) -> binary() | #{non_neg_integer() => non_neg_integer()}.
|
||||||
|
heap_fragment_heap(#heap{heap = Heap}) ->
|
||||||
|
Heap.
|
||||||
|
|
||||||
|
|
||||||
|
%% -- data type heap_value
|
||||||
|
|
||||||
|
-spec heap_value(#maps{}, pointer(),
|
||||||
|
binary() | #{non_neg_integer() => non_neg_integer()}) -> heap_value().
|
||||||
|
heap_value(Maps, Ptr, Heap) ->
|
||||||
|
heap_value(Maps, Ptr, Heap, 0).
|
||||||
|
|
||||||
|
-spec heap_value(#maps{}, pointer(),
|
||||||
|
binary() | #{non_neg_integer() => non_neg_integer()}, offset()) -> heap_value().
|
||||||
|
heap_value(Maps, Ptr, Heap, Offs) ->
|
||||||
|
{Ptr, heap_fragment(Maps, Offs, Heap)}.
|
||||||
|
|
||||||
|
-spec heap_value_pointer(heap_value()) -> pointer().
|
||||||
|
heap_value_pointer({Ptr, _}) -> Ptr.
|
||||||
|
|
||||||
|
-spec heap_value_maps(heap_value()) -> #maps{}.
|
||||||
|
heap_value_maps({_, Heap}) -> Heap#heap.maps.
|
||||||
|
|
||||||
|
-spec heap_value_offset(heap_value()) -> offset().
|
||||||
|
heap_value_offset({_, Heap}) -> Heap#heap.offset.
|
||||||
|
|
||||||
|
-spec heap_value_heap(heap_value()) ->
|
||||||
|
binary() | #{non_neg_integer() => non_neg_integer()}.
|
||||||
|
heap_value_heap({_, Heap}) -> Heap#heap.heap.
|
||||||
|
|
||||||
|
%% -- Value to binary --------------------------------------------------------
|
||||||
|
|
||||||
|
-spec to_binary(aeso_sophia:data()) -> aeso_sophia:heap().
|
||||||
|
%% Encode the data as a heap where the first word is the value (for unboxed
|
||||||
|
%% types) or a pointer to the value (for boxed types).
|
||||||
|
to_binary(Data) ->
|
||||||
|
to_binary(Data, 0).
|
||||||
|
|
||||||
|
to_binary(Data, BaseAddress) ->
|
||||||
|
{Address, Memory} = to_binary1(Data, BaseAddress + 32),
|
||||||
|
R = <<Address:256, Memory/binary>>,
|
||||||
|
R.
|
||||||
|
|
||||||
|
|
||||||
|
%% Allocate the data in memory, from the given address. Return a pair
|
||||||
|
%% of memory contents from that address and the value representing the
|
||||||
|
%% data.
|
||||||
|
to_binary1(Data,_Address) when is_integer(Data) ->
|
||||||
|
{Data,<<>>};
|
||||||
|
to_binary1(Data, Address) when is_binary(Data) ->
|
||||||
|
%% a string
|
||||||
|
Words = aeso_memory:binary_to_words(Data),
|
||||||
|
{Address,<<(size(Data)):256, << <<W:256>> || W <- Words>>/binary>>};
|
||||||
|
to_binary1(none, Address) -> to_binary1({variant, 0, []}, Address);
|
||||||
|
to_binary1({some, Value}, Address) -> to_binary1({variant, 1, [Value]}, Address);
|
||||||
|
to_binary1(word, Address) -> to_binary1({?TYPEREP_WORD_TAG}, Address);
|
||||||
|
to_binary1(string, Address) -> to_binary1({?TYPEREP_STRING_TAG}, Address);
|
||||||
|
to_binary1(typerep, Address) -> to_binary1({?TYPEREP_TYPEREP_TAG}, Address);
|
||||||
|
to_binary1(function, Address) -> to_binary1({?TYPEREP_FUN_TAG}, Address);
|
||||||
|
to_binary1({list, T}, Address) -> to_binary1({?TYPEREP_LIST_TAG, T}, Address);
|
||||||
|
to_binary1({option, T}, Address) -> to_binary1({variant, [[], [T]]}, Address);
|
||||||
|
to_binary1({tuple, Ts}, Address) -> to_binary1({?TYPEREP_TUPLE_TAG, Ts}, Address);
|
||||||
|
to_binary1({variant, Cons}, Address) -> to_binary1({?TYPEREP_VARIANT_TAG, Cons}, Address);
|
||||||
|
to_binary1({map, K, V}, Address) -> to_binary1({?TYPEREP_MAP_TAG, K, V}, Address);
|
||||||
|
to_binary1({variant, Tag, Args}, Address) ->
|
||||||
|
to_binary1(list_to_tuple([Tag | Args]), Address);
|
||||||
|
to_binary1(Map, Address) when is_map(Map) ->
|
||||||
|
Size = maps:size(Map),
|
||||||
|
%% Sort according to binary ordering
|
||||||
|
KVs = lists:sort([ {to_binary(K), to_binary(V)} || {K, V} <- maps:to_list(Map) ]),
|
||||||
|
{Address, <<Size:256, << <<(byte_size(K)):256, K/binary,
|
||||||
|
(byte_size(V)):256, V/binary>> || {K, V} <- KVs >>/binary >>};
|
||||||
|
to_binary1({}, _Address) ->
|
||||||
|
{0, <<>>};
|
||||||
|
to_binary1(Data, Address) when is_tuple(Data) ->
|
||||||
|
{Elems,Memory} = to_binaries(tuple_to_list(Data),Address+32*size(Data)),
|
||||||
|
ElemsBin = << <<W:256>> || W <- Elems>>,
|
||||||
|
{Address,<< ElemsBin/binary, Memory/binary >>};
|
||||||
|
to_binary1([],_Address) ->
|
||||||
|
<<Nil:256>> = <<(-1):256>>,
|
||||||
|
{Nil,<<>>};
|
||||||
|
to_binary1([H|T],Address) ->
|
||||||
|
to_binary1({H,T},Address).
|
||||||
|
|
||||||
|
|
||||||
|
to_binaries([],_Address) ->
|
||||||
|
{[],<<>>};
|
||||||
|
to_binaries([H|T],Address) ->
|
||||||
|
{HRep,HMem} = to_binary1(H,Address),
|
||||||
|
{TRep,TMem} = to_binaries(T,Address+size(HMem)),
|
||||||
|
{[HRep|TRep],<<HMem/binary, TMem/binary>>}.
|
||||||
|
|
||||||
|
%% Interpret a return value (a binary) using a type rep.
|
||||||
|
|
||||||
|
-spec from_heap(Type :: ?Type(), Heap :: binary(), Ptr :: integer()) ->
|
||||||
|
{ok, term()} | {error, term()}.
|
||||||
|
from_heap(Type, Heap, Ptr) ->
|
||||||
|
try {ok, from_binary(#{}, Type, Heap, Ptr)}
|
||||||
|
catch _:Err ->
|
||||||
|
%% io:format("** Error: from_heap failed with ~p\n ~p\n", [Err, erlang:get_stacktrace()]),
|
||||||
|
{error, Err}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Base address is the address of the first word of the given heap.
|
||||||
|
-spec from_binary(T :: ?Type(),
|
||||||
|
Heap :: binary(),
|
||||||
|
BaseAddr :: non_neg_integer()) ->
|
||||||
|
{ok, term()} | {error, term()}.
|
||||||
|
from_binary(T, Heap = <<V:256, _/binary>>, BaseAddr) ->
|
||||||
|
from_heap(T, <<0:BaseAddr/unit:8, Heap/binary>>, V);
|
||||||
|
from_binary(_, Bin, _BaseAddr) ->
|
||||||
|
{error, {binary_too_short, Bin}}.
|
||||||
|
|
||||||
|
-spec from_binary(?Type(), binary()) -> {ok, term()} | {error, term()}.
|
||||||
|
from_binary(T, Heap) ->
|
||||||
|
from_binary(T, Heap, 0).
|
||||||
|
|
||||||
|
from_binary(_, word, _, V) ->
|
||||||
|
V;
|
||||||
|
from_binary(_, signed_word, _, V) ->
|
||||||
|
<<N:256/signed>> = <<V:256>>,
|
||||||
|
N;
|
||||||
|
from_binary(_, bool, _, V) ->
|
||||||
|
case V of
|
||||||
|
0 -> false;
|
||||||
|
1 -> true
|
||||||
|
end;
|
||||||
|
from_binary(_, string, Heap, V) ->
|
||||||
|
StringSize = heap_word(Heap,V),
|
||||||
|
BitAddr = 8*(V+32),
|
||||||
|
<<_:BitAddr,Bytes:StringSize/binary,_/binary>> = Heap,
|
||||||
|
Bytes;
|
||||||
|
from_binary(_, {tuple, []}, _, _) ->
|
||||||
|
{};
|
||||||
|
from_binary(Visited, {tuple,Cpts}, Heap, V) ->
|
||||||
|
check_circular_refs(Visited, V),
|
||||||
|
NewVisited = Visited#{V => true},
|
||||||
|
ElementNums = lists:seq(0, length(Cpts)-1),
|
||||||
|
TypesAndPointers = lists:zip(Cpts, ElementNums),
|
||||||
|
ElementAddress = fun(Index) -> V + 32 * Index end,
|
||||||
|
Element = fun(Index) ->
|
||||||
|
heap_word(Heap, ElementAddress(Index))
|
||||||
|
end,
|
||||||
|
Convert = fun(Type, Index) ->
|
||||||
|
from_binary(NewVisited, Type, Heap, Element(Index))
|
||||||
|
end,
|
||||||
|
Elements = [Convert(T, I) || {T,I} <- TypesAndPointers],
|
||||||
|
list_to_tuple(Elements);
|
||||||
|
from_binary(Visited, {list, Elem}, Heap, V) ->
|
||||||
|
<<Nil:256>> = <<(-1):256>>,
|
||||||
|
if V==Nil ->
|
||||||
|
[];
|
||||||
|
true ->
|
||||||
|
{H,T} = from_binary(Visited, {tuple,[Elem,{list,Elem}]},Heap,V),
|
||||||
|
[H|T]
|
||||||
|
end;
|
||||||
|
from_binary(Visited, {option, A}, Heap, V) ->
|
||||||
|
from_binary(Visited, {variant_t, [{none, []}, {some, [A]}]}, Heap, V);
|
||||||
|
from_binary(Visited, {variant, Cons}, Heap, V) ->
|
||||||
|
Tag = heap_word(Heap, V),
|
||||||
|
Args = lists:nth(Tag + 1, Cons),
|
||||||
|
Visited1 = Visited#{V => true},
|
||||||
|
{variant, Tag, tuple_to_list(from_binary(Visited1, {tuple, Args}, Heap, V + 32))};
|
||||||
|
from_binary(Visited, {variant_t, TCons}, Heap, V) -> %% Tagged variants
|
||||||
|
{Tags, Cons} = lists:unzip(TCons),
|
||||||
|
{variant, I, Args} = from_binary(Visited, {variant, Cons}, Heap, V),
|
||||||
|
Tag = lists:nth(I + 1, Tags),
|
||||||
|
case Args of
|
||||||
|
[] -> Tag;
|
||||||
|
_ -> list_to_tuple([Tag | Args])
|
||||||
|
end;
|
||||||
|
from_binary(_Visited, {map, A, B}, Heap, Ptr) ->
|
||||||
|
%% FORMAT: [Size] [KeySize] Key [ValSize] Val .. [KeySize] Key [ValSize] Val
|
||||||
|
Size = heap_word(Heap, Ptr),
|
||||||
|
map_binary_to_value(A, B, Size, Heap, Ptr + 32);
|
||||||
|
from_binary(Visited, typerep, Heap, V) ->
|
||||||
|
check_circular_refs(Visited, V),
|
||||||
|
Tag = heap_word(Heap, V),
|
||||||
|
Arg1 = fun(T, I) -> from_binary(Visited#{V => true}, T, Heap, heap_word(Heap, V + 32 * I)) end,
|
||||||
|
Arg = fun(T) -> Arg1(T, 1) end,
|
||||||
|
case Tag of
|
||||||
|
?TYPEREP_WORD_TAG -> word;
|
||||||
|
?TYPEREP_STRING_TAG -> string;
|
||||||
|
?TYPEREP_TYPEREP_TAG -> typerep;
|
||||||
|
?TYPEREP_LIST_TAG -> {list, Arg(typerep)};
|
||||||
|
?TYPEREP_TUPLE_TAG -> {tuple, Arg({list, typerep})};
|
||||||
|
?TYPEREP_VARIANT_TAG -> {variant, Arg({list, {list, typerep}})};
|
||||||
|
?TYPEREP_MAP_TAG -> {map, Arg(typerep), Arg1(typerep, 2)};
|
||||||
|
?TYPEREP_FUN_TAG -> function
|
||||||
|
end.
|
||||||
|
|
||||||
|
map_binary_to_value(KeyType, ValType, N, Bin, Ptr) ->
|
||||||
|
%% Avoid looping on bogus sizes
|
||||||
|
MaxN = byte_size(Bin) div 64,
|
||||||
|
Heap = heap_fragment(Bin),
|
||||||
|
map_from_binary({value, KeyType, ValType}, min(N, MaxN), Heap, Ptr, #{}).
|
||||||
|
|
||||||
|
map_from_binary(_, 0, _, _, Map) -> Map;
|
||||||
|
map_from_binary({value, KeyType, ValType} = Output, I, Heap, Ptr, Map) ->
|
||||||
|
KeySize = get_word(Heap, Ptr),
|
||||||
|
KeyPtr = Ptr + 32,
|
||||||
|
KeyBin = get_chunk(Heap, KeyPtr, KeySize),
|
||||||
|
ValSize = get_word(Heap, KeyPtr + KeySize),
|
||||||
|
ValPtr = KeyPtr + KeySize + 32,
|
||||||
|
ValBin = get_chunk(Heap, ValPtr, ValSize),
|
||||||
|
%% Keys and values are self contained binaries
|
||||||
|
{ok, Key} = from_binary(KeyType, KeyBin),
|
||||||
|
{ok, Val} = from_binary(ValType, ValBin),
|
||||||
|
map_from_binary(Output, I - 1, Heap, ValPtr + ValSize, Map#{Key => Val}).
|
||||||
|
|
||||||
|
check_circular_refs(Visited, V) ->
|
||||||
|
case maps:is_key(V, Visited) of
|
||||||
|
true -> exit(circular_references);
|
||||||
|
false -> ok
|
||||||
|
end.
|
||||||
|
|
||||||
|
heap_word(Heap, Addr) when is_binary(Heap) ->
|
||||||
|
BitSize = 8*Addr,
|
||||||
|
<<_:BitSize,W:256,_/binary>> = Heap,
|
||||||
|
W;
|
||||||
|
heap_word(Heap, Addr) when is_map(Heap) ->
|
||||||
|
0 = Addr rem 32, %% Check that it's word aligned.
|
||||||
|
maps:get(Addr, Heap, 0).
|
||||||
|
|
||||||
|
get_word(#heap{offset = Offs, heap = Mem}, Addr) when Addr >= Offs ->
|
||||||
|
get_word(Mem, Addr - Offs);
|
||||||
|
get_word(Mem, Addr) when is_binary(Mem) ->
|
||||||
|
<<_:Addr/unit:8, Word:256, _/binary>> = Mem,
|
||||||
|
Word.
|
||||||
|
|
||||||
|
get_chunk(#heap{offset = Offs, heap = Mem}, Addr, Bytes) when Addr >= Offs ->
|
||||||
|
get_chunk(Mem, Addr - Offs, Bytes);
|
||||||
|
get_chunk(Mem, Addr, Bytes) when is_binary(Mem) ->
|
||||||
|
<<_:Addr/unit:8, Chunk:Bytes/binary, _/binary>> = Mem,
|
||||||
|
Chunk.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+5
-5
@@ -25,14 +25,14 @@
|
|||||||
|
|
||||||
-include("aeso_icode.hrl").
|
-include("aeso_icode.hrl").
|
||||||
|
|
||||||
-type type_def() :: fun(([aeb_aevm_data:type()]) -> aeb_aevm_data:type()).
|
-type type_def() :: fun(([aeso_sophia:type()]) -> aeso_sophia:type()).
|
||||||
|
|
||||||
-type bindings() :: any().
|
-type bindings() :: any().
|
||||||
-type fun_dec() :: { string()
|
-type fun_dec() :: { string()
|
||||||
, [modifier()]
|
, [modifier()]
|
||||||
, arg_list()
|
, arg_list()
|
||||||
, expr()
|
, expr()
|
||||||
, aeb_aevm_data:type()}.
|
, aeso_sophia:type()}.
|
||||||
|
|
||||||
-type modifier() :: private | stateful.
|
-type modifier() :: private | stateful.
|
||||||
|
|
||||||
@@ -42,10 +42,10 @@
|
|||||||
, functions => [fun_dec()]
|
, functions => [fun_dec()]
|
||||||
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
, namespace => aeso_syntax:con() | aeso_syntax:qcon()
|
||||||
, env => [bindings()]
|
, env => [bindings()]
|
||||||
, state_type => aeb_aevm_data:type()
|
, state_type => aeso_sophia:type()
|
||||||
, event_type => aeb_aevm_data:type()
|
, event_type => aeso_sophia:type()
|
||||||
, types => #{ type_name() => type_def() }
|
, types => #{ type_name() => type_def() }
|
||||||
, type_vars => #{ string() => aeb_aevm_data:type() }
|
, type_vars => #{ string() => aeso_sophia:type() }
|
||||||
, constructors => #{ [string()] => integer() } %% name to tag
|
, constructors => #{ [string()] => integer() } %% name to tag
|
||||||
, options => [any()]
|
, options => [any()]
|
||||||
}.
|
}.
|
||||||
|
|||||||
+10
-1
@@ -1,5 +1,14 @@
|
|||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_typerep_def.hrl").
|
-define(Type(), aeso_sophia:type()).
|
||||||
|
|
||||||
|
-define(TYPEREP_WORD_TAG, 0).
|
||||||
|
-define(TYPEREP_STRING_TAG, 1).
|
||||||
|
-define(TYPEREP_LIST_TAG, 2).
|
||||||
|
-define(TYPEREP_TUPLE_TAG, 3).
|
||||||
|
-define(TYPEREP_VARIANT_TAG, 4).
|
||||||
|
-define(TYPEREP_TYPEREP_TAG, 5).
|
||||||
|
-define(TYPEREP_MAP_TAG, 6).
|
||||||
|
-define(TYPEREP_FUN_TAG, 7).
|
||||||
|
|
||||||
-record(arg, {name::string(), type::?Type()}).
|
-record(arg, {name::string(), type::?Type()}).
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ make_args(Args) ->
|
|||||||
|
|
||||||
fun_hash({FName, _, Args, _, TypeRep}) ->
|
fun_hash({FName, _, Args, _, TypeRep}) ->
|
||||||
ArgType = {tuple, [T || {_, T} <- Args]},
|
ArgType = {tuple, [T || {_, T} <- Args]},
|
||||||
<<Hash:256>> = aeb_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
<<Hash:256>> = aeso_abi:function_type_hash(list_to_binary(lists:last(FName)), ArgType, TypeRep),
|
||||||
{integer, Hash}.
|
{integer, Hash}.
|
||||||
|
|
||||||
%% Expects two return addresses below N elements on the stack. Picks the top
|
%% Expects two return addresses below N elements on the stack. Picks the top
|
||||||
@@ -343,8 +343,6 @@ 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) ->
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @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.
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
||||||
|
%%% @doc
|
||||||
|
%%% Memory speifics that compiler and VM need to agree upon
|
||||||
|
%%% @end
|
||||||
|
%%% Created : 19 Dec 2018
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeso_memory).
|
||||||
|
|
||||||
|
-export([binary_to_words/1]).
|
||||||
|
|
||||||
|
binary_to_words(<<>>) ->
|
||||||
|
[];
|
||||||
|
binary_to_words(<<N:256,Bin/binary>>) ->
|
||||||
|
[N|binary_to_words(Bin)];
|
||||||
|
binary_to_words(Bin) ->
|
||||||
|
binary_to_words(<<Bin/binary,0>>).
|
||||||
|
|
||||||
+12
-32
@@ -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,7 +99,9 @@ con_arg() -> choice(type(), ?RULE(keyword(indexed), type(), set_ann(indexed,
|
|||||||
%% -- Let declarations -------------------------------------------------------
|
%% -- Let declarations -------------------------------------------------------
|
||||||
|
|
||||||
letdecl() ->
|
letdecl() ->
|
||||||
?RULE(keyword('let'), letdef(), set_pos(get_pos(_1), _2)).
|
choice(
|
||||||
|
?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()).
|
||||||
|
|
||||||
@@ -134,15 +136,11 @@ type200() ->
|
|||||||
type300() -> type400().
|
type300() -> type400().
|
||||||
|
|
||||||
type400() ->
|
type400() ->
|
||||||
choice(
|
?RULE(typeAtom(), optional(type_args()),
|
||||||
[?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(
|
||||||
@@ -205,8 +203,8 @@ exprAtom() ->
|
|||||||
?LAZY_P(begin
|
?LAZY_P(begin
|
||||||
Expr = ?LAZY_P(expr()),
|
Expr = ?LAZY_P(expr()),
|
||||||
choice(
|
choice(
|
||||||
[ id_or_addr(), con(), token(qid), token(qcon)
|
[ id(), con(), token(qid), token(qcon)
|
||||||
, token(bytes), token(string), token(char)
|
, token(hash), 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}
|
||||||
@@ -328,26 +326,6 @@ 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)).
|
||||||
@@ -439,7 +417,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, {tuple, [{origin, system}], []}}.
|
{'if', Ann, Cond, Then, {unit, [{origin, system}]}}.
|
||||||
|
|
||||||
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
else_branches([Elif = {elif, _, _, _} | Stmts], Acc) ->
|
||||||
else_branches(Stmts, [Elif | Acc]);
|
else_branches(Stmts, [Elif | Acc]);
|
||||||
@@ -455,6 +433,7 @@ 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}.
|
||||||
|
|
||||||
@@ -475,9 +454,10 @@ 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 = {bytes, _, _}) -> E;
|
parse_pattern(E = {hash, _, _}) -> 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).
|
||||||
|
|||||||
+8
-14
@@ -160,7 +160,8 @@ 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) ->
|
||||||
@@ -183,7 +184,9 @@ 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) ->
|
||||||
@@ -233,8 +236,6 @@ 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));
|
||||||
@@ -318,17 +319,9 @@ 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(_, {bytes, _, Bin}) ->
|
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
|
||||||
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(_, {Type, _, Bin})
|
expr_p(_, {unit, _}) -> text("()");
|
||||||
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
|
||||||
@@ -432,6 +425,7 @@ 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, []).
|
||||||
|
|||||||
+10
-8
@@ -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, "+"],
|
||||||
BYTES = ["#", HEXDIGIT, "+"],
|
HASH = ["#", 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", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
Keywords = ["contract", "include", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
|
||||||
"stateful", "true", "false", "mod", "public", "private", "indexed", "internal", "namespace"],
|
"stateful", "true", "false", "and", "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)}
|
||||||
, {BYTES, token(bytes, fun parse_bytes/1)}
|
, {HASH, token(hash, fun parse_hash/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,8 +117,10 @@ unescape([C | Chars], Acc) ->
|
|||||||
|
|
||||||
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16).
|
||||||
|
|
||||||
parse_bytes("#" ++ Chars) ->
|
parse_hash("#" ++ Chars) ->
|
||||||
N = list_to_integer(Chars, 16),
|
N = list_to_integer(Chars, 16),
|
||||||
Digits = (length(Chars) + 1) div 2,
|
case length(Chars) > 64 of %% 64 hex digits = 32 bytes
|
||||||
<<N:Digits/unit:8>>.
|
true -> <<N:64/unit:8>>; %% signature
|
||||||
|
false -> <<N:32/unit:8>> %% address
|
||||||
|
end.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-module(aeso_sophia).
|
||||||
|
|
||||||
|
-export_type([data/0,
|
||||||
|
type/0,
|
||||||
|
heap/0]).
|
||||||
|
|
||||||
|
-type type() :: word | signed_word | string | typerep | function
|
||||||
|
| {list, type()}
|
||||||
|
| {option, type()}
|
||||||
|
| {tuple, [type()]}
|
||||||
|
| {variant, [[type()]]}.
|
||||||
|
|
||||||
|
|
||||||
|
-type data() :: none
|
||||||
|
| {some, data()}
|
||||||
|
| {option, data()}
|
||||||
|
| word
|
||||||
|
| string
|
||||||
|
| {list, data()}
|
||||||
|
| {tuple, [data()]}
|
||||||
|
| {variant, integer(), [data()]}
|
||||||
|
| integer()
|
||||||
|
| binary()
|
||||||
|
| [data()]
|
||||||
|
| {}
|
||||||
|
| {data()}
|
||||||
|
| {data(), data()}.
|
||||||
|
|
||||||
|
-type heap() :: binary().
|
||||||
|
|
||||||
+4
-7
@@ -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()} | stateful | private].
|
-type ann() :: [{line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}].
|
||||||
|
|
||||||
-type name() :: string().
|
-type name() :: string().
|
||||||
-type id() :: {id, ann(), name()}.
|
-type id() :: {id, ann(), name()}.
|
||||||
@@ -43,7 +43,8 @@
|
|||||||
|
|
||||||
-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()}.
|
||||||
|
|
||||||
@@ -59,7 +60,6 @@
|
|||||||
-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,10 +70,7 @@
|
|||||||
:: {int, ann(), integer()}
|
:: {int, ann(), integer()}
|
||||||
| {bool, ann(), true | false}
|
| {bool, ann(), true | false}
|
||||||
| {hash, ann(), binary()}
|
| {hash, ann(), binary()}
|
||||||
| {account_pubkey, binary()}
|
| {unit, ann()}
|
||||||
| {contract_pubkey, binary()}
|
|
||||||
| {oracle_pubkey, binary()}
|
|
||||||
| {oracle_query_id, binary()}
|
|
||||||
| {string, ann(), binary()}
|
| {string, ann(), binary()}
|
||||||
| {char, ann(), integer()}.
|
| {char, ann(), integer()}.
|
||||||
|
|
||||||
|
|||||||
+22
-18
@@ -39,6 +39,11 @@ 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));
|
||||||
@@ -50,6 +55,7 @@ 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);
|
||||||
@@ -98,31 +104,29 @@ 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() => aeso_syntax:ann()}).
|
-spec entity_alg() -> alg([entity()]).
|
||||||
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 = [E || E <- maps:keys(Ys), IsBound(E)],
|
{Bound, Others} = lists:partition(IsBound, Ys),
|
||||||
Others = Remove(Bound, Ys),
|
|
||||||
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
Bound1 = [ {Unbind(Tag), X} || {Tag, X} <- Bound ],
|
||||||
maps:merge(Remove(Bound1, Xs), Others)
|
lists:umerge(Xs -- Bound1, Others)
|
||||||
end,
|
end,
|
||||||
#alg{ zero = #{}
|
#alg{ zero = []
|
||||||
, plus = fun maps:merge/2
|
, plus = fun lists:umerge/2
|
||||||
, scoped = Scoped }.
|
, scoped = Scoped }.
|
||||||
|
|
||||||
-spec used(_) -> [{entity(), aeso_syntax:ann()}].
|
-spec used(_) -> [entity()].
|
||||||
used(D) ->
|
used(D) ->
|
||||||
Kind = fun(expr) -> term;
|
Kind = fun(expr) -> term;
|
||||||
(bind_expr) -> bound_term;
|
(bind_expr) -> bound_term;
|
||||||
@@ -130,14 +134,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 =
|
||||||
maps:to_list(fold(entity_alg(),
|
fold(entity_alg(),
|
||||||
fun(K, {id, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
fun(K, {id, _, X}) -> [{Kind(K), [X]}];
|
||||||
(K, {qid, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
(K, {qid, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
||||||
(K, {con, Ann, X}) -> #{{Kind(K), [X]} => Ann};
|
(K, {con, _, X}) -> [{Kind(K), [X]}];
|
||||||
(K, {qcon, Ann, Xs}) -> #{{Kind(K), Xs} => Ann, NS(Xs) => Ann};
|
(K, {qcon, _, Xs}) -> [{Kind(K), Xs}, NS(Xs)];
|
||||||
(_, _) -> #{}
|
(_, _) -> []
|
||||||
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, "3.0.0"},
|
{vsn, "2.0.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
-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]).
|
||||||
+9
-24
@@ -4,9 +4,6 @@
|
|||||||
-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(),
|
||||||
@@ -22,8 +19,8 @@ sandbox(Code) ->
|
|||||||
|
|
||||||
malicious_from_binary_test() ->
|
malicious_from_binary_test() ->
|
||||||
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
||||||
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
|
{ok, {error, circular_references}} = ?SANDBOX(aeso_heap:from_binary({list, word}, CircularList)),
|
||||||
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeso_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
from_words(Ws) ->
|
from_words(Ws) ->
|
||||||
@@ -100,15 +97,7 @@ 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}"]),
|
||||||
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
[16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]),
|
||||||
[?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() ->
|
||||||
@@ -137,9 +126,7 @@ parameterized_contract(FunName, Types) ->
|
|||||||
|
|
||||||
parameterized_contract(ExtraCode, FunName, Types) ->
|
parameterized_contract(ExtraCode, FunName, Types) ->
|
||||||
lists:flatten(
|
lists:flatten(
|
||||||
["contract Remote =\n"
|
["contract Dummy =\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"
|
||||||
@@ -152,15 +139,13 @@ 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", ["ok_111111111111111111111111111111ZrdqRz9",
|
aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []),
|
||||||
"oq_1111111111111111111111111111113AFEFpt5"], []),
|
|
||||||
|
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
permissive_literals_fail_test() ->
|
permissive_literals_fail_test() ->
|
||||||
Contract =
|
Contract =
|
||||||
"contract OracleTest =\n"
|
"contract OracleTest =\n"
|
||||||
" stateful function haxx(o : oracle(list(string), option(int))) =\n"
|
" 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"], []),
|
||||||
@@ -176,7 +161,7 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
|
|||||||
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
||||||
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
|
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
|
||||||
?assertEqual(RetVMType1, RetVMType),
|
?assertEqual(RetVMType1, RetVMType),
|
||||||
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
{ok, {_Hash, ArgTuple}} = aeso_heap:from_binary(CalldataType, Calldata),
|
||||||
case FunName of
|
case FunName of
|
||||||
"init" ->
|
"init" ->
|
||||||
ok;
|
ok;
|
||||||
@@ -192,8 +177,8 @@ encode_decode(T, D) ->
|
|||||||
D.
|
D.
|
||||||
|
|
||||||
encode(D) ->
|
encode(D) ->
|
||||||
aeb_heap:to_binary(D).
|
aeso_heap:to_binary(D).
|
||||||
|
|
||||||
decode(T,B) ->
|
decode(T,B) ->
|
||||||
{ok, D} = aeb_heap:from_binary(T, B),
|
{ok, D} = aeso_heap:from_binary(T, B),
|
||||||
D.
|
D.
|
||||||
|
|||||||
+27
-58
@@ -5,72 +5,41 @@
|
|||||||
|
|
||||||
do_test() ->
|
do_test() ->
|
||||||
test_contract(1),
|
test_contract(1),
|
||||||
test_contract(2),
|
test_contract(2).
|
||||||
test_contract(3).
|
|
||||||
|
|
||||||
test_contract(N) ->
|
test_contract(N) ->
|
||||||
{Contract,MapACI,DecACI} = test_cases(N),
|
{Contract,DecACI} = test_cases(N),
|
||||||
{ok,JSON} = aeso_aci:encode(Contract),
|
{ok,Enc} = aeso_aci:encode(Contract),
|
||||||
?assertEqual(MapACI, jsx:decode(JSON, [return_maps])),
|
?assertEqual(DecACI, jsx:decode(Enc)).
|
||||||
?assertEqual(DecACI, aeso_aci:decode(JSON)).
|
|
||||||
|
|
||||||
test_cases(1) ->
|
test_cases(1) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"contract C =\n"
|
||||||
" function a(i : int) = i+1\n">>,
|
" function a(i : int) = i+1\n">>,
|
||||||
MapACI = #{<<"contract">> =>
|
DecodedACI = [{<<"contract">>,
|
||||||
#{<<"name">> => <<"C">>,
|
[{<<"name">>,<<"C">>},
|
||||||
<<"type_defs">> => [],
|
{<<"type_defs">>,[]},
|
||||||
<<"functions">> =>
|
{<<"functions">>,
|
||||||
[#{<<"name">> => <<"a">>,
|
[[{<<"name">>,<<"a">>},
|
||||||
<<"arguments">> =>
|
{<<"arguments">>,
|
||||||
[#{<<"name">> => <<"i">>,
|
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
|
||||||
<<"type">> => [<<"int">>]}],
|
{<<"type">>,<<"int">>},
|
||||||
<<"returns">> => <<"int">>,
|
{<<"stateful">>,false}]]}]}],
|
||||||
<<"stateful">> => false}]}},
|
{Contract,DecodedACI};
|
||||||
DecACI = <<"contract C =\n"
|
|
||||||
" function a : (int) => int\n">>,
|
|
||||||
{Contract,MapACI,DecACI};
|
|
||||||
|
|
||||||
test_cases(2) ->
|
test_cases(2) ->
|
||||||
Contract = <<"contract C =\n"
|
Contract = <<"contract C =\n"
|
||||||
" type allan = int\n"
|
" type allan = int\n"
|
||||||
" function a(i : allan) = i+1\n">>,
|
" function a(i : allan) = i+1\n">>,
|
||||||
MapACI = #{<<"contract">> =>
|
DecodedACI = [{<<"contract">>,
|
||||||
#{<<"name">> => <<"C">>,
|
[{<<"name">>,<<"C">>},
|
||||||
<<"type_defs">> =>
|
{<<"type_defs">>,
|
||||||
[#{<<"name">> => <<"allan">>,
|
[[{<<"name">>,<<"allan">>},
|
||||||
<<"typedef">> => <<"int">>,
|
{<<"vars">>,[]},
|
||||||
<<"vars">> => []}],
|
{<<"typedef">>,<<"int">>}]]},
|
||||||
<<"functions">> =>
|
{<<"functions">>,
|
||||||
[#{<<"arguments">> =>
|
[[{<<"name">>,<<"a">>},
|
||||||
[#{<<"name">> => <<"i">>,
|
{<<"arguments">>,
|
||||||
<<"type">> => [<<"int">>]}],
|
[[{<<"name">>,<<"i">>},{<<"type">>,<<"int">>}]]},
|
||||||
<<"name">> => <<"a">>,
|
{<<"type">>,<<"int">>},
|
||||||
<<"returns">> => <<"int">>,
|
{<<"stateful">>,false}]]}]}],
|
||||||
<<"stateful">> => false}]}},
|
{Contract,DecodedACI}.
|
||||||
DecACI = <<"contract C =\n"
|
|
||||||
" function a : (int) => int\n">>,
|
|
||||||
{Contract,MapACI,DecACI};
|
|
||||||
test_cases(3) ->
|
|
||||||
Contract = <<"contract C =\n"
|
|
||||||
" datatype bert('a) = Bin('a)\n"
|
|
||||||
" function a(i : bert(string)) = 1\n">>,
|
|
||||||
MapACI = #{<<"contract">> =>
|
|
||||||
#{<<"functions">> =>
|
|
||||||
[#{<<"arguments">> =>
|
|
||||||
[#{<<"name">> => <<"i">>,
|
|
||||||
<<"type">> =>
|
|
||||||
[#{<<"C.bert">> => [<<"string">>]}]}],
|
|
||||||
<<"name">> => <<"a">>,<<"returns">> => <<"int">>,
|
|
||||||
<<"stateful">> => false}],
|
|
||||||
<<"name">> => <<"C">>,
|
|
||||||
<<"type_defs">> =>
|
|
||||||
[#{<<"name">> => <<"bert">>,
|
|
||||||
<<"typedef">> =>
|
|
||||||
#{<<"variant">> =>
|
|
||||||
[#{<<"Bin">> => [<<"'a">>]}]},
|
|
||||||
<<"vars">> => [#{<<"name">> => <<"'a">>}]}]}},
|
|
||||||
DecACI = <<"contract C =\n"
|
|
||||||
" datatype bert('a) = Bin('a)\n"
|
|
||||||
" function a : (C.bert(string)) => int\n">>,
|
|
||||||
{Contract,MapACI,DecACI}.
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
-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
|
||||||
@@ -18,14 +16,10 @@
|
|||||||
simple_compile_test_() ->
|
simple_compile_test_() ->
|
||||||
[ {"Testing the " ++ ContractName ++ " contract",
|
[ {"Testing the " ++ ContractName ++ " contract",
|
||||||
fun() ->
|
fun() ->
|
||||||
case compile(ContractName) of
|
#{byte_code := ByteCode,
|
||||||
#{byte_code := ByteCode,
|
contract_source := _,
|
||||||
contract_source := _,
|
type_info := _} = compile(ContractName),
|
||||||
type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode);
|
?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() ->
|
||||||
@@ -86,7 +80,6 @@ compilable_contracts() ->
|
|||||||
"dutch_auction",
|
"dutch_auction",
|
||||||
"environment",
|
"environment",
|
||||||
"factorial",
|
"factorial",
|
||||||
"functions",
|
|
||||||
"fundme",
|
"fundme",
|
||||||
"identity",
|
"identity",
|
||||||
"maps",
|
"maps",
|
||||||
@@ -105,11 +98,7 @@ 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
|
||||||
@@ -191,8 +180,6 @@ 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)">>]}
|
||||||
@@ -231,102 +218,4 @@ 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]) ++
|
lists:map(Lit, [contract, type, 'let', switch, rec, 'and']) ++
|
||||||
%% 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(bytes, Hash)
|
, Tok(hash, 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({bytes, _, <<N:256>>}) -> fmt("#~64.16.0b", N);
|
show_token({hash, _, <<N:256>>}) -> fmt("#~.16b", N);
|
||||||
show_token({comment, _, S}) -> S;
|
show_token({comment, _, S}) -> S;
|
||||||
show_token({_, _, _}) -> "TODO".
|
show_token({_, _, _}) -> "TODO".
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
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,6 +36,11 @@ 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
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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 }
|
||||||
|
|
||||||
stateful function authorize(n : int, s : signature) : bool =
|
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 })
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
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
|
||||||
stateful function tick() = put(state{ value = state.value + 1 })
|
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 stateful function spend(to, amount) =
|
private 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}
|
||||||
|
|
||||||
stateful function set_remote(remote) = put({remote = remote})
|
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
|
||||||
stateful function nested_value(value : int) : int =
|
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}
|
||||||
|
|
||||||
stateful function set_worker(worker) = put(state{worker = worker})
|
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
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
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 stateful function spend(args : spend_args) =
|
private 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 = ak_11111111111111111111111111111111273Yts })
|
put(state{ beneficiary = #0 })
|
||||||
|
|
||||||
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} }
|
||||||
stateful function map_state_i() = put(state{ map_i = map_i() })
|
function map_state_i() = put(state{ map_i = map_i() })
|
||||||
stateful function map_state_s() = put(state{ map_s = map_s() })
|
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 }
|
||||||
stateful function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
|
function set_state_i(k, p) = put(state{ map_i = set_i(k, p, state.map_i) })
|
||||||
stateful function set_state_s(k, p) = put(state{ map_s = set_s(k, p, state.map_s) })
|
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 }
|
||||||
stateful function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
function setx_state_i(k, x) = put(state{ map_i[k].x = x })
|
||||||
stateful function setx_state_s(k, x) = put(state{ map_s[k].x = x })
|
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 }
|
||||||
stateful function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
|
function addx_state_i(k, d) = put(state{ map_i[k].x @ x = x + d })
|
||||||
stateful function addx_state_s(k, d) = put(state{ map_s[k].x @ x = x + d })
|
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)
|
||||||
stateful function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
|
function delete_state_i(k) = put(state{ map_i = delete_i(k, state.map_i) })
|
||||||
stateful function delete_state_s(k) = put(state{ map_s = delete_s(k, state.map_s) })
|
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)
|
||||||
stateful function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
function fromlist_state_i(xs) = put(state{ map_i = fromlist_i(xs) })
|
||||||
stateful function fromlist_state_s(xs) = put(state{ map_s = fromlist_s(xs) })
|
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)
|
||||||
|
|
||||||
stateful function registerOracle(acct : address,
|
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)
|
||||||
|
|
||||||
stateful function registerIntIntOracle(acct : address,
|
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)
|
||||||
|
|
||||||
stateful function registerStringStringOracle(acct : address,
|
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)
|
||||||
|
|
||||||
stateful function signedRegisterOracle(acct : address,
|
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)
|
||||||
|
|
||||||
stateful function createQuery(o : oracle_id,
|
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!
|
||||||
stateful function unsafeCreateQuery(o : oracle_id,
|
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!
|
||||||
stateful function unsafeCreateQueryThenErr(o : oracle_id,
|
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
|
||||||
|
|
||||||
stateful function extendOracle(o : oracle_id,
|
function extendOracle(o : oracle_id,
|
||||||
ttl : ttl) : () =
|
ttl : ttl) : () =
|
||||||
Oracle.extend(o, ttl)
|
Oracle.extend(o, ttl)
|
||||||
|
|
||||||
stateful function signedExtendOracle(o : oracle_id,
|
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)
|
||||||
|
|
||||||
stateful function respond(o : oracle_id,
|
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)
|
||||||
|
|
||||||
stateful function signedRespond(o : oracle_id,
|
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)
|
||||||
|
|
||||||
stateful function complexOracle(question) =
|
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)
|
||||||
|
|
||||||
stateful function signedComplexOracle(question, sig) =
|
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 =
|
||||||
|
|
||||||
stateful function call(r : Remote1, x : int) : int =
|
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) =
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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
|
||||||
|
|
||||||
stateful function set(value : int) =
|
function set(value : int) =
|
||||||
put(state{data = value})
|
put(state{data = value})
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ contract SpendContract =
|
|||||||
|
|
||||||
contract SpendTest =
|
contract SpendTest =
|
||||||
|
|
||||||
stateful function spend(to, amount) =
|
function spend(to, amount) =
|
||||||
let total = Contract.balance
|
let total = Contract.balance
|
||||||
Chain.spend(to, amount)
|
Chain.spend(to, amount)
|
||||||
total - amount
|
total - amount
|
||||||
|
|
||||||
stateful function withdraw(amount) : int =
|
function withdraw(amount) : int =
|
||||||
spend(Call.caller, amount)
|
spend(Call.caller, amount)
|
||||||
|
|
||||||
stateful function withdraw_from(account, amount) =
|
function withdraw_from(account, amount) =
|
||||||
account.withdraw(amount)
|
account.withdraw(amount)
|
||||||
withdraw(amount)
|
withdraw(amount)
|
||||||
|
|
||||||
stateful function spend_from(from, to, amount) =
|
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
|
||||||
|
|
||||||
stateful function update(new_state : state) = put(new_state)
|
function update(new_state : state) = put(new_state)
|
||||||
stateful function update_i(new_i) = put(state{ i = new_i })
|
function update_i(new_i) = put(state{ i = new_i })
|
||||||
stateful function update_s(new_s) = put(state{ s = new_s })
|
function update_s(new_s) = put(state{ s = new_s })
|
||||||
stateful function update_m(new_m) = put(state{ m = new_m })
|
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)
|
||||||
stateful function nop(r : Remote) = put(state{ i = state.i })
|
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)
|
||||||
|
|
||||||
stateful function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
|
function remote_update_i (r : Remote, i) = put(r.fun_update_i(state, i))
|
||||||
stateful function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
|
function remote_update_s (r : Remote, s) = put(r.fun_update_s(state, s))
|
||||||
stateful function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
|
function remote_update_m (r : Remote, m) = put(r.fun_update_m(state, m))
|
||||||
stateful function remote_update_mk(r : Remote, k, v) = put(r.fun_update_mk(state, k, v))
|
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) = ()
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
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,5 +40,3 @@ 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")
|
||||||
|
|
||||||
stateful function start(bal : int) =
|
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)}))
|
||||||
|
|
||||||
stateful function stop() =
|
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
|
||||||
stateful function set_color(c) = switch(state) Started(st) => put(Started(st{color = c}))
|
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