Compare commits

..

36 Commits

Author SHA1 Message Date
Thomas Arts be9935cd7e Merge branch 'master' into quickcheck-ci 2019-02-11 13:11:46 +01:00
Hans Svensson 026ff52528 Merge pull request #24 from aeternity/merge_roma
Merge ROMA branch to master
2019-02-08 14:13:10 +01:00
Hans Svensson 3ba89d9f55 Merge ROMA into MINERVA 2019-02-08 13:38:37 +01:00
Robert Virding 0b4c2f14fe Move module documentation to separate files (#23) 2019-02-08 10:31:35 +01:00
Robert Virding b65c4edd19 Merge pull request #22 from aeternity/fix-compiler-interface
Remove specific filename extension handling
2019-02-06 15:17:53 +01:00
Robert Virding 515f444e7c Remove specific filename extension handling
And take the chance to make file handling errors ahve the same format
as other errors.
2019-02-06 14:02:46 +01:00
Dincho Todorov d3fa04483e Initial CircleCI integration (#20) 2019-01-29 15:35:41 +02:00
Ulf Norell 2edafe0adc Merge pull request #19 from aeternity/dialyzer-warnings
Fix incorrect type specs
2019-01-29 09:19:13 +01:00
Ulf Norell 3a7c8f905a Fix incorrect type specs
h/t OTP-21 dialyzer
2019-01-28 14:54:34 +01:00
Hans Svensson 9026e1fe6b Merge pull request #18 from aeternity/minor_fixing_up
Cleanup whitespace, bad typespec, and remaining enacl reference
2019-01-28 11:24:29 +01:00
Hans Svensson f1f2b09294 Cleanup whitespace, bad typespec, and remaining enacl reference 2019-01-28 10:50:32 +01:00
Ulf Norell 53299b9b17 Merge pull request #17 from aeternity/PT-163478903-builtin-bits-type
PT-163478903 builtin bits type
2019-01-28 10:05:09 +01:00
Robert Virding 64fd91197b Merge pull request #9 from aeternity/interface-to-sophia
PT-163063316 Interface to sophia
2019-01-25 16:20:38 +01:00
Robert Virding a73abf8e8e Fix testing to use new error message format 2019-01-25 16:16:20 +01:00
Robert Virding db1c0fa05a Improve pretty printing the code AST 2019-01-25 16:16:20 +01:00
Robert Virding 70ad303e16 Document the aeso_compiler module
The module doc format is loosely based on the standard erlang doc
formats.
2019-01-25 16:16:20 +01:00
Robert Virding fe1a2758c3 Improve the interface to the compiler
It is now more consistent though we can still discuss how we want the
interface to look.
2019-01-25 16:16:20 +01:00
Ulf Norell 79de25b3a5 Fix minor bugs in compilation of bit fields 2019-01-25 16:09:31 +01:00
Ulf Norell 3e1290efaf Add Bits.all and rename Bits.zero to Bits.none 2019-01-25 16:09:31 +01:00
Ulf Norell 9c77622c7c Add set operations on bit fields (union, isect, diff) 2019-01-25 16:09:31 +01:00
Ulf Norell a367d5040a Add builtin bit field type 2019-01-25 16:09:31 +01:00
Ulf Norell d8bf0bda45 Remove integer bit operations 2019-01-25 16:09:31 +01:00
Hans Svensson 922107e438 Merge pull request #16 from aeternity/git_mess_up_fix
Fix git mess up
2019-01-25 10:53:06 +01:00
Hans Svensson f133483a90 Fix git mess up 2019-01-25 10:49:36 +01:00
Hans Svensson be42ee08ab Merge pull request #15 from aeternity/fix_string_concat
Refactor String.concat to not shift 256 bits
2019-01-25 10:36:09 +01:00
Hans Svensson 86285a8ae0 Refactor String.concat to not shift 256 bits
Adding SafeMath to VM_AEVM_SOPHIA_2 will break String.concat otherwise.
2019-01-25 09:57:23 +01:00
Thomas Arts aa2e6aa218 machinery for running QuickCheck
No script needed if we make sure extra_src_dirs has different name than "eqc"

Obsolete QuickCheck property
2019-01-24 09:11:26 +01:00
Hans Svensson c5c73097fc Merge pull request #13 from aeternity/PT-163388694-add_native_bit_shift
Use ?SHL and ?SHR for 'bsl' and 'bsr'
2019-01-23 13:15:14 +01:00
Hans Svensson 23ccce4c22 Use ?SHL and ?SHR for 'bsl' and 'bsr' 2019-01-22 21:53:30 +01:00
Ulf Norell 387fdf5c34 Merge pull request #12 from aeternity/PT-163362624-generic-hash
PT-163362624 generic hash functions
2019-01-22 15:04:47 +01:00
Ulf Norell f0dcda27bc Update aebytecode commit 2019-01-22 14:54:38 +01:00
Ulf Norell 5fd24fec86 Add more hash primops 2019-01-22 09:09:37 +01:00
Hans Svensson 4bedbfee61 Merge pull request #11 from aeternity/PT-163083710-implement_ecverify
Add Crypto.ecverify
2019-01-22 08:51:10 +01:00
Hans Svensson e4c46ee16f Bump aebytecode dependency 2019-01-21 20:14:20 +01:00
Hans Svensson d8fff8f20f Add Crypto.ecverify 2019-01-21 14:20:15 +01:00
Luca Favatella b074aa3323 Rename description of node in readme (#10) 2019-01-18 10:58:26 +00:00
23 changed files with 465 additions and 186 deletions
+3
View File
@@ -0,0 +1,3 @@
{build, "rebar3 as eqc compile"}.
{test_root, "."}.
{test_path, "_build/eqc/lib/aesophia/quickcheck"}. %% here are the properties
+5
View File
@@ -16,3 +16,8 @@ _build
.idea
*.iml
rebar3.crashdump
current_counterexample.eqc
.qcci
*.erl~
*.aes~
+8 -2
View File
@@ -5,6 +5,12 @@ This is the __sophia__ compiler for the æternity system which compiles contract
For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md).
It is an OTP application written in Erlang and is by default included in
[æternity Epoch](https://github.com/aeternity/epoch). However, it can
also be included in other system to compile contracts coded in sophia which
[the æternity node](https://github.com/aeternity/epoch). However, it can
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)
+86
View File
@@ -0,0 +1,86 @@
# aeso_compiler
### Module
### aeso_compiler
The Sophia compiler
### Description
This module provides the interface to the standard Sophia compiler. It
returns the compiled module in a map which can then be loaded.
### Types
``` erlang
contract_string() = string() | binary()
contract_map() = #{bytecode => binary(),
compiler_version => string(),
contract_souce => string(),
type_info => type_info()}
type_info()
errorstring() = binary()
```
### Exports
#### file(File)
#### file(File, Options) -> CompRet
#### from_string(ContractString, Options) -> CompRet
Types
``` erlang
ContractString = contract_string()
Options = [Option]
CompRet = {ok,ContractMap} | {error,ErrorString}
ContractMap = contract_map()
ErrorString = errorstring()
```
Compile a contract defined in a file or in a string.
The **pp_** options all print to standard output the following:
`pp_sophia_code` - print the input Sophia code.
`pp_ast` - print the AST of the code
`pp_types` - print information about the types
`pp_typed_ast` - print the AST with type information at each node
`pp_icode` - print the internal code structure
`pp_assembler` - print the generated assembler code
`pp_bytecode` - print the bytecode instructions
#### check_call(ContractString, Options) -> CheckRet
Types
```
ContractString = string() | binary()
CheckRet = {ok,string(),{Types,Type | any()},Terms} | {error,Term}
Types = [Type]
Type = term()
```
Check a call in contract through the `__call` function.
#### sophia_type_to_typerep(String) -> TypeRep
Types
``` erlang
{ok,TypeRep} | {error, badtype}
```
Get the type representation of a type declaration.
#### version() -> Version
Types
``` erlang
Version = integer()
```
Get the current version of the Sophia compiler.
+109
View File
@@ -0,0 +1,109 @@
%%% File : aeso_heap_eqc.erl
%%% Author : Ulf Norell
%%% Description :
%%% Created : 28 May 2018 by Ulf Norell
-module(aeso_heap_eqc).
-compile([export_all, nowarn_export_all]).
-include_lib("eqc/include/eqc.hrl").
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
sandbox(Code) ->
Parent = self(),
Tag = make_ref(),
{Pid, Ref} = spawn_monitor(fun() -> Parent ! {Tag, Code()} end),
receive
{Tag, Res} -> erlang:demonitor(Ref, [flush]), {ok, Res};
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
after 100 ->
exit(Pid, kill),
{error, loop}
end.
prop_from_binary() ->
?FORALL({T, Bin}, {type(), blob()},
begin
Tag = fun(X) when is_atom(X) -> X; (X) when is_tuple(X) -> element(1, X) end,
case ?SANDBOX(aeso_heap:from_binary(T, Bin)) of
{ok, Res} -> collect({Tag(T), element(1, Res)}, true);
Err -> equals(Err, {ok, '_'})
end end).
type() -> ?LET(Depth, choose(0, 2), type(Depth, true)).
type(Depth, TypeRep) ->
oneof(
[ elements([word, string] ++ [typerep || TypeRep]) ] ++
[ ?LETSHRINK([T], [type(Depth - 1, TypeRep)], {list, T}) || Depth > 0 ] ++
[ ?LETSHRINK([T], [type(Depth - 1, TypeRep)], {option, T}) || Depth > 0 ] ++
[ ?LETSHRINK(Ts, list(type(Depth - 1, TypeRep)), {tuple, Ts}) || Depth > 0 ] ++
[ ?LETSHRINK([K, V], vector(2, type(Depth - 1, TypeRep)), {map, K, V}) || Depth > 0 ] ++
[]
).
blob() ->
?LET(Blobs, list(oneof([ ?LET(Ws, words(), return(from_words(Ws)))
, binary() ])),
return(list_to_binary(Blobs))).
words() -> list(word()).
word() ->
frequency(
[ {4, ?LET(N, nat(), 32 * N)}
, {1, choose(0, 320)}
, {2, -1}
, {2, elements(["foo", "zzzzz"])} ]).
from_words(Ws) ->
<< <<(from_word(W))/binary>> || W <- Ws >>.
from_word(W) when is_integer(W) ->
<<W:256>>;
from_word(S) when is_list(S) ->
Len = length(S),
Bin = <<(list_to_binary(S))/binary, 0:(32 - Len)/unit:8>>,
<<Len:256, Bin/binary>>.
typerep() -> ?LET(Depth, choose(0, 2),
?LET(T, type(Depth, true), return(typerep(T)))).
typerep(word) -> word;
typerep(string) -> string;
typerep(typerep) -> typerep;
typerep({tuple, Ts}) -> {tuple, typerep(Ts)};
typerep({list, T}) -> {list, typerep(T)};
typerep({variant, Cs}) -> {variant, typerep(Cs)};
typerep({option, T}) -> {variant, [[], [typerep(T)]]};
typerep({map, K, V}) -> {list, typerep({tuple, [K, V]})};
typerep([]) -> [];
typerep([T | Ts]) -> [typerep(T) | typerep(Ts)].
value(word) ->
<<N:256>> = <<(-1):256>>,
choose(0, N);
value(string) ->
?LET(N, choose(0, 128), binary(N));
value(typerep) ->
typerep();
value({list, T}) ->
list(value(T));
value({option, T}) ->
weighted_default({1, none}, {3, {some, value(T)}});
value({tuple, Ts}) ->
?LET(Vs, [ value(T) || T <- Ts ], list_to_tuple(Vs));
value({map, K, V}) ->
map(value(K), value(V));
value({variant, Cs}) ->
?LET(I, choose(0, length(Cs) - 1),
{variant, I, [ value(T) || T <- lists:nth(I + 1, Cs) ]}).
typed_val() ->
?LET(T, type(), ?LET(V, value(T), return({T, V}))).
prop_roundtrip() ->
?FORALL(T, type(),
?FORALL(V, value(T),
?FORALL(B, choose(0, 4),
equals(aeso_heap:from_binary(T, aeso_heap:to_binary(V, B * 32), B * 32),
{ok, V})))).
+59
View File
@@ -0,0 +1,59 @@
%%% File : aeso_utils_eqc.erl
%%% Author : Ulf Norell
%%% Description :
%%% Created : 2 Jul 2018 by Ulf Norell
-module(aeso_utils_eqc).
-compile([export_all, nowarn_export_all]).
-include_lib("eqc/include/eqc.hrl").
%% QuickCheck property
graph() ->
?LET(M, map(choose(0, 10), list(choose(0, 10))),
return(complete(M))).
complete(G) ->
Is = lists:usort(lists:concat(maps:values(G))),
maps:merge(maps:from_list([ {I, []} || I <- Is ]), G).
prop_scc() ->
?FORALL(G, graph(),
begin
SCCs = aeso_utils:scc(G),
BadSCC = fun({acyclic, I}) -> reachable_from(G, I, I);
({cyclic, Is}) -> [] /= [ {I, J} || I <- Is, J <- Is, not reachable_from(G, I, J) ]
end,
ToList = fun({acyclic, I}) -> [I];
({cyclic, Is}) -> Is end,
?WHENFAIL(eqc:format("SCCs = ~p\n", [SCCs]),
conjunction(
[ {elems, equals(lists:sort(lists:flatmap(ToList, SCCs)), lists:sort(maps:keys(G)))}
, {sorted, equals([], [ {I, J} || {I, Js} <- maps:to_list(G),
J <- Js,
find_component(I, SCCs) < find_component(J, SCCs) ])}
, {precise, equals([], [ SCC || SCC <- SCCs, BadSCC(SCC) ])}
]))
end).
reachable_from(Graph, I, J) ->
reachable_from1(Graph, maps:get(I, Graph, []), J).
reachable_from1(_, [], _) -> false;
reachable_from1(_, [I | _], I) -> true;
reachable_from1(Graph, [I | Is], J) ->
case maps:get(I, Graph, undefined) of
undefined -> reachable_from1(Graph, Is, J);
Js -> reachable_from1(maps:remove(I, Graph), Js ++ Is, J)
end.
find_component(X, SCCs) ->
ISCCs = lists:zip(SCCs, lists:seq(1, length(SCCs))),
HasX = fun({acyclic, Y}) -> X == Y;
({cyclic, Ys}) -> lists:member(X, Ys) end,
case [ I || {SCC, I} <- ISCCs, HasX(SCC) ] of
[I | _] -> I;
[] -> false
end.
+8 -1
View File
@@ -1,10 +1,17 @@
{erl_opts, [debug_info]}.
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git",
{ref,"99bf097"}}}
{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}.
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0",
[{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git",
{ref,"99bf097759dedbe7553f87a796bc7e1c7322e64b"}},
{ref,"720510a24de32c9bad6486f34ca7babde124bf1e"}},
0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
[
+5 -4
View File
@@ -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]).
+24 -8
View File
@@ -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, []},
@@ -148,12 +149,29 @@ global_env() ->
{["Map", "delete"], Fun([K, Map(K, V)], Map(K, V))},
{["Map", "member"], Fun([K, Map(K, V)], Bool)},
{["Map", "size"], Fun1(Map(K, V), Int)},
%% Crypto/Curve operations
{["Crypto", "ecverify"], Fun([Hash, Address, SignId], Bool)},
{["Crypto", "sha3"], Fun1(A, Hash)},
{["Crypto", "sha256"], Fun1(A, Hash)},
{["Crypto", "blake2b"], Fun1(A, Hash)},
%% Strings
{["String", "length"], Fun1(String, Int)},
{["String", "concat"], Fun([String, String], String)},
{["String", "sha3"], Fun1(String, Int)},
{["String", "length"], Fun1(String, Int)},
{["String", "concat"], Fun([String, String], String)},
{["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)},
{["Int", "to_str"], Fun1(Int, String)},
{["Address", "to_str"], Fun1(Address, String)}
].
@@ -684,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})
@@ -707,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}.
+54 -6
View File
@@ -316,6 +316,23 @@ ast_body({map, _, Map, [Upd]}, Icode) ->
ast_body({map, Ann, Map, [Upd | Upds]}, Icode) ->
ast_body({map, Ann, {map, Ann, Map, [Upd]}, Upds}, Icode);
%% Crypto
ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) ->
prim_call(?PRIM_CALL_CRYPTO_ECVERIFY, #integer{value = 0},
[ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)],
[word, word, sign_t()], word);
ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_SHA256, Term, Type, Icode);
ast_body(?qid_app(["Crypto", "blake2b"], [Term], [Type], _), Icode) ->
generic_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B, Term, Type, Icode);
ast_body(?qid_app(["String", "sha256"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_SHA256_STRING, String, Icode);
ast_body(?qid_app(["String", "blake2b"], [String], _, _), Icode) ->
string_hash_primop(?PRIM_CALL_CRYPTO_BLAKE2B_STRING, String, Icode);
%% Strings
%% -- String length
ast_body(?qid_app(["String", "length"], [String], _, _), Icode) ->
@@ -331,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)]);
@@ -509,12 +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)] };
ast_binop('bsl', _, A, B, Icode) ->
#binop{op = '*', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop('bsr', _, A, B, Icode) ->
#binop{op = 'div', left = ast_body(A, Icode),
right = #binop{op = '^', left = {integer, 2}, right = ast_body(B, Icode)}};
ast_binop(Op, _, A, B, Icode) ->
#binop{op = Op, left = ast_body(A, Icode), right = ast_body(B, Icode)}.
@@ -593,6 +631,16 @@ prim_call(Prim, Amount, Args, ArgTypes, OutType) ->
type_hash= #integer{value = TypeHash}
}.
generic_hash_primop(PrimOp, Term, Type, Icode) ->
ArgType = ast_type(Type, Icode),
TypeValue = type_value(ArgType),
prim_call(PrimOp, #integer{value = 0},
[TypeValue, ast_body(Term, Icode)],
[typerep, ArgType], word).
string_hash_primop(PrimOp, String, Icode) ->
prim_call(PrimOp, #integer{value = 0}, [ast_body(String, Icode)], [string], word).
make_type_def(Args, Def, Icode = #{ type_vars := TypeEnv }) ->
TVars = [ X || {tvar, _, X} <- Args ],
fun(Types) ->
+53 -36
View File
@@ -38,7 +38,7 @@ builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
builtin_deps1(map_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}];
@@ -81,8 +81,9 @@ option_some(X) -> {tuple, [{integer, 1}, X]}.
-define(EXP(A, B), op('^', A, B)).
-define(AND(A, B), op('&&', A, B)).
-define(BSL(X, B), ?MUL(X, ?EXP(2, ?MUL(B, 8)))).
-define(BSR(X, B), ?DIV(X, ?EXP(2, ?MUL(B, 8)))).
%% Bit shift operations takes their arguments backwards!?
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
op(Op, A, B) -> {binop, Op, operand(A), operand(B)}.
@@ -143,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));
@@ -321,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}.
@@ -336,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() ->
@@ -393,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}.
+3 -2
View File
@@ -46,7 +46,9 @@ file(Filename) ->
file(File, Options) ->
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()}.
@@ -285,4 +287,3 @@ parse_error({Line, Pos}, ErrorString) ->
read_contract(Name) ->
file:read_file(Name).
+1
View File
@@ -59,6 +59,7 @@ builtin_types() ->
Word = fun([]) -> word end,
#{ "bool" => Word
, "int" => Word
, "bits" => Word
, "string" => fun([]) -> string end
, "address" => Word
, "hash" => Word
+2
View File
@@ -562,6 +562,8 @@ assemble_infix('^') -> i(?EXP);
assemble_infix('bor') -> i(?OR);
assemble_infix('band') -> i(?AND);
assemble_infix('bxor') -> i(?XOR);
assemble_infix('bsl') -> i(?SHL);
assemble_infix('bsr') -> i(?SHR);
assemble_infix('<') -> i(?SLT); %% comparisons are SIGNED
assemble_infix('>') -> i(?SGT);
assemble_infix('==') -> i(?EQ);
+3 -3
View File
@@ -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() ->
+1 -7
View File
@@ -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]}.
+1 -2
View File
@@ -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 =
+2 -2
View File
@@ -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()}
+24 -88
View File
@@ -6,13 +6,7 @@
[ {src_file, undefined, undefined, string, "Sophia source code file"}
, {verbose, $v, "verbose", undefined, "Verbose output"}
, {help, $h, "help", undefined, "Show this message"}
, {create_calldata, $c, "create_calldata", string,
"Create calldata with respect to (compiled) contract in this file"}
, {create_calldata_fun, undefined, "calldata_fun", string,
"Deprecated calldata creation - using function + arguments - function"}
, {create_calldata_args, undefined, "calldata_args", string,
"Deprecated calldata creation - using function + arguments - arguments"}
, {outfile, $o, "out", string, "Output the result to file (experimental)"} ]).
, {outfile, $o, "out", string, "Output file (experimental)"} ]).
usage() ->
getopt:usage(?OPT_SPEC, "aesophia").
@@ -20,14 +14,11 @@ usage() ->
main(Args) ->
case getopt:parse(?OPT_SPEC, Args) of
{ok, {Opts, []}} ->
IsHelp = proplists:get_value(help, Opts, false),
CreateCallData = proplists:get_value(create_calldata, Opts, undefined),
if IsHelp ->
usage();
CreateCallData /= undefined ->
create_calldata(CreateCallData, Opts);
case proplists:get_value(help, Opts, false) of
false ->
compile(Opts);
true ->
compile(Opts)
usage()
end;
{ok, {_, NonOpts}} ->
@@ -53,83 +44,28 @@ compile(File, Opts) ->
Verbose = proplists:get_value(verbose, Opts, false),
OutFile = proplists:get_value(outfile, Opts, undefined),
Res =
try aeso_compiler:file(File, [pp_ast || Verbose]) of
{ok, Map} ->
io:format("\nCompiled successfully!\n"),
{ok, Map};
{error, Reason} ->
io:format("\nError: ~p\n\n", [Reason]),
{error, Reason}
catch
error:Error ->
Where = hd(erlang:get_stacktrace()),
ErrorString = io_lib:format("Error: ~p in\n ~p", [Error, Where]),
io:format("~s\n", [ErrorString]),
{error, list_to_binary(lists:flatten(ErrorString))}
end,
write_outfile(OutFile, Res).
create_calldata(ContractFile, Opts) ->
case file:read_file(ContractFile) of
{ok, Bin} ->
try
Contract = binary_to_term(Bin),
create_calldata_(Contract, Opts)
catch _:_ ->
io:format("Error: Bad contract file ~s\n\n", [ContractFile]), usage()
end;
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [ContractFile]), usage()
end.
create_calldata_(Contract, Opts) ->
case proplists:get_value(src_file, Opts, undefined) of
undefined -> %% Check if old deprecated style is used
case {proplists:get_value(create_calldata_fun, Opts, undefined),
proplists:get_value(create_calldata_args, Opts, undefined)} of
{undefined, _} ->
io:format("Error: not enough create call data input\n\n"), usage();
{_, undefined} ->
io:format("Error: not enough create call data input\n\n"), usage();
{Fun, Args} ->
create_calldata(Contract, Fun, Args, Opts)
end;
CallFile ->
case file:read_file(CallFile) of
{ok, Bin} ->
create_calldata(Contract, "", binary_to_list(Bin), Opts);
{error, _} ->
io:format("Error: Could not find file ~s\n\n", [CallFile]), usage()
end
end.
create_calldata(Contract, CallFun, CallArgs, Opts) ->
OutFile = proplists:get_value(outfile, Opts, undefined),
Res = try
case aeso_compiler:create_calldata(Contract, CallFun, CallArgs) of
{ok, CallData, _CallDataType, _OutputType} ->
io:format("Call data created successfully!\n"),
{ok, CallData};
Err = {error, Reason} ->
io:format("Error: Create calldata failed: ~p\n\n", [Reason]),
Err
end
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("~s\n", [ErrorString]),
{error, list_to_binary(lists:flatten(ErrorString))}
end,
write_outfile(OutFile, Res).
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, Res) ->
write_outfile(Out, ResMap) ->
%% Lazy approach
file:write_file(Out, term_to_binary(Res)),
io:format("Output written to: ~s\n\n", [Out]).
file:write_file(Out, term_to_binary(ResMap)),
io:format("Output written to: ~s\n", [Out]).
-2
View File
@@ -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)
+13 -15
View File
@@ -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... */
-7
View File
@@ -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)