diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..33debcf --- /dev/null +++ b/.circleci/config.yml @@ -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 diff --git a/.gitignore b/.gitignore index ed03531..48f764e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ _build rebar3.crashdump current_counterexample.eqc .qcci +*.erl~ +*.aes~ + diff --git a/README.md b/README.md index d29c484..2592113 100644 --- a/README.md +++ b/README.md @@ -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 [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. + +## Interface Modules + +The basic modules for interfacing the compiler: + +* [aeso_compiler: the Sophia compiler](./docs/aeso_compiler.md) diff --git a/docs/aeso_compiler.md b/docs/aeso_compiler.md new file mode 100644 index 0000000..8bb0f38 --- /dev/null +++ b/docs/aeso_compiler.md @@ -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. diff --git a/rebar.config b/rebar.config index dd271ad..7416560 100644 --- a/rebar.config +++ b/rebar.config @@ -1,16 +1,32 @@ {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", {ref,"720510a"}}} + , {getopt, "1.0.1"} ]}. + {profiles, [ {eqc, [{erl_opts, [{parse_transform, eqc_cover}]}, {deps, [{eqc_ci, "1.0.0"}]}, {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, [ {warnings, [unknown]}, {plt_apps, all_deps}, diff --git a/rebar.lock b/rebar.lock index 368e4dc..37c2aca 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1,10 @@ +{"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", {ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}}, - 0}]. + 0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}. +[ +{pkg_hash,[ + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} +]. diff --git a/src/aeso_abi.erl b/src/aeso_abi.erl index ff767ea..c9f32ba 100644 --- a/src/aeso_abi.erl +++ b/src/aeso_abi.erl @@ -27,8 +27,8 @@ -type typerep() :: aeso_sophia:type(). -type function_type_info() :: { FunctionHash :: hash() , FunctionName :: function_name() - , ArgType :: aeso_sophia:heap() %% binary typerep - , OutType :: aeso_sophia:heap() %% binary typerep + , ArgType :: binary() %% binary typerep + , OutType :: binary() %% binary typerep }. -type type_info() :: [function_type_info()]. @@ -84,8 +84,8 @@ check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) -> {expected, ExpectArgs, '=>', ExpectRet}}} end. --spec check_calldata(aeso_sophia:heap(), type_info()) -> - {'ok', typerep()} | {'error', atom()}. +-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 @@ -153,7 +153,7 @@ arg_typerep_from_function(Function, TypeInfo) -> end. -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) -> case lists:keyfind(TypeHash, 1, TypeInfo) of {TypeHash,_Function, ArgTypeBin, OutTypeBin} -> diff --git a/src/aeso_ast.erl b/src/aeso_ast.erl index ee25e15..90e6595 100644 --- a/src/aeso_ast.erl +++ b/src/aeso_ast.erl @@ -17,11 +17,12 @@ line({symbol, Line, _}) -> Line. symbol_name({symbol, _, Name}) -> Name. pp(Ast) -> - %% TODO: Actually do *Pretty* printing. - io:format("~p~n", [Ast]). + %% io:format("Tree:\n~p\n",[Ast]), + String = prettypr:format(aeso_pretty:decls(Ast, [])), + io:format("Ast:\n~s\n", [String]). pp_typed(TypedAst) -> + %% io:format("Typed tree:\n~p\n",[TypedAst]), String = prettypr:format(aeso_pretty:decls(TypedAst, [show_generated])), - %%io:format("Typed tree:\n~p\n",[TypedAst]), - io:format("Type info:\n~s\n",[String]). + io:format("Type ast:\n~s\n",[String]). diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index cfeb08e..76e8d62 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -79,6 +79,7 @@ global_env() -> Event = {id, Ann, "event"}, State = {id, Ann, "state"}, Hash = {id, Ann, "hash"}, + Bits = {id, Ann, "bits"}, Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end, Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end, Unit = {tuple_t, Ann, []}, @@ -159,6 +160,16 @@ global_env() -> {["String", "sha3"], Fun1(String, Hash)}, {["String", "sha256"], 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 {["Int", "to_str"], Fun1(Int, String)}, {["Address", "to_str"], Fun1(Address, String)} @@ -691,8 +702,7 @@ infer_infix({BoolOp, As}) {fun_t, As, [], [Bool,Bool], Bool}; infer_infix({IntOp, As}) when IntOp == '+'; IntOp == '-'; IntOp == '*'; IntOp == '/'; - IntOp == '^'; IntOp == 'mod'; IntOp == 'bsl'; IntOp == 'bsr'; - IntOp == 'band'; IntOp == 'bor'; IntOp == 'bxor' -> + IntOp == '^'; IntOp == 'mod' -> Int = {id, As, "int"}, {fun_t, As, [], [Int, Int], Int}; infer_infix({RelOp, As}) @@ -714,8 +724,7 @@ infer_infix({'++', As}) -> infer_prefix({'!',As}) -> Bool = {id, As, "bool"}, {fun_t, As, [], [Bool], Bool}; -infer_prefix({IntOp,As}) - when IntOp =:= '-'; IntOp =:= 'bnot' -> +infer_prefix({IntOp,As}) when IntOp =:= '-' -> Int = {id, As, "int"}, {fun_t, As, [], [Int], Int}. diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 0509847..2e00104 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -348,6 +348,33 @@ ast_body(?qid_app(["String", "concat"], [String1, String2], _, _), Icode) -> ast_body(?qid_app(["String", "sha3"], [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 ast_body(?qid_app(["Int", "to_str"], [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) -> #funcall{ function = #var_ref{ name = {builtin, list_concat} }, 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) -> #binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}. diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index 07e967e..383fcf4 100644 --- a/src/aeso_builtins.erl +++ b/src/aeso_builtins.erl @@ -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_from_list) -> [map_put]; 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(addr_to_str) -> [{baseX_int, 58}]; builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}]; @@ -144,9 +144,11 @@ builtin_function(BF) -> string_length -> bfun(BF, builtin_string_length()); string_concat -> bfun(BF, builtin_string_concat()); 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 -> bfun(BF, builtin_str_equal()); + popcount -> bfun(BF, builtin_popcount()); int_to_str -> bfun(BF, builtin_int_to_str()); addr_to_str -> bfun(BF, builtin_addr_to_str()); {baseX_int, X} -> bfun(BF, builtin_baseX_int(X)); @@ -322,14 +324,17 @@ builtin_string_concat() -> {[{"s1", string}, {"s2", string}], ?DEREF(n1, s1, ?DEREF(n2, s2, - {ifte, ?EQ(n2, 0), - ?V(s1), %% Second string is empty return first string - ?LET(ret, {inline_asm, [?A(?MSIZE)]}, - {seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len - ?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]), - {inline_asm, [?A(?POP)]}, %% Discard fun ret val - ?V(ret) %% Put the actual return value - ]})} + {ifte, ?EQ(n1, 0), + ?V(s2), %% First string is empty return second string + {ifte, ?EQ(n2, 0), + ?V(s1), %% Second string is empty return first string + ?LET(ret, {inline_asm, [?A(?MSIZE)]}, + {seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len + ?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]), + {inline_asm, [?A(?POP)]}, %% Discard fun ret val + ?V(ret) %% Put the actual return value + ]})} + } )), word}. @@ -337,33 +342,33 @@ builtin_string_concat_inner1() -> %% 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. {[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}], - ?DEREF(w1, p1, - {ifte, ?GT(n1, 32), - {seq, [?V(w1), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, - ?call(string_concat_inner1, [?SUB(n1, 32), ?NXT(p1), ?V(n2), ?V(p2)])]}, - {ifte, ?EQ(n1, 0), - ?call(string_concat_inner2, [?I(32), ?I(0), ?V(n2), ?V(p2)]), - ?call(string_concat_inner2, [?SUB(32, n1), ?V(w1), ?V(n2), ?V(p2)])} + ?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]), + ?LET(nx, ?MOD(n1, 32), + {ifte, ?EQ(nx, 0), + ?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]), + {seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}), + ?call(string_shift_copy, [?V(nx), ?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}. -builtin_string_concat_inner2() -> - %% Current "work in progess" word 'x', has 'o' bytes that are "free" - fill them from - %% words of the second string. - {[{"o", word}, {"x", word}, {"n2", word}, {"p2", pointer}], - {ifte, ?LT(n2, 1), - {seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}, %% Use MSIZE as dummy return value - ?DEREF(w2, p2, - {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 - }) - }, +builtin_string_shift_copy() -> + {[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}], + ?DEREF(w, p, + {seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, + {ifte, ?GT(n, ?SUB(32, off)), + ?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]), + {inline_asm, [?A(?MSIZE)]}}] + }), word}. builtin_str_equal_p() -> @@ -394,6 +399,17 @@ builtin_str_equal() -> )), 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() -> {[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}. diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index e132005..e54b8ef 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -21,8 +21,8 @@ -include("aeso_icode.hrl"). --type option() :: pp_sophia_code | pp_ast | pp_icode | pp_assembler | - pp_bytecode. +-type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast | + pp_icode| pp_assembler | pp_bytecode. -type options() :: [option()]. -export_type([ option/0 @@ -38,34 +38,58 @@ version() -> ?COMPILER_VERSION. --spec file(string()) -> map(). +-spec file(string()) -> {ok, map()} | {error, binary()}. file(Filename) -> file(Filename, []). --spec file(string(), options()) -> map(). -file(Filename, Options) -> - C = read_contract(Filename), - from_string(C, Options). +-spec file(string(), options()) -> {ok, map()} | {error, binary()}. +file(File, Options) -> + case read_contract(File) of + {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) -> - Ast = parse(ContractString, Options), - ok = pp_sophia_code(Ast, Options), - ok = pp_ast(Ast, Options), - TypedAst = aeso_ast_infer_types:infer(Ast, Options), - %% pp_types is handled inside aeso_ast_infer_types. - ok = pp_typed_ast(TypedAst, Options), - ICode = to_icode(TypedAst, Options), - TypeInfo = extract_type_info(ICode), - ok = pp_icode(ICode, Options), - Assembler = assemble(ICode, Options), - ok = pp_assembler(Assembler, Options), - ByteCodeList = to_bytecode(Assembler, Options), - ByteCode = << << B:8 >> || B <- ByteCodeList >>, - ok = pp_bytecode(ByteCode, Options), - #{byte_code => ByteCode, type_info => TypeInfo, - contract_source => ContractString, - compiler_version => version()}. + try + Ast = parse(ContractString, Options), + ok = pp_sophia_code(Ast, Options), + ok = pp_ast(Ast, Options), + TypedAst = aeso_ast_infer_types:infer(Ast, Options), + %% pp_types is handled inside aeso_ast_infer_types. + ok = pp_typed_ast(TypedAst, Options), + ICode = to_icode(TypedAst, Options), + TypeInfo = extract_type_info(ICode), + ok = pp_icode(ICode, Options), + Assembler = assemble(ICode, Options), + ok = pp_assembler(Assembler, Options), + ByteCodeList = to_bytecode(Assembler, Options), + ByteCode = << << B:8 >> || B <- ByteCodeList >>, + ok = pp_bytecode(ByteCode, Options), + {ok, #{byte_code => ByteCode, + compiler_version => version(), + contract_source => ContractString, + 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"). @@ -76,30 +100,40 @@ from_string(ContractString, Options) -> -spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()} when Type :: term(). check_call(ContractString, Options) -> - Ast = parse(ContractString, Options), - ok = pp_sophia_code(Ast, Options), - ok = pp_ast(Ast, Options), - TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]), - {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), - ok = pp_typed_ast(TypedAst, Options), - Icode = to_icode(TypedAst, Options), - ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], - RetVMType = case RetType of - {id, _, "_"} -> any; - _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) - end, - ok = pp_icode(Icode, Options), - #{ functions := Funs } = Icode, - ArgIcode = get_arg_icode(Funs), - try [ icode_to_term(T, Arg) || {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ] of - ArgTerms -> - {ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms} - catch throw:Err -> - {error, Err} + try + Ast = parse(ContractString, Options), + ok = pp_sophia_code(Ast, Options), + ok = pp_ast(Ast, Options), + TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]), + {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), + ok = pp_typed_ast(TypedAst, Options), + Icode = to_icode(TypedAst, Options), + ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], + RetVMType = case RetType of + {id, _, "_"} -> any; + _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) + end, + ok = pp_icode(Icode, Options), + #{ functions := Funs } = Icode, + ArgIcode = get_arg_icode(Funs), + ArgTerms = [ icode_to_term(T, Arg) || + {T, Arg} <- lists:zip(ArgVMTypes, ArgIcode) ], + {ok, FunName, {ArgVMTypes, RetVMType}, ArgTerms} + catch + error:{parse_errors, Errors} -> + {error, join_errors("Parse errors", Errors, fun (E) -> E end)}; + error:{type_errors, Errors} -> + {error, join_errors("Type errors", Errors, fun (E) -> E end)}; + error:{badmatch, {error, missing_call_function}} -> + {error, join_errors("Type errors", ["missing __call function"], + fun (E) -> E end)}; + throw:Error -> %Don't ask + {error, join_errors("Code errors", [Error], + fun (E) -> io_lib:format("~p", [E]) end)} end. -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}. create_calldata(Contract, "", CallCode) when is_map(Contract) -> case check_call(CallCode, []) of @@ -113,7 +147,7 @@ create_calldata(Contract, Function, Argument) when is_map(Contract) -> %% Function should be "foo : type", and %% Argument should be "Arg1, Arg2, .., ArgN" (no parens) case string:lexemes(Function, ": ") of - %% If function is a single word fallback to old calldata generation + %% If function is a single word fallback to old calldata generation [FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument); [FunName | _] -> Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space @@ -247,13 +281,9 @@ parse_string(Text) -> parse_error(Pos, ErrorString) end. -parse_error({Line,Pos}, ErrorString) -> - Error = io_lib:format("line ~p, column ~p: ~s", [Line,Pos,ErrorString]), - error({parse_errors,[Error]}). +parse_error({Line, Pos}, ErrorString) -> + Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]), + error({parse_errors, [Error]}). read_contract(Name) -> - {ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))), - binary_to_list(Bin). - -contract_path() -> - "apps/aesophia/test/contracts". + file:read_file(Name). diff --git a/src/aeso_icode.erl b/src/aeso_icode.erl index 33fcc56..8ee3839 100644 --- a/src/aeso_icode.erl +++ b/src/aeso_icode.erl @@ -59,6 +59,7 @@ builtin_types() -> Word = fun([]) -> word end, #{ "bool" => Word , "int" => Word + , "bits" => Word , "string" => fun([]) -> string end , "address" => Word , "hash" => Word diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index f84295e..57c61cf 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -176,11 +176,11 @@ expr200() -> infixr(expr300(), binop('||')). expr300() -> infixr(expr400(), binop('&&')). expr400() -> infix(expr500(), 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)). -expr700() -> infixl(expr750(), binop(['*', '/', mod, 'band'])). +expr700() -> infixl(expr750(), binop(['*', '/', mod])). 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)). exprAtom() -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 3462a2e..ac073c1 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -170,7 +170,7 @@ expr(E) -> expr_p(0, E). %% -- 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({con, _, Name}) -> text(Name); name({qid, _, Names}) -> text(string:join(Names, ".")); @@ -359,20 +359,14 @@ bin_prec('++') -> {500, 600, 500}; bin_prec('::') -> {500, 600, 500}; 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(mod) -> {700, 700, 750}; -bin_prec('band') -> {700, 700, 750}; bin_prec('^') -> {750, 750, 800}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. un_prec('-') -> {650, 650}; -un_prec('!') -> {800, 800}; -un_prec('bnot') -> {800, 800}. +un_prec('!') -> {800, 800}. equals(Ann, A, B) -> {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 5cee063..17f4153 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -37,8 +37,7 @@ lexer() -> , {"[^/*]+|[/*]", skip()} ], Keywords = ["contract", "import", "let", "rec", "switch", "type", "record", "datatype", "if", "elif", "else", "function", - "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal", - "band", "bor", "bxor", "bsl", "bsr", "bnot"], + "stateful", "true", "false", "and", "mod", "public", "private", "indexed", "internal"], KW = string:join(Keywords, "|"), Rules = diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 5767e82..f8dc364 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -75,10 +75,10 @@ -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() :: {lam, ann(), [arg()], expr()} diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 46c5a67..6a47608 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -5,8 +5,8 @@ {applications, [kernel, stdlib, - enacl, syntax_tools, + getopt, aebytecode ]}, {env,[]}, diff --git a/src/aesophia.erl b/src/aesophia.erl new file mode 100644 index 0000000..c7c57d9 --- /dev/null +++ b/src/aesophia.erl @@ -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]). diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 3c462ab..fced305 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -28,13 +28,15 @@ simple_compile_test_() -> end} || ContractName <- compilable_contracts() ] ++ [ {"Testing error messages of " ++ ContractName, fun() -> - {type_errors, Errors} = compile(ContractName), - check_errors(lists:sort(ExpectedErrors), lists:sort(Errors)) + <<"Type errors\n",ErrorString/binary>> = compile(ContractName), + check_errors(lists:sort(ExpectedErrors), ErrorString) end} || {ContractName, ExpectedErrors} <- failing_contracts() ] }. -check_errors(Expect, Actual) -> +check_errors(Expect, ErrorString) -> + %% This removes the final single \n as well. + Actual = binary:split(<>, <<"\n\n">>, [global,trim]), case {Expect -- Actual, Actual -- Expect} of {[], Extra} -> ?assertMatch({unexpected, []}, {unexpected, Extra}); {Missing, []} -> ?assertMatch({missing, []}, {missing, Missing}); @@ -42,10 +44,10 @@ check_errors(Expect, Actual) -> end. compile(Name) -> - try - aeso_compiler:from_string(aeso_test_utils:read_contract(Name), []) - catch _:{type_errors, _} = E -> - E + String = aeso_test_utils:read_contract(Name), + case aeso_compiler:from_string(String, []) of + {ok,Map} -> Map; + {error,ErrorString} -> ErrorString end. %% compilable_contracts() -> [ContractName]. @@ -75,82 +77,94 @@ compilable_contracts() -> failing_contracts() -> [ {"name_clash", - ["Duplicate definitions of abort at\n - (builtin location)\n - line 14, column 3\n", - "Duplicate definitions of double_def at\n - line 10, column 3\n - line 11, column 3\n", - "Duplicate definitions of double_proto at\n - line 4, column 3\n - line 5, column 3\n", - "Duplicate definitions of proto_and_def at\n - line 7, column 3\n - line 8, column 3\n", - "Duplicate definitions of put at\n - (builtin location)\n - line 15, column 3\n", - "Duplicate definitions of state at\n - (builtin location)\n - line 16, column 3\n"]} + [<<"Duplicate definitions of abort at\n" + " - (builtin location)\n" + " - line 14, column 3">>, + <<"Duplicate definitions of double_def at\n" + " - line 10, 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", - ["Unbound variable zz at line 17, column 21\n", - "Cannot unify int\n" - " and list(int)\n" - "when checking the application at line 26, column 9 of\n" - " (::) : (int, list(int)) => list(int)\n" - "to arguments\n" - " x : int\n" - " x : int\n", - "Cannot unify string\n" - " and int\n" - "when checking the assignment of the field\n" - " x : map(string, string) (at line 9, column 46)\n" - "to the old value __x and the new value\n" - " __x {[\"foo\"] @ x = x + 1} : map(string, int)\n", - "Cannot unify int\n" - " and string\n" - "when checking the type of the expression at line 34, column 45\n" - " 1 : int\n" - "against the expected type\n" - " string\n", - "Cannot unify string\n" - " and int\n" - "when checking the type of the expression at line 34, column 50\n" - " \"bla\" : string\n" - "against the expected type\n" - " int\n", - "Cannot unify string\n" - " and int\n" - "when checking the type of the expression at line 32, column 18\n" - " \"x\" : string\n" - "against the expected type\n" - " int\n", - "Cannot unify string\n" - " and int\n" - "when checking the type of the expression at line 11, column 56\n" - " \"foo\" : string\n" - "against the expected type\n" - " int\n", - "Cannot unify int\n" - " and string\n" - "when comparing the types of the if-branches\n" - " - w : int (at line 38, column 13)\n" - " - z : string (at line 39, column 10)\n", - "Not a record type: string\n" - "arising from the projection of the field y (at line 22, column 38)\n", - "Not a record type: string\n" - "arising from an assignment of the field y (at line 21, column 42)\n", - "Not a record type: string\n" - "arising from an assignment of the field y (at line 20, column 38)\n", - "Not a record type: string\n" - "arising from an assignment of the field y (at line 19, column 35)\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 5, column 10)\n", - "Record type r2 does not have field y (at line 15, column 22)\n", - "The field z is missing when constructing an element of type r2 (at line 15, column 24)\n", - "Repeated name x in pattern\n" - " x :: x (at line 26, column 7)\n", - "No record type with fields y, z (at line 14, column 22)\n"]} + [<<"Unbound variable zz at line 17, column 21">>, + <<"Cannot unify int\n" + " and list(int)\n" + "when checking the application at line 26, column 9 of\n" + " (::) : (int, list(int)) => list(int)\n" + "to arguments\n" + " x : int\n" + " x : int">>, + <<"Cannot unify string\n" + " and int\n" + "when checking the assignment of the field\n" + " x : map(string, string) (at line 9, column 46)\n" + "to the old value __x and the new value\n" + " __x {[\"foo\"] @ x = x + 1} : map(string, int)">>, + <<"Cannot unify int\n" + " and string\n" + "when checking the type of the expression at line 34, column 45\n" + " 1 : int\n" + "against the expected type\n" + " string">>, + <<"Cannot unify string\n" + " and int\n" + "when checking the type of the expression at line 34, column 50\n" + " \"bla\" : string\n" + "against the expected type\n" + " int">>, + <<"Cannot unify string\n" + " and int\n" + "when checking the type of the expression at line 32, column 18\n" + " \"x\" : string\n" + "against the expected type\n" + " int">>, + <<"Cannot unify string\n" + " and int\n" + "when checking the type of the expression at line 11, column 56\n" + " \"foo\" : string\n" + "against the expected type\n" + " int">>, + <<"Cannot unify int\n" + " and string\n" + "when comparing the types of the if-branches\n" + " - w : int (at line 38, column 13)\n" + " - z : string (at line 39, column 10)">>, + <<"Not a record type: string\n" + "arising from the projection of the field y (at line 22, column 38)">>, + <<"Not a record type: string\n" + "arising from an assignment of the field y (at line 21, column 42)">>, + <<"Not a record type: string\n" + "arising from an assignment of the field y (at line 20, column 38)">>, + <<"Not a record type: string\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" + " - r (at line 4, column 10)\n" + " - r' (at line 5, column 10)">>, + <<"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)">>, + <<"Repeated name x in pattern\n" + " x :: x (at line 26, column 7)">>, + <<"No record type with fields y, z (at line 14, column 22)">>]} , {"init_type_error", - ["Cannot unify string\n" - " and map(int, int)\n" - "when checking that 'init' returns a value of type 'state' at line 7, column 3\n"]} + [<<"Cannot unify string\n" + " and map(int, int)\n" + "when checking that 'init' returns a value of type 'state' at line 7, column 3">>]} , {"missing_state_type", - ["Cannot unify string\n" - " and ()\n" - "when checking that 'init' returns a value of type 'state' at line 5, column 3\n"]} + [<<"Cannot unify string\n" + " and ()\n" + "when checking that 'init' returns a value of type 'state' at line 5, column 3">>]} , {"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 y is missing when constructing an element of type r(int) (at line 8, column 40)\n", - "The fields y, z are missing when constructing an element of type r('1) (at line 6, 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)">>, + <<"The fields y, z are missing when constructing an element of type r('1) (at line 6, column 40)">>]} ]. diff --git a/test/contracts/all_syntax.aes b/test/contracts/all_syntax.aes index b3ac623..4b80311 100644 --- a/test/contracts/all_syntax.aes +++ b/test/contracts/all_syntax.aes @@ -36,8 +36,6 @@ contract AllSyntax = (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) (x, y :: _) => () - function bitOperations(x, y) = bnot (0xff00 band x bsl 4 bxor 0xa5a5a5 bsr 4 bor y) - function mutual() = let rec recFun(x : int) = mutFun(x) and mutFun(x) = if(x =< 0) 1 else x * recFun(x - 1) diff --git a/test/contracts/multi_sig.aes b/test/contracts/multi_sig.aes index 6c8c9ff..028b621 100644 --- a/test/contracts/multi_sig.aes +++ b/test/contracts/multi_sig.aes @@ -5,7 +5,7 @@ contract MultiSig = - record pending_state = { yetNeeded : uint, ownersDone : uint, index : uint } + record pending_state = { yetNeeded : int, ownersDone : bits, index : int } datatype event = Confirmation (address, hash) // of { .owner : Address, .operation : Hash } @@ -13,18 +13,18 @@ contract MultiSig = | OwnerChanged (address, address) // of { .oldOwner : Address, .newOwner : Address } | OwnerAdded (address) // of { .newOwner : 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 - , nOwners : uint - , owners : map(uint, address) - , ownerIndex : map(address, uint) + record state = { nRequired : int + , nOwners : int + , owners : map(int, address) + , ownerIndex : map(address, int) , pending : map(hash, pending_state) , pendingIndex : list(address) } - function init (owners : list(address), nRequired : uint) : state = + function init (owners : list(address), nRequired : int) : state = let n = length(owners) + 1 { nRequired = nRequired, nOwners = n, @@ -39,10 +39,9 @@ contract MultiSig = function revoke(operation : hash) = let ownerIx = lookup(state.ownerIndex, caller()) let pending = lookup(state.pendingIndex, operation) - let ownerIxBit = 1 bsl (ownerIx - 1) - let _ = require(pending.ownersDone band ownerIxBit > 0) + let _ = require(Bits.test(pending.ownersDone, ownerIx)) let pending' = pending { yetNeeded = pending.yetNeeded + 1 - , ownersDone = pending.ownersDone - ownerIxBit } + , ownersDone = Bits.clear(pending.ownersDone, ownerIx - 1) } put(state{ pendingIndex.operator = pending' }) event(Revoke(caller, operation)) @@ -91,7 +90,7 @@ contract MultiSig = , pendingIx = [] }, event = [OwnerRemoved(oldOwner)] } - function changeRequirement(newReq : uint) = + function changeRequirement(newReq : int) = let _ = require(newReq =< state.nOwners) switch(check_pending(callhash())) CheckFail(state') => { state = state' } @@ -102,7 +101,7 @@ contract MultiSig = event = [ReqChanged(newReq)] } - function getOwner(ownerIx0 : uint) = + function getOwner(ownerIx0 : int) = lookup(state.owners, ownerIx0 + 1) function isOwner(owner : address) = @@ -116,8 +115,7 @@ contract MultiSig = Some(pending) => let _ = require(isOwner(owner)) let ownerIx = lookup(state.ownerIndex, owner) - let ownerIxBit = 1 bsl (ownerIx - 1) - (pending.ownersDone band ownerIxBit) != 0 + Bits.test(pending.ownersDone, ownerIx - 1) /* Leave the rest for now... */ diff --git a/test/contracts/operators.aes b/test/contracts/operators.aes index 9ac89fb..1363a6c 100644 --- a/test/contracts/operators.aes +++ b/test/contracts/operators.aes @@ -1,5 +1,4 @@ // - + * / mod arithmetic operators -// bnot band bor bxor bsl bsr bitwise operators // ! && || logical operators // == != < > =< >= comparison operators // :: ++ list operators @@ -13,12 +12,6 @@ contract Operators = "/" => a / b "mod" => a mod 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) = switch(op)