Merge branch 'master' into quickcheck-ci

This commit is contained in:
Thomas Arts 2019-02-11 13:11:46 +01:00 committed by GitHub
commit be9935cd7e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 531 additions and 229 deletions

37
.circleci/config.yml Normal file
View File

@ -0,0 +1,37 @@
version: 2.1
executors:
aebuilder:
docker:
- image: aeternity/builder
user: builder
working_directory: ~/aesophia
jobs:
build:
executor: aebuilder
steps:
- checkout
- restore_cache:
keys:
- dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
- dialyzer-cache-v2-{{ .Branch }}-
- dialyzer-cache-v2-
- run:
name: Build
command: rebar3 compile
- run:
name: Static Analysis
command: rebar3 dialyzer
- run:
name: Eunit
command: rebar3 eunit
- run:
name: Common Tests
command: rebar3 ct
- save_cache:
key: dialyzer-cache-v2-{{ .Branch }}-{{ .Revision }}
paths:
- _build/default/rebar3_20.3.8_plt
- store_artifacts:
path: _build/test/logs

3
.gitignore vendored
View File

@ -18,3 +18,6 @@ _build
rebar3.crashdump rebar3.crashdump
current_counterexample.eqc current_counterexample.eqc
.qcci .qcci
*.erl~
*.aes~

View File

@ -6,5 +6,11 @@ For more information about æternity smart contracts and the sophia language see
It is an OTP application written in Erlang and is by default included in It is an OTP application written in Erlang and is by default included in
[the æternity node](https://github.com/aeternity/epoch). However, it can [the æternity node](https://github.com/aeternity/epoch). However, it can
also be included in other system to compile contracts coded in sophia which also be included in other systems to compile contracts coded in sophia which
can then be loaded into the æternity system. can then be loaded into the æternity system.
## Interface Modules
The basic modules for interfacing the compiler:
* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md)

86
docs/aeso_compiler.md Normal file
View File

@ -0,0 +1,86 @@
# aeso_compiler
### Module
### aeso_compiler
The Sophia compiler
### Description
This module provides the interface to the standard Sophia compiler. It
returns the compiled module in a map which can then be loaded.
### Types
``` erlang
contract_string() = string() | binary()
contract_map() = #{bytecode => binary(),
compiler_version => string(),
contract_souce => string(),
type_info => type_info()}
type_info()
errorstring() = binary()
```
### Exports
#### file(File)
#### file(File, Options) -> CompRet
#### from_string(ContractString, Options) -> CompRet
Types
``` erlang
ContractString = contract_string()
Options = [Option]
CompRet = {ok,ContractMap} | {error,ErrorString}
ContractMap = contract_map()
ErrorString = errorstring()
```
Compile a contract defined in a file or in a string.
The **pp_** options all print to standard output the following:
`pp_sophia_code` - print the input Sophia code.
`pp_ast` - print the AST of the code
`pp_types` - print information about the types
`pp_typed_ast` - print the AST with type information at each node
`pp_icode` - print the internal code structure
`pp_assembler` - print the generated assembler code
`pp_bytecode` - print the bytecode instructions
#### check_call(ContractString, Options) -> CheckRet
Types
```
ContractString = string() | binary()
CheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}
Types = [Type]
Type = term()
```
Check a call in contract through the `__call` function.
#### sophia_type_to_typerep(String) -> TypeRep
Types
``` erlang
{ok,TypeRep} | {error, badtype}
```
Get the type representation of a type declaration.
#### version() -> Version
Types
``` erlang
Version = integer()
```
Get the current version of the Sophia compiler.

View File

