From 86285a8ae039b5877749d679f54cabeaddd9af41 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 22 Jan 2019 20:34:48 +0100 Subject: [PATCH 01/21] Refactor String.concat to not shift 256 bits Adding SafeMath to VM_AEVM_SOPHIA_2 will break String.concat otherwise. --- src/aeso_builtins.erl | 73 ++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index 07e967e..6ad31b5 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,7 +144,8 @@ 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()); int_to_str -> bfun(BF, builtin_int_to_str()); @@ -154,7 +155,6 @@ builtin_function(BF) -> {baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X)); {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); - {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse_ -> bfun(BF, builtin_string_reverse_()) end. @@ -322,14 +322,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 +340,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() -> From f133483a902b0707c6a0b3e870fab9411925a406 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 25 Jan 2019 10:49:36 +0100 Subject: [PATCH 02/21] Fix git mess up --- src/aeso_builtins.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index 6ad31b5..0b44692 100644 --- a/src/aeso_builtins.erl +++ b/src/aeso_builtins.erl @@ -155,6 +155,7 @@ builtin_function(BF) -> {baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X)); {baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X)); {baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X)); + {baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X)); string_reverse -> bfun(BF, builtin_string_reverse()); string_reverse_ -> bfun(BF, builtin_string_reverse_()) end. From d8bf0bda45938bf4c9e94aede1f3c3166982ebc5 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 25 Jan 2019 12:49:11 +0100 Subject: [PATCH 03/21] Remove integer bit operations --- src/aeso_ast_infer_types.erl | 6 ++---- src/aeso_ast_to_icode.erl | 3 --- src/aeso_parser.erl | 6 +++--- src/aeso_pretty.erl | 8 +------- src/aeso_scan.erl | 3 +-- src/aeso_syntax.erl | 4 ++-- test/contracts/all_syntax.aes | 2 -- test/contracts/operators.aes | 7 ------- 8 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index cfeb08e..7bbe6b4 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -691,8 +691,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 +713,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..9f90550 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -526,9 +526,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_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..f9209dd 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -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/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/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) From a367d5040a09f8052a56156e9470c93794ece7fe Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 25 Jan 2019 13:34:28 +0100 Subject: [PATCH 04/21] Add builtin bit field type --- src/aeso_ast_infer_types.erl | 7 +++++++ src/aeso_ast_to_icode.erl | 24 ++++++++++++++++++++++++ src/aeso_builtins.erl | 12 ++++++++++++ src/aeso_icode.erl | 1 + test/contracts/multi_sig.aes | 28 +++++++++++++--------------- 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 7bbe6b4..dcfdfb1 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,12 @@ 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", "zero"], Bits}, %% Conversion {["Int", "to_str"], Fun1(Int, String)}, {["Address", "to_str"], Fun1(Address, String)} diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 9f90550..023fd42 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -348,6 +348,30 @@ 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", "test"], [Bits, Ix], _, _), Icode) -> + %% (Bits bsr Ix) band 1 + #binop{ op = 'band' + , left = #binop{ op = 'bsr', left = ast_body(Ix, Icode), right = ast_body(Bits, Icode) } + , right = #integer{ value = 1 } }; +ast_body(?qid_app(["Bits", "set"], [Bits, Ix], _, _), Icode) -> + %% Bits bor (1 bsl Ix) + #binop{ op = 'bor' + , left = ast_body(Bits, Icode) + , right = #binop{ op = 'bsl', left = ast_body(Ix, Icode), right = #integer{ value = 1 } } }; +ast_body(?qid_app(["Bits", "clear"], [Bits, Ix], _, _), Icode) -> + %% Bits band (bnot (1 bsl Ix)) + #binop{ op = 'band' + , left = ast_body(Bits, Icode) + , right = #unop{ op = 'bnot' + , rand = #binop{ op = 'bsl' + , left = ast_body(Ix, Icode) + , right = #integer{ value = 1 } } } }; +ast_body({qid, _, ["Bits", "zero"]}, _Icode) -> + #integer{ value = 0 }; +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)]); diff --git a/src/aeso_builtins.erl b/src/aeso_builtins.erl index 0b44692..383fcf4 100644 --- a/src/aeso_builtins.erl +++ b/src/aeso_builtins.erl @@ -148,6 +148,7 @@ builtin_function(BF) -> 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)); @@ -398,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_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/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... */ From 9c77622c7c6e25a6badb1b3045989a70127f229d Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 25 Jan 2019 14:10:12 +0100 Subject: [PATCH 05/21] Add set operations on bit fields (union, isect, diff) --- src/aeso_ast_infer_types.erl | 3 +++ src/aeso_ast_to_icode.erl | 37 ++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index dcfdfb1..154e617 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -165,6 +165,9 @@ global_env() -> {["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", "zero"], Bits}, %% Conversion {["Int", "to_str"], Fun1(Int, String)}, diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 023fd42..b9fb28f 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -349,24 +349,25 @@ ast_body(?qid_app(["String", "sha3"], [String], _, _), Icode) -> #unop{ op = 'sha3', rand = ast_body(String, Icode) }; %% -- Bits -ast_body(?qid_app(["Bits", "test"], [Bits, Ix], _, _), Icode) -> - %% (Bits bsr Ix) band 1 - #binop{ op = 'band' - , left = #binop{ op = 'bsr', left = ast_body(Ix, Icode), right = ast_body(Bits, Icode) } - , right = #integer{ value = 1 } }; -ast_body(?qid_app(["Bits", "set"], [Bits, Ix], _, _), Icode) -> - %% Bits bor (1 bsl Ix) - #binop{ op = 'bor' - , left = ast_body(Bits, Icode) - , right = #binop{ op = 'bsl', left = ast_body(Ix, Icode), right = #integer{ value = 1 } } }; -ast_body(?qid_app(["Bits", "clear"], [Bits, Ix], _, _), Icode) -> - %% Bits band (bnot (1 bsl Ix)) - #binop{ op = 'band' - , left = ast_body(Bits, Icode) - , right = #unop{ op = 'bnot' - , rand = #binop{ op = 'bsl' - , left = ast_body(Ix, Icode) - , right = #integer{ value = 1 } } } }; +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 }; + (AST) -> ast_body(AST, Icode) 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 | 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", "zero"]}, _Icode) -> #integer{ value = 0 }; ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) -> From 3e1290efafc9f37869fdac9292b4e638b000fd8b Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 25 Jan 2019 14:12:23 +0100 Subject: [PATCH 06/21] Add Bits.all and rename Bits.zero to Bits.none --- src/aeso_ast_infer_types.erl | 3 ++- src/aeso_ast_to_icode.erl | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 154e617..76e8d62 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -168,7 +168,8 @@ global_env() -> {["Bits", "intersection"], Fun([Bits, Bits], Bits)}, {["Bits", "union"], Fun([Bits, Bits], Bits)}, {["Bits", "difference"], Fun([Bits, Bits], Bits)}, - {["Bits", "zero"], Bits}, + {["Bits", "none"], Bits}, + {["Bits", "all"], Bits}, %% Conversion {["Int", "to_str"], Fun1(Int, String)}, {["Address", "to_str"], Fun1(Address, String)} diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index b9fb28f..a3a55aa 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -368,8 +368,10 @@ ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode) ["intersection", A, B] -> And(A, B); ["difference", A, B] -> And(A, Neg(And(A, B))) end; -ast_body({qid, _, ["Bits", "zero"]}, _Icode) -> +ast_body({qid, _, ["Bits", "none"]}, _Icode) -> #integer{ value = 0 }; +ast_body({qid, _, ["Bits", "all"]}, _Icode) -> + #integer{ value = -1 }; ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) -> builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]); From 79de25b3a5f22db37aa716c8663dae0715896531 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 25 Jan 2019 15:03:52 +0100 Subject: [PATCH 07/21] Fix minor bugs in compilation of bit fields --- src/aeso_ast_to_icode.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index a3a55aa..2e00104 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -353,14 +353,14 @@ 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 }; - (AST) -> ast_body(AST, Icode) end, + (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 | Args] of + 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))); @@ -371,7 +371,7 @@ ast_body(?qid_app(["Bits", Fun], Args, _, _), Icode) ast_body({qid, _, ["Bits", "none"]}, _Icode) -> #integer{ value = 0 }; ast_body({qid, _, ["Bits", "all"]}, _Icode) -> - #integer{ value = -1 }; + #integer{ value = 1 bsl 256 - 1 }; ast_body(?qid_app(["Bits", "sum"], [Bits], _, _), Icode) -> builtin_call(popcount, [ast_body(Bits, Icode), #integer{ value = 0 }]); From fe1a2758c3d6d06b9c8cf2dbce8d55561cc411cb Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Fri, 11 Jan 2019 17:36:31 +0100 Subject: [PATCH 08/21] Improve the interface to the compiler It is now more consistent though we can still discuss how we want the interface to look. --- src/aeso_compiler.erl | 134 +++++++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 49 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index e132005..2e2b27a 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 @@ -43,29 +43,51 @@ file(Filename) -> file(Filename, []). -spec file(string(), options()) -> map(). -file(Filename, Options) -> - C = read_contract(Filename), - from_string(C, Options). +file(File, Options) -> + case read_contract(File, Options) of + {ok,Bin} -> from_string(Bin, Options); + {error,Error} -> {error,{File,Error}} + end. --spec from_string(string(), options()) -> map(). +-spec from_string(binary() | string(), options()) -> map(). +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,26 +98,36 @@ 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()) -> @@ -113,7 +145,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 @@ -251,9 +283,13 @@ 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). +read_contract(Name, Opts) -> + FileName = filename(Name, ".aes", Opts), + file:read_file(FileName). -contract_path() -> - "apps/aesophia/test/contracts". +filename(File, Suffix, _Opts) -> + Base = filename:basename(File, Suffix), + Sdir = filename:dirname(File), + if Sdir == "." -> Base ++ Suffix; + true -> filename:join(Sdir, Base ++ Suffix) + end. From 70ad303e160f454a838ddbe663bdc9574e85b7f5 Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Fri, 11 Jan 2019 17:40:20 +0100 Subject: [PATCH 09/21] Document the aeso_compiler module The module doc format is loosely based on the standard erlang doc formats. --- .gitignore | 2 ++ README.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/.gitignore b/.gitignore index 40ca652..695011d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ _build .idea *.iml rebar3.crashdump +*.erl~ +*.aes~ diff --git a/README.md b/README.md index d29c484..b8d9720 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,88 @@ 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 can then be loaded into the æternity system. + +## Modules + +### 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. From db1c0fa05a49139ee80c96d33ed75bba63cdf95d Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Fri, 11 Jan 2019 17:41:54 +0100 Subject: [PATCH 10/21] Improve pretty printing the code AST --- src/aeso_ast.erl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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]). From a73abf8e8ebe16336abec7eca4cefb10d1c7fc46 Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Fri, 25 Jan 2019 15:57:02 +0100 Subject: [PATCH 11/21] Fix testing to use new error message format --- test/aeso_compiler_tests.erl | 176 +++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 81 deletions(-) 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)">>]} ]. From f1f2b09294c196d6e64435a6f8d1dcab24cc7c82 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 28 Jan 2019 10:47:12 +0100 Subject: [PATCH 12/21] Cleanup whitespace, bad typespec, and remaining enacl reference --- src/aeso_compiler.erl | 126 +++++++++++++++++++++--------------------- src/aesophia.app.src | 1 - 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 2e2b27a..8d270e6 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -22,7 +22,7 @@ -type option() :: pp_sophia_code | pp_ast | pp_types | pp_typed_ast | - pp_icode| pp_assembler | pp_bytecode. + pp_icode| pp_assembler | pp_bytecode. -type options() :: [option()]. -export_type([ option/0 @@ -38,50 +38,50 @@ version() -> ?COMPILER_VERSION. --spec file(string()) -> map(). +-spec file(string()) -> {ok, map()} | {error, binary()}. file(Filename) -> file(Filename, []). --spec file(string(), options()) -> map(). +-spec file(string(), options()) -> {ok, map()} | {error, binary()}. file(File, Options) -> case read_contract(File, Options) of - {ok,Bin} -> from_string(Bin, Options); - {error,Error} -> {error,{File,Error}} + {ok, Bin} -> from_string(Bin, Options); + {error, Error} -> {error, {File, Error}} end. --spec from_string(binary() | 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) -> 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 - }} + 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)} + 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. @@ -99,35 +99,35 @@ join_errors(Prefix, Errors, Pfun) -> when Type :: term(). check_call(ContractString, Options) -> 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} + 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)} + 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()) -> @@ -279,9 +279,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, Opts) -> FileName = filename(Name, ".aes", Opts), diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 46c5a67..37b056d 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -5,7 +5,6 @@ {applications, [kernel, stdlib, - enacl, syntax_tools, aebytecode ]}, From 3a7c8f905a6e9e0f9b416e48b175f17f8c888e39 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 28 Jan 2019 14:54:34 +0100 Subject: [PATCH 13/21] Fix incorrect type specs h/t OTP-21 dialyzer --- src/aeso_abi.erl | 10 +++++----- src/aeso_compiler.erl | 2 +- src/aeso_pretty.erl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) 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_compiler.erl b/src/aeso_compiler.erl index 8d270e6..c41b09c 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -131,7 +131,7 @@ check_call(ContractString, Options) -> 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 diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index f9209dd..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, ".")); From ceb7de21195ec57f4d5c778f75cc4a55f6c00dac Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Tue, 29 Jan 2019 13:58:00 +0100 Subject: [PATCH 14/21] Remove a leftover reference to enacl --- src/aesophia.app.src | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 46c5a67..37b056d 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -5,7 +5,6 @@ {applications, [kernel, stdlib, - enacl, syntax_tools, aebytecode ]}, From b8cb7ab1b550eca5fbba71b5d5558f7999e0c65c Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 28 Jan 2019 14:54:34 +0100 Subject: [PATCH 15/21] Fix incorrect type specs h/t OTP-21 dialyzer --- src/aeso_abi.erl | 10 +++++----- src/aeso_compiler.erl | 2 +- src/aeso_pretty.erl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) 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_compiler.erl b/src/aeso_compiler.erl index e132005..bbe8e10 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -99,7 +99,7 @@ check_call(ContractString, Options) -> 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 diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 3462a2e..71da2b4 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, ".")); From d3fa04483ea18fdb318a06a4f47530e42add5b55 Mon Sep 17 00:00:00 2001 From: Dincho Todorov Date: Tue, 29 Jan 2019 15:35:41 +0200 Subject: [PATCH 16/21] Initial CircleCI integration (#20) --- .circleci/config.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/config.yml 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 From 87e5562f74e6b9906ec2f28f2fffffb6e304a6bc Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 25 Jan 2019 14:49:18 +0100 Subject: [PATCH 17/21] Super simple standalone version of the compiler --- rebar.config | 5 +++ rebar.lock | 8 ++++- src/aeso_compiler.erl | 2 +- src/aesophia.app.src | 1 + src/aesophia.erl | 71 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/aesophia.erl diff --git a/rebar.config b/rebar.config index 66e9732..03d2d8b 100644 --- a/rebar.config +++ b/rebar.config @@ -1,8 +1,13 @@ {erl_opts, [debug_info]}. +{escript_name, aesophia}. + +{provider_hooks, [{post, [{compile, escriptize}]}]}. + %% NOTE: When possible deps are referenced by Git ref to ensure consistency between builds. {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"99bf097"}}} + , {getopt, "1.0.1"} ]}. diff --git a/rebar.lock b/rebar.lock index 34aa736..6f391f3 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,4 +1,10 @@ +{"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", {ref,"99bf097759dedbe7553f87a796bc7e1c7322e64b"}}, - 0}]. + 0}, + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}. +[ +{pkg_hash,[ + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} +]. diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index bbe8e10..ca496ab 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -252,7 +252,7 @@ parse_error({Line,Pos}, ErrorString) -> error({parse_errors,[Error]}). read_contract(Name) -> - {ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))), + {ok, Bin} = file:read_file(Name), binary_to_list(Bin). contract_path() -> diff --git a/src/aesophia.app.src b/src/aesophia.app.src index 37b056d..6a47608 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -6,6 +6,7 @@ [kernel, stdlib, 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]). From 65b679117641375a00f93dd12457c8f980566080 Mon Sep 17 00:00:00 2001 From: Dincho Todorov Date: Tue, 29 Jan 2019 15:35:41 +0200 Subject: [PATCH 18/21] Initial CircleCI integration (#20) --- .circleci/config.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .circleci/config.yml 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 From 362373c0d7bd34474c8a28e2fd65e59f42cfe45d Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Wed, 30 Jan 2019 10:55:11 +0100 Subject: [PATCH 19/21] Add escript post_hooks --- rebar.config | 19 ++++++++++++++----- src/aeso_compiler.erl | 2 -- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/rebar.config b/rebar.config index 03d2d8b..f2e5106 100644 --- a/rebar.config +++ b/rebar.config @@ -1,15 +1,24 @@ {erl_opts, [debug_info]}. -{escript_name, aesophia}. - -{provider_hooks, [{post, [{compile, escriptize}]}]}. - -%% NOTE: When possible deps are referenced by Git ref to ensure consistency between builds. {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"99bf097"}}} , {getopt, "1.0.1"} ]}. +{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]}, diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index ca496ab..92d9afe 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -255,5 +255,3 @@ read_contract(Name) -> {ok, Bin} = file:read_file(Name), binary_to_list(Bin). -contract_path() -> - "apps/aesophia/test/contracts". From 515f444e7cb631ed354bda06cce16d21d73d2983 Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Wed, 6 Feb 2019 14:02:46 +0100 Subject: [PATCH 20/21] Remove specific filename extension handling And take the chance to make file handling errors ahve the same format as other errors. --- src/aeso_compiler.erl | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index c41b09c..e54b8ef 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -44,9 +44,11 @@ file(Filename) -> -spec file(string(), options()) -> {ok, map()} | {error, binary()}. file(File, Options) -> - case read_contract(File, Options) of + case read_contract(File) of {ok, Bin} -> from_string(Bin, Options); - {error, Error} -> {error, {File, Error}} + {error, Error} -> + ErrorString = [File,": ",file:format_error(Error)], + {error, join_errors("File errors", [ErrorString], fun(E) -> E end)} end. -spec from_string(binary() | string(), options()) -> {ok, map()} | {error, binary()}. @@ -283,13 +285,5 @@ parse_error({Line, Pos}, ErrorString) -> Error = io_lib:format("line ~p, column ~p: ~s", [Line, Pos, ErrorString]), error({parse_errors, [Error]}). -read_contract(Name, Opts) -> - FileName = filename(Name, ".aes", Opts), - file:read_file(FileName). - -filename(File, Suffix, _Opts) -> - Base = filename:basename(File, Suffix), - Sdir = filename:dirname(File), - if Sdir == "." -> Base ++ Suffix; - true -> filename:join(Sdir, Base ++ Suffix) - end. +read_contract(Name) -> + file:read_file(Name). From 0b4c2f14fe7366c92374c729d9f9cb32c217d2f6 Mon Sep 17 00:00:00 2001 From: Robert Virding Date: Fri, 8 Feb 2019 10:31:35 +0100 Subject: [PATCH 21/21] Move module documentation to separate files (#23) --- README.md | 87 ++----------------------------------------- docs/aeso_compiler.md | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 83 deletions(-) create mode 100644 docs/aeso_compiler.md diff --git a/README.md b/README.md index b8d9720..2592113 100644 --- a/README.md +++ b/README.md @@ -6,90 +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. -## Modules +## Interface Modules -### aeso_compiler +The basic modules for interfacing the 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. +* [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.