@ -1,16 +1,32 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
%% NOTE: When possible deps are referenced by Git ref to ensure consistency between builds.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"720510a"}}} {ref,"720510a"}}}
, {getopt, "1.0.1"}
]}. ]}.
{profiles, [ {eqc, [{erl_opts, [{parse_transform, eqc_cover}]}, {profiles, [ {eqc, [{erl_opts, [{parse_transform, eqc_cover}]},
{deps, [{eqc_ci, "1.0.0"}]}, {deps, [{eqc_ci, "1.0.0"}]},
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc! {extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
]} ]}
]}. ]}.
{escript_incl_apps, [aesophia, aebytecode, getopt]}.
{escript_main_app, aesophia}.
{escript_name, aesophia}.
{escript_emu_args, "%%! +sbtu +A0\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},

View File

@ -1,4 +1,10 @@
{"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}}, {ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
0}]. 0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
[
{pkg_hash,[
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
].

View File

@ -27,8 +27,8 @@
-type typerep() :: aeso_sophia:type(). -type typerep() :: aeso_sophia:type().
-type function_type_info() :: { FunctionHash :: hash() -type function_type_info() :: { FunctionHash :: hash()
, FunctionName :: function_name() , FunctionName :: function_name()
, ArgType :: aeso_sophia:heap() %% binary typerep , ArgType :: binary() %% binary typerep
, OutType :: aeso_sophia:heap() %% binary typerep , OutType :: binary() %% binary typerep
}. }.
-type type_info() :: [function_type_info()]. -type type_info() :: [function_type_info()].
@ -84,8 +84,8 @@ check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
{expected, ExpectArgs, '=>', ExpectRet}}} {expected, ExpectArgs, '=>', ExpectRet}}}
end. end.
-spec check_calldata(aeso_sophia:heap(), type_info()) -> -spec check_calldata(binary(), type_info()) ->
{'ok', typerep()} | {'error', atom()}. {'ok', typerep(), typerep()} | {'error', atom()}.
check_calldata(CallData, TypeInfo) -> check_calldata(CallData, TypeInfo) ->
%% The first element of the CallData should be the function name %% The first element of the CallData should be the function name
case get_function_hash_from_calldata(CallData) of case get_function_hash_from_calldata(CallData) of
@ -153,7 +153,7 @@ arg_typerep_from_function(Function, TypeInfo) ->
end. end.
-spec typereps_from_type_hash(hash(), type_info()) -> -spec typereps_from_type_hash(hash(), type_info()) ->
{'ok', typerep()} | {'error', 'bad_type_data' | 'unknown_function'}. {'ok', typerep(), typerep()} | {'error', 'bad_type_data' | 'unknown_function'}.
typereps_from_type_hash(TypeHash, TypeInfo) -> typereps_from_type_hash(TypeHash, TypeInfo) ->
case lists:keyfind(TypeHash, 1, TypeInfo) of case lists:keyfind(TypeHash, 1, TypeInfo) of
{TypeHash,_Function, ArgTypeBin, OutTypeBin} -> {TypeHash,_Function, ArgTypeBin, OutTypeBin} ->

View File

@ -17,11 +17,12 @@ line({symbol, Line, _}) -> Line.
symbol_name({symbol, _, Name}) -> Name. symbol_name({symbol, _, Name}) -> Name.
pp(Ast) -> pp(Ast) ->
%% TODO: Actually do *Pretty* printing. %% io:format("Tree:\n~p\n",[Ast]),
io:format("~p~n", [Ast]). String = prettypr:format(aeso_pretty:decls(Ast, [])),
io:format("Ast:\n~s\n", [String]).
pp_typed(TypedAst) -> pp_typed(TypedAst) ->
String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
%% io:format("Typed tree:\n~p\n",[TypedAst]), %% io:format("Typed tree:\n~p\n",[TypedAst]),
io:format("Type info:\n~s\n",[String]). String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])),
io:format("Type ast:\n~s\n",[String]).

View File

@ -79,6 +79,7 @@ global_env() ->
Event = {id, Ann, "event"}, Event = {id, Ann, "event"},
State = {id, Ann, "state"}, State = {id, Ann, "state"},
Hash = {id, Ann, "hash"}, Hash = {id, Ann, "hash"},
Bits = {id, Ann, "bits"},
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, []},
@ -159,6 +160,16 @@ global_env() ->
{["String", "sha3"], Fun1(String, Hash)}, {["String", "sha3"], Fun1(String, Hash)},
{["String", "sha256"], Fun1(String, Hash)}, {["String", "sha256"], Fun1(String, Hash)},
{["String", "blake2b"], Fun1(String, Hash)}, {["String", "blake2b"], Fun1(String, Hash)},
%% Bits
{["Bits", "set"], Fun([Bits, Int], Bits)},
{["Bits", "clear"], Fun([Bits, Int], Bits)},
{["Bits", "test"], Fun([Bits, Int], Bool)},
{["Bits", "sum"], Fun1(Bits, Int)},
{["Bits", "intersection"], Fun([Bits, Bits], Bits)},
{["Bits", "union"], Fun([Bits, Bits], Bits)},
{["Bits", "difference"], Fun([Bits, Bits], Bits)},
{["Bits", "none"], Bits},
{["Bits", "all"], Bits},
%% Conversion %% Conversion
{["Int", "to_str"], Fun1(Int, String)}, {["Int", "to_str"], Fun1(Int, String)},
{["Address", "to_str"], Fun1(Address, String)} {["Address", "to_str"], Fun1(Address, String)}
@ -691,8 +702,7 @@ infer_infix({BoolOp, As})
{fun_t, As, [], [Bool,Bool], Bool}; {fun_t, As, [], [Bool,Bool], Bool};
infer_infix({IntOp, As}) infer_infix({IntOp, As})
when IntOp == '+'; IntOp == '-'; IntOp == '*'; IntOp == '/'; when IntOp == '+'; IntOp == '-'; IntOp == '*'; IntOp == '/';
IntOp == '^'; IntOp == 'mod'; IntOp == 'bsl'; IntOp == 'bsr'; IntOp == '^'; IntOp == 'mod' ->
IntOp == 'band'; IntOp == 'bor'; IntOp == 'bxor' ->
Int = {id, As, "int"}, Int = {id, As, "int"},
{fun_t, As, [], [Int, Int], Int}; {fun_t, As, [], [Int, Int], Int};
infer_infix({RelOp, As}) infer_infix({RelOp, As})
@ -714,8 +724,7 @@ infer_infix({'++', As}) ->
infer_prefix({'!',As}) -> infer_prefix({'!',As}) ->
Bool = {id, As, "bool"}, Bool = {id, As, "bool"},
{fun_t, As, [], [Bool], Bool}; {fun_t, As, [], [Bool], Bool};
infer_prefix({IntOp,As}) infer_prefix({IntOp,As}) when IntOp =:= '-' ->
when IntOp =:= '-'; IntOp =:= 'bnot' ->
Int = {id, As, "int"}, Int = {id, As, "int"},
{fun_t, As, [], [Int], Int}. {fun_t, As, [], [Int], Int}.

View File

@ -348,6 +348,33 @@ ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) ->
ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) ->
#unop{ op = 'sha3', rand = ast_body(String, Icode) }; #unop{ op = 'sha3', rand = ast_body(String, Icode) };
%% -- Bits
ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode)
when Fun == "test"; Fun == "set"; Fun == "clear";
Fun == "union"; Fun == "intersection"; Fun == "difference" ->
C = fun(N) when is_integer(N) -> #integer{ value = N };
(X) -> X end,
Bin = fun(O) -> fun(A, B) -> #binop{ op = O, left = C(A), right = C(B) } end end,
And = Bin('band'),
Or = Bin('bor'),
Bsl = fun(A, B) -> (Bin('bsl'))(B, A) end, %% flipped arguments
Bsr = fun(A, B) -> (Bin('bsr'))(B, A) end,
Neg = fun(A) -> #unop{ op = 'bnot', rand = C(A) } end,
case [Fun | [ ast_body(Arg, Icode) || Arg <- Args ]] of
["test", Bits, Ix] -> And(Bsr(Bits, Ix), 1);
["set", Bits, Ix] -> Or(Bits, Bsl(1, Ix));
["clear", Bits, Ix] -> And(Bits, Neg(Bsl(1, Ix)));
["union", A, B] -> Or(A, B);
["intersection", A, B] -> And(A, B);
["difference", A, B] -> And(A, Neg(And(A, B)))
end;
ast_body({qid, _, ["Bits", "none"]}, _Icode) ->
#integer{ value = 0 };
ast_body({qid, _, ["Bits", "all"]}, _Icode) ->
#integer{ value = 1 bsl 256 - 1 };
ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) ->
builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]);
%% -- Conversion %% -- Conversion
ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) -> ast_body(?qid_app(["Int", "to_str"], [Int], _, _), Icode) ->
builtin_call(int_to_str, [ast_body(Int, Icode)]); builtin_call(int_to_str, [ast_body(Int, Icode)]);
@ -526,9 +553,6 @@ ast_binop(Op, Ann, {typed, _, A, Type}, B, Icode)
ast_binop('++', _, A, B, Icode) -> ast_binop('++', _, A, B, Icode) ->
#funcall{ function = #var_ref{ name = {builtin, list_concat} }, #funcall{ function = #var_ref{ name = {builtin, list_concat} },
args = [ast_body(A, Icode), ast_body(B, Icode)] }; args = [ast_body(A, Icode), ast_body(B, Icode)] };
%% Bit shift operations takes their arguments backwards!?
ast_binop(Op, _, A, B, Icode) when Op =:= 'bsl'; Op =:= 'bsr' ->
#binop{op = Op, right = ast_body(A, Icode), left = ast_body(B, Icode)};
ast_binop(Op, _, A, B, Icode) -> ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.

View File

@ -38,7 +38,7 @@ builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put]; builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_from_list) -> [map_put]; builtin_deps1(map_from_list) -> [map_put];
builtin_deps1(str_equal) -> [str_equal_p]; builtin_deps1(str_equal) -> [str_equal_p];
builtin_deps1(string_concat) -> [string_concat_inner1, string_concat_inner2]; builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
builtin_deps1(int_to_str) -> [{baseX_int, 10}]; builtin_deps1(int_to_str) -> [{baseX_int, 10}];
builtin_deps1(addr_to_str) -> [{baseX_int, 58}]; builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
@ -144,9 +144,11 @@ builtin_function(BF) ->
string_length -> bfun(BF, builtin_string_length()); string_length -> bfun(BF, builtin_string_length());
string_concat -> bfun(BF, builtin_string_concat()); string_concat -> bfun(BF, builtin_string_concat());
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1()); string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
string_concat_inner2 -> bfun(BF, builtin_string_concat_inner2()); string_copy -> bfun(BF, builtin_string_copy());
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
str_equal_p -> bfun(BF, builtin_str_equal_p()); str_equal_p -> bfun(BF, builtin_str_equal_p());
str_equal -> bfun(BF, builtin_str_equal()); str_equal -> bfun(BF, builtin_str_equal());
popcount -> bfun(BF, builtin_popcount());
int_to_str -> bfun(BF, builtin_int_to_str()); int_to_str -> bfun(BF, builtin_int_to_str());
addr_to_str -> bfun(BF, builtin_addr_to_str()); addr_to_str -> bfun(BF, builtin_addr_to_str());
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X)); {baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
@ -322,6 +324,8 @@ builtin_string_concat() ->
{[{"s1", string}, {"s2", string}], {[{"s1", string}, {"s2", string}],
?DEREF(n1, s1, ?DEREF(n1, s1,
?DEREF(n2, s2, ?DEREF(n2, s2,
{ifte, ?EQ(n1, 0),
?V(s2), %% First string is empty return second string
{ifte, ?EQ(n2, 0), {ifte, ?EQ(n2, 0),
?V(s1), %% Second string is empty return first string ?V(s1), %% Second string is empty return first string
?LET(ret, {inline_asm, [?A(?MSIZE)]}, ?LET(ret, {inline_asm, [?A(?MSIZE)]},
@ -330,6 +334,7 @@ builtin_string_concat() ->
{inline_asm, [?A(?POP)]}, %% Discard fun ret val {inline_asm, [?A(?POP)]}, %% Discard fun ret val
?V(ret) %% Put the actual return value ?V(ret) %% Put the actual return value
]})} ]})}
}
)), )),
word}. word}.
@ -337,33 +342,33 @@ builtin_string_concat_inner1() ->
%% Copy all whole words from the first string, and set up for word fusion %% Copy all whole words from the first string, and set up for word fusion
%% Special case when the length of the first string is divisible by 32. %% Special case when the length of the first string is divisible by 32.
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}], {[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
?DEREF(w1, p1, ?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
{ifte, ?GT(n1, 32), ?LET(nx, ?MOD(n1, 32),
{seq, [?V(w1), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, {ifte, ?EQ(nx, 0),
?call(string_concat_inner1, [?SUB(n1, 32), ?NXT(p1), ?V(n2), ?V(p2)])]}, ?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
{ifte, ?EQ(n1, 0), {seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
?call(string_concat_inner2, [?I(32), ?I(0), ?V(n2), ?V(p2)]), ?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
?call(string_concat_inner2, [?SUB(32, n1), ?V(w1), ?V(n2), ?V(p2)])} })),
word}.
builtin_string_copy() ->
{[{"n", word}, {"p", pointer}],
?DEREF(w, p,
{ifte, ?GT(n, 31),
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
?V(w)
}), }),
word}. word}.
builtin_string_concat_inner2() -> builtin_string_shift_copy() ->
%% Current "work in progess" word 'x', has 'o' bytes that are "free" - fill them from {[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
%% words of the second string. ?DEREF(w, p,
{[{"o", word}, {"x", word}, {"n2", word}, {"p2", pointer}], {seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
{ifte, ?LT(n2, 1), {ifte, ?GT(n, ?SUB(32, off)),
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}, %% Use MSIZE as dummy return value ?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
?DEREF(w2, p2, {inline_asm, [?A(?MSIZE)]}}]
{ifte, ?GT(n2, o), }),
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
?call(string_concat_inner2,
[?V(o), ?BSL(w2, o), ?SUB(n2, 32), ?NXT(p2)])
]},
{seq, [?ADD(x, ?BSR(w2, ?SUB(32, o))),
{inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]} %% Use MSIZE as dummy return value
})
},
word}. word}.
builtin_str_equal_p() -> builtin_str_equal_p() ->
@ -394,6 +399,17 @@ builtin_str_equal() ->
)), )),
word}. word}.
%% Count the number of 1s in a bit field.
builtin_popcount() ->
%% function popcount(bits, acc) =
%% if (bits == 0) acc
%% else popcount(bits bsr 1, acc + bits band 1)
{[{"bits", word}, {"acc", word}],
{ifte, ?EQ(bits, 0),
?V(acc),
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
}, word}.
builtin_int_to_str() -> builtin_int_to_str() ->
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}. {[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.

View File

@ -21,8 +21,8 @@
-include("aeso_icode.hrl"). -include("aeso_icode.hrl").
-type option() :: pp_sophia_code | pp_ast | pp_icode | pp_assembler | -type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast |
pp_bytecode. pp_icode| pp_assembler | pp_bytecode.
-type options() :: [option()]. -type options() :: [option()].
-export_type([ option/0 -export_type([ option/0
@ -38,17 +38,24 @@
version() -> version() ->
?COMPILER_VERSION. ?COMPILER_VERSION.
-spec file(string()) -> map(). -spec file(string()) -> {ok, map()} | {error, binary()}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
-spec file(string(), options()) -> map(). -spec file(string(), options()) -> {ok, map()} | {error, binary()}.
file(Filename, Options) -> file(File, Options) ->
C = read_contract(Filename), case read_contract(File) of
from_string(C, Options). {ok, Bin} -> from_string(Bin, Options);
{error, Error} ->
ErrorString = [File,": ",file:format_error(Error)],
{error, join_errors("File errors", [ErrorString], fun(E) -> E end)}
end.
-spec from_string(string(), options()) -> map(). -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}.
from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) -> from_string(ContractString, Options) ->
try
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
ok = pp_sophia_code(Ast, Options), ok = pp_sophia_code(Ast, Options),
ok = pp_ast(Ast, Options), ok = pp_ast(Ast, Options),
@ -63,9 +70,26 @@ from_string(ContractString, Options) ->
ByteCodeList = to_bytecode(Assembler, Options), ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
ok = pp_bytecode(ByteCode, Options), ok = pp_bytecode(ByteCode, Options),
#{byte_code => ByteCode, type_info => TypeInfo, {ok, #{byte_code => ByteCode,
compiler_version => version(),
contract_source => ContractString, contract_source => ContractString,
compiler_version => version()}. type_info => TypeInfo
}}
catch
%% The compiler errors.
error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun(E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun(E) -> E end)};
error:{code_errors, Errors} ->
{error, join_errors("Code errors", Errors,
fun (E) -> io_lib:format("~p", [E]) end)}
%% General programming errors in the compiler just signal error.
end.
join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")).
-define(CALL_NAME, "__call"). -define(CALL_NAME, "__call").
@ -76,6 +100,7 @@ from_string(ContractString, Options) ->
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()} -spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
when Type :: term(). when Type :: term().
check_call(ContractString, Options) -> check_call(ContractString, Options) ->
try
Ast = parse(ContractString, Options), Ast = parse(ContractString, Options),
ok = pp_sophia_code(Ast, Options), ok = pp_sophia_code(Ast, Options),
ok = pp_ast(Ast, Options), ok = pp_ast(Ast, Options),
@ -91,15 +116,24 @@ check_call(ContractString, Options) ->
ok = pp_icode(Icode, Options), ok = pp_icode(Icode, Options),
#{ functions := Funs } = Icode, #{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs), ArgIcode = get_arg_icode(Funs),
try [ icode_to_term(T, Arg) || {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ] of ArgTerms = [ icode_to_term(T, Arg) ||
ArgTerms -> {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ],
{ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms} {ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms}
catch throw:Err -> catch
{error, Err} error:{parse_errors, Errors} ->
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
error:{type_errors, Errors} ->
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
error:{badmatch, {error, missing_call_function}} ->
{error, join_errors("Type errors", ["missing __call function"],
fun (E) -> E end)};
throw:Error -> %Don't ask
{error, join_errors("Code errors", [Error],
fun (E) -> io_lib:format("~p", [E]) end)}
end. end.
-spec create_calldata(map(), string(), string()) -> -spec create_calldata(map(), string(), string()) ->
{ok, aeso_sophia:heap(), aeso_sophia:type(), aeso_sophia:type()} {ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
| {error, argument_syntax_error}. | {error, argument_syntax_error}.
create_calldata(Contract, "", CallCode) when is_map(Contract) -> create_calldata(Contract, "", CallCode) when is_map(Contract) ->
case check_call(CallCode, []) of case check_call(CallCode, []) of
@ -252,8 +286,4 @@ parse_error({Line,Pos}, ErrorString) ->
error({parse_errors, [Error]}). error({parse_errors, [Error]}).
read_contract(Name) -> read_contract(Name) ->
{ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))), file:read_file(Name).
binary_to_list(Bin).
contract_path() ->
"apps/aesophia/test/contracts".

View File

@ -59,6 +59,7 @@ builtin_types() ->
Word = fun([]) -> word end, Word = fun([]) -> word end,
#{ "bool" => Word #{ "bool" => Word
, "int" => Word , "int" => Word
, "bits" => Word
, "string" => fun([]) -> string end , "string" => fun([]) -> string end
, "address" => Word , "address" => Word
, "hash" => Word , "hash" => Word

View File

@ -176,11 +176,11 @@ expr200() -> infixr(expr300(), binop('||')).
expr300() -> infixr(expr400(), binop('&&')). expr300() -> infixr(expr400(), binop('&&')).
expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])).
expr500() -> infixr(expr600(), binop(['::', '++'])). expr500() -> infixr(expr600(), binop(['::', '++'])).
expr600() -> infixl(expr650(), binop(['+', '-', 'bor', 'bxor', 'bsr', 'bsl'])). expr600() -> infixl(expr650(), binop(['+', '-'])).
expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)).
expr700() -> infixl(expr750(), binop(['*', '/', mod, 'band'])). expr700() -> infixl(expr750(), binop(['*', '/', mod])).
expr750() -> infixl(expr800(), binop(['^'])). expr750() -> infixl(expr800(), binop(['^'])).
expr800() -> ?RULE(many(choice(token('!'), token('bnot'))), expr900(), prefixes(_1, _2)). expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)).
expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)).
exprAtom() -> exprAtom() ->

View File

@ -170,7 +170,7 @@ expr(E) -> expr_p(0, E).
%% -- Not exported ----------------------------------------------------------- %% -- Not exported -----------------------------------------------------------
-spec name(aeso_syntax:id() | aeso_syntax:con() | aeso_syntax:tvar()) -> doc(). -spec name(aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon() | aeso_syntax:tvar()) -> doc().
name({id, _, Name}) -> text(Name); name({id, _, Name}) -> text(Name);
name({con, _, Name}) -> text(Name); name({con, _, Name}) -> text(Name);
name({qid, _, Names}) -> text(string:join(Names, ".")); name({qid, _, Names}) -> text(string:join(Names, "."));
@ -359,20 +359,14 @@ bin_prec('++') -> {500, 600, 500};
bin_prec('::') -> {500, 600, 500}; bin_prec('::') -> {500, 600, 500};
bin_prec('+') -> {600, 600, 650}; bin_prec('+') -> {600, 600, 650};
bin_prec('-') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650};
bin_prec('bor') -> {600, 600, 650};
bin_prec('bxor') -> {600, 600, 650};
bin_prec('bsl') -> {600, 600, 650};
bin_prec('bsr') -> {600, 600, 650};
bin_prec('*') -> {700, 700, 750}; bin_prec('*') -> {700, 700, 750};
bin_prec('/') -> {700, 700, 750}; bin_prec('/') -> {700, 700, 750};
bin_prec(mod) -> {700, 700, 750}; bin_prec(mod) -> {700, 700, 750};
bin_prec('band') -> {700, 700, 750};
bin_prec('^') -> {750, 750, 800}. bin_prec('^') -> {750, 750, 800}.
-spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}.
un_prec('-') -> {650, 650}; un_prec('-') -> {650, 650};
un_prec('!') -> {800, 800}; un_prec('!') -> {800, 800}.
un_prec('bnot') -> {800, 800}.
equals(Ann, A, B) -> equals(Ann, A, B) ->
{app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}.

View File

@ -37,8 +37,7 @@ lexer() ->
, {"[^/*]+|[/*]", skip()} ], , {"[^/*]+|[/*]", skip()} ],
Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function", Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function",
"stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal"],
"band", "bor", "bxor", "bsl", "bsr", "bnot"],
KW = string:join(Keywords, "|"), KW = string:join(Keywords, "|"),
Rules = Rules =

View File

@ -75,10 +75,10 @@
-type op() :: bin_op() | un_op(). -type op() :: bin_op() | un_op().
-type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | 'band' | 'bor' | 'bsl' | 'bsr' | 'bxor' -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^'
| '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!='
| '||' | '&&' | '..'. | '||' | '&&' | '..'.
-type un_op() :: '-' | '!' | 'bnot'. -type un_op() :: '-' | '!'.
-type expr() -type expr()
:: {lam, ann(), [arg()], expr()} :: {lam, ann(), [arg()], expr()}

View File

@ -5,8 +5,8 @@
{applications, {applications,
[kernel, [kernel,
stdlib, stdlib,
enacl,
syntax_tools, syntax_tools,
getopt,
aebytecode aebytecode
]}, ]},
{env,[]}, {env,[]},

71
src/aesophia.erl Normal file
View File

@ -0,0 +1,71 @@
-module(aesophia).
-export([main/1]).
-define(OPT_SPEC,
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {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 proplists:get_value(help, Opts, false) of
false ->
compile(Opts);
true ->
usage()
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]).

View File

@ -28,13 +28,15 @@ simple_compile_test_() ->
end} || ContractName <- compilable_contracts() ] ++ end} || ContractName <- compilable_contracts() ] ++
[ {"Testing error messages of " ++ ContractName, [ {"Testing error messages of " ++ ContractName,
fun() -> fun() ->
{type_errors, Errors} = compile(ContractName), <<"Type errors\n",ErrorString/binary>> = compile(ContractName),
check_errors(lists:sort(ExpectedErrors), lists:sort(Errors)) check_errors(lists:sort(ExpectedErrors), ErrorString)
end} || end} ||
{ContractName, ExpectedErrors} <- failing_contracts() ] {ContractName, ExpectedErrors} <- failing_contracts() ]
}. }.
check_errors(Expect, Actual) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well.
Actual = binary:split(<<ErrorString/binary,$\n>>, <<"\n\n">>, [global,trim]),
case {Expect -- Actual, Actual -- Expect} of case {Expect -- Actual, Actual -- Expect} of
{[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra});
{Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing});
@ -42,10 +44,10 @@ check_errors(Expect, Actual) ->
end. end.
compile(Name) -> compile(Name) ->
try String = aeso_test_utils:read_contract(Name),
aeso_compiler:from_string(aeso_test_utils:read_contract(Name), []) case aeso_compiler:from_string(String, []) of
catch _:{type_errors, _} = E -> {ok,Map} -> Map;
E {error,ErrorString} -> ErrorString
end. end.
%% compilable_contracts() -> [ContractName]. %% compilable_contracts() -> [ContractName].
@ -75,82 +77,94 @@ compilable_contracts() ->
failing_contracts() -> failing_contracts() ->
[ {"name_clash", [ {"name_clash",
["Duplicate definitions of abort at\n - (builtin location)\n - line 14, column 3\n", [<<"Duplicate definitions of abort at\n"
"Duplicate definitions of double_def at\n - line 10, column 3\n - line 11, column 3\n", " - (builtin location)\n"
"Duplicate definitions of double_proto at\n - line 4, column 3\n - line 5, column 3\n", " - line 14, column 3">>,
"Duplicate definitions of proto_and_def at\n - line 7, column 3\n - line 8, column 3\n", <<"Duplicate definitions of double_def at\n"
"Duplicate definitions of put at\n - (builtin location)\n - line 15, column 3\n", " - line 10, column 3\n"
"Duplicate definitions of state at\n - (builtin location)\n - line 16, column 3\n"]} " - line 11, column 3">>,
<<"Duplicate definitions of double_proto at\n"
" - line 4, column 3\n"
" - line 5, column 3">>,
<<"Duplicate definitions of proto_and_def at\n"
" - line 7, column 3\n"
" - line 8, column 3">>,
<<"Duplicate definitions of put at\n"
" - (builtin location)\n"
" - line 15, column 3">>,
<<"Duplicate definitions of state at\n"
" - (builtin location)\n"
" - line 16, column 3">>]}
, {"type_errors", , {"type_errors",
["Unbound variable zz at line 17, column 21\n", [<<"Unbound variable zz at line 17, column 21">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and list(int)\n" " and list(int)\n"
"when checking the application at line 26, column 9 of\n" "when checking the application at line 26, column 9 of\n"
" (::) : (int, list(int)) => list(int)\n" " (::) : (int, list(int)) => list(int)\n"
"to arguments\n" "to arguments\n"
" x : int\n" " x : int\n"
" x : int\n", " x : int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the assignment of the field\n" "when checking the assignment of the field\n"
" x : map(string, string) (at line 9, column 46)\n" " x : map(string, string) (at line 9, column 46)\n"
"to the old value __x and the new value\n" "to the old value __x and the new value\n"
" __x {[\"foo\"] @ x = x + 1} : map(string, int)\n", " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when checking the type of the expression at line 34, column 45\n" "when checking the type of the expression at line 34, column 45\n"
" 1 : int\n" " 1 : int\n"
"against the expected type\n" "against the expected type\n"
" string\n", " string">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 34, column 50\n" "when checking the type of the expression at line 34, column 50\n"
" \"bla\" : string\n" " \"bla\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 32, column 18\n" "when checking the type of the expression at line 32, column 18\n"
" \"x\" : string\n" " \"x\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify string\n" <<"Cannot unify string\n"
" and int\n" " and int\n"
"when checking the type of the expression at line 11, column 56\n" "when checking the type of the expression at line 11, column 56\n"
" \"foo\" : string\n" " \"foo\" : string\n"
"against the expected type\n" "against the expected type\n"
" int\n", " int">>,
"Cannot unify int\n" <<"Cannot unify int\n"
" and string\n" " and string\n"
"when comparing the types of the if-branches\n" "when comparing the types of the if-branches\n"
" - w : int (at line 38, column 13)\n" " - w : int (at line 38, column 13)\n"
" - z : string (at line 39, column 10)\n", " - z : string (at line 39, column 10)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from the projection of the field y (at line 22, column 38)\n", "arising from the projection of the field y (at line 22, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 21, column 42)\n", "arising from an assignment of the field y (at line 21, column 42)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 20, column 38)\n", "arising from an assignment of the field y (at line 20, column 38)">>,
"Not a record type: string\n" <<"Not a record type: string\n"
"arising from an assignment of the field y (at line 19, column 35)\n", "arising from an assignment of the field y (at line 19, column 35)">>,
"Ambiguous record type with field y (at line 13, column 25) could be one of\n" <<"Ambiguous record type with field y (at line 13, column 25) could be one of\n"
" - r (at line 4, column 10)\n" " - r (at line 4, column 10)\n"
" - r' (at line 5, column 10)\n", " - r' (at line 5, column 10)">>,
"Record type r2 does not have field y (at line 15, column 22)\n", <<"Record type r2 does not have field y (at line 15, column 22)">>,
"The field z is missing when constructing an element of type r2 (at line 15, column 24)\n", <<"The field z is missing when constructing an element of type r2 (at line 15, column 24)">>,
"Repeated name x in pattern\n" <<"Repeated name x in pattern\n"
" x :: x (at line 26, column 7)\n", " x :: x (at line 26, column 7)">>,
"No record type with fields y, z (at line 14, column 22)\n"]} <<"No record type with fields y, z (at line 14, column 22)">>]}
, {"init_type_error", , {"init_type_error",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and map(int, int)\n" " and map(int, int)\n"
"when checking that 'init' returns a value of type 'state' at line 7, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]}
, {"missing_state_type", , {"missing_state_type",
["Cannot unify string\n" [<<"Cannot unify string\n"
" and ()\n" " and ()\n"
"when checking that 'init' returns a value of type 'state' at line 5, column 3\n"]} "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]}
, {"missing_fields_in_record_expression", , {"missing_fields_in_record_expression",
["The field x is missing when constructing an element of type r('a) (at line 7, column 40)\n", [<<"The field x is missing when constructing an element of type r('a) (at line 7, column 40)">>,
"The field y is missing when constructing an element of type r(int) (at line 8, column 40)\n", <<"The field y is missing when constructing an element of type r(int) (at line 8, column 40)">>,
"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)\n"]} <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]}
]. ].

View File

@ -36,8 +36,6 @@ contract AllSyntax =
(x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)})
(x, y :: _) => () (x, y :: _) => ()
function bitOperations(x, y) = bnot (0xff00 band x bsl 4 bxor 0xa5a5a5 bsr 4 bor y)
function mutual() = function mutual() =
let rec recFun(x : int) = mutFun(x) let rec recFun(x : int) = mutFun(x)
and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1) and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1)

View File

@ -5,7 +5,7 @@
contract MultiSig = contract MultiSig =
record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint } record pending_state = { yetNeeded : int, ownersDone : bits, index : int }
datatype event = datatype event =
Confirmation (address, hash) // of { .owner : Address, .operation : Hash } Confirmation (address, hash) // of { .owner : Address, .operation : Hash }
@ -13,18 +13,18 @@ contract MultiSig =
| OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address } | OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address }
| OwnerAdded (address) // of { .newOwner : Address } | OwnerAdded (address) // of { .newOwner : Address }
| OwnerRemoved (address) // of { .removedOwner : Address } | OwnerRemoved (address) // of { .removedOwner : Address }
| ReqChanged (uint) // of { .newReq : uint } | ReqChanged (int) // of { .newReq : int }
let maxOwners : uint = 250 let maxOwners : int = 250
record state = { nRequired : uint record state = { nRequired : int
, nOwners : uint , nOwners : int
, owners : map(uint, address) , owners : map(int, address)
, ownerIndex : map(address, uint) , ownerIndex : map(address, int)
, pending : map(hash, pending_state) , pending : map(hash, pending_state)
, pendingIndex : list(address) } , pendingIndex : list(address) }
function init (owners : list(address), nRequired : uint) : state = function init (owners : list(address), nRequired : int) : state =
let n = length(owners) + 1 let n = length(owners) + 1
{ nRequired = nRequired, { nRequired = nRequired,
nOwners = n, nOwners = n,
@ -39,10 +39,9 @@ contract MultiSig =
function revoke(operation : hash) = function revoke(operation : hash) =
let ownerIx = lookup(state.ownerIndex, caller()) let ownerIx = lookup(state.ownerIndex, caller())
let pending = lookup(state.pendingIndex, operation) let pending = lookup(state.pendingIndex, operation)
let ownerIxBit = 1 bsl (ownerIx - 1) let _ = require(Bits.test(pending.ownersDone, ownerIx))
let _ = require(pending.ownersDone band ownerIxBit > 0)
let pending' = pending { yetNeeded = pending.yetNeeded + 1 let pending' = pending { yetNeeded = pending.yetNeeded + 1
, ownersDone = pending.ownersDone - ownerIxBit } , ownersDone = Bits.clear(pending.ownersDone, ownerIx - 1) }
put(state{ pendingIndex.operator = pending' }) put(state{ pendingIndex.operator = pending' })
event(Revoke(caller, operation)) event(Revoke(caller, operation))
@ -91,7 +90,7 @@ contract MultiSig =
, pendingIx = [] }, , pendingIx = [] },
event = [OwnerRemoved(oldOwner)] } event = [OwnerRemoved(oldOwner)] }
function changeRequirement(newReq : uint) = function changeRequirement(newReq : int) =
let _ = require(newReq =< state.nOwners) let _ = require(newReq =< state.nOwners)
switch(check_pending(callhash())) switch(check_pending(callhash()))
CheckFail(state') => { state = state' } CheckFail(state') => { state = state' }
@ -102,7 +101,7 @@ contract MultiSig =
event = [ReqChanged(newReq)] } event = [ReqChanged(newReq)] }
function getOwner(ownerIx0 : uint) = function getOwner(ownerIx0 : int) =
lookup(state.owners, ownerIx0 + 1) lookup(state.owners, ownerIx0 + 1)
function isOwner(owner : address) = function isOwner(owner : address) =
@ -116,8 +115,7 @@ contract MultiSig =
Some(pending) => Some(pending) =>
let _ = require(isOwner(owner)) let _ = require(isOwner(owner))
let ownerIx = lookup(state.ownerIndex, owner) let ownerIx = lookup(state.ownerIndex, owner)
let ownerIxBit = 1 bsl (ownerIx - 1) Bits.test(pending.ownersDone, ownerIx - 1)
(pending.ownersDone band ownerIxBit) != 0
/* Leave the rest for now... */ /* Leave the rest for now... */

View File

@ -1,5 +1,4 @@
// - + * / mod arithmetic operators // - + * / mod arithmetic operators
// bnot band bor bxor bsl bsr bitwise operators
// ! && || logical operators // ! && || logical operators
// == != < > =< >= comparison operators // == != < > =< >= comparison operators
// :: ++ list operators // :: ++ list operators
@ -13,12 +12,6 @@ contract Operators =
"/" => a / b "/" => a / b
"mod" => a mod b "mod" => a mod b
"^" => a ^ b "^" => a ^ b
"bnot" => bnot a
"band" => a band b
"bor" => a bor b
"bxor" => a bxor b
"bsl" => a bsl b
"bsr" => a bsr b
function bool_op(a : bool, b : bool, op : string) = function bool_op(a : bool, b : bool, op : string) =
switch(op) switch(op)