Added list comprehensions and standard List, Option, Func, Pair, and Triple library (#105)

* Added standard List library and list comprehensions

Added List library.

Flatmaps WIP

Fixed dependency in flat_map

fcode generation

Updated tests to use custom list lib

Added comprehension test

Added stdlib

sanity

Test

* Extended stdlib for lists. Added error message for redefinition of stdlibx

* Fixed type template

* Improved stdlib

* More functions

* Fixed cyclic includes

* Refixed imports and added few tests

* Added fail test

* Undelete removed type spec

* Remove typo

* Fix iter function

* Fixed typo

* Added if guards and let statements in list comp

* Added more fail tests

* Option stliv

* 2 and 3 tuple stdlib

* Updated stdlib to new syntax. Added recursor and changed all/any functions

* Fixed performance issues. Changed include management

* Fixed hash type
This commit is contained in:
Radosław Rowicki
2019-08-14 13:53:58 +02:00
committed by Hans Svensson
parent 69ad8ce9bc
commit b669d2df1e
29 changed files with 760 additions and 57 deletions
+7 -7
View File
@@ -80,11 +80,11 @@ encode_decode_sophia_string(SophiaType, String) ->
, " record r = {x : an_alias(int), y : variant}\n"
, " datatype variant = Red | Blue(map(string, int))\n"
, " entrypoint foo : arg_type => arg_type\n" ],
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_implicit_stdlib]) of
{ok, _, {[Type], _}, [Arg]} ->
io:format("Type ~p~n", [Type]),
Data = encode(Arg),
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_implicit_stdlib]) of
{ok, Sophia} ->
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
{error, Err} ->
@@ -152,7 +152,7 @@ oracle_test() ->
" Oracle.get_question(o, q)\n",
{ok, _, {[word, word], {list, string}}, [16#123, 16#456]} =
aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9",
"oq_1111111111111111111111111111113AFEFpt5"], []),
"oq_1111111111111111111111111111113AFEFpt5"], [no_implicit_stdlib]),
ok.
@@ -162,7 +162,7 @@ permissive_literals_fail_test() ->
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
" Chain.spend(o, 1000000)\n",
{error, <<"Type errors\nCannot unify", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
aeso_compiler:check_call(Contract, "haxx", ["#123"], [no_implicit_stdlib]),
ok.
encode_decode_calldata(FunName, Types, Args) ->
@@ -173,8 +173,8 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
encode_decode_calldata_(Code, FunName, Args, RetType).
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]),
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, [no_implicit_stdlib]),
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_implicit_stdlib]),
?assertEqual(RetType, RetVMType),
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
@@ -182,7 +182,7 @@ encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
"init" ->
ok;
_ ->
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata),
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, [no_implicit_stdlib]),
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
?assertMatch({X, X}, {Args, Values})
end,
+3 -2
View File
@@ -9,7 +9,7 @@ simple_aci_test_() ->
test_contract(N) ->
{Contract,MapACI,DecACI} = test_cases(N),
{ok,JSON} = aeso_aci:contract_interface(json, Contract),
{ok,JSON} = aeso_aci:contract_interface(json, Contract, [no_implicit_stdlib]),
?assertEqual([MapACI], JSON),
?assertEqual({ok, DecACI}, aeso_aci:render_aci_json(JSON)).
@@ -87,7 +87,8 @@ aci_test_() ->
fun() -> aci_test_contract(ContractName) end}
|| ContractName <- all_contracts()].
all_contracts() -> aeso_compiler_tests:compilable_contracts().
all_contracts() -> [C || C <- aeso_compiler_tests:compilable_contracts()
, not aeso_compiler_tests:wants_stdlib(C)].
aci_test_contract(Name) ->
String = aeso_test_utils:read_contract(Name),
+8 -4
View File
@@ -21,12 +21,14 @@ calldata_test_() ->
ContractString = aeso_test_utils:read_contract(ContractName),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
@@ -45,12 +47,14 @@ calldata_aci_test_() ->
io:format("ACI:\n~s\n", [ContractACIBin]),
AevmExprs =
case not lists:member(ContractName, not_yet_compilable(aevm)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]);
true -> ast_exprs(ContractACI, Fun, Args, [{backend, aevm}]
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined
end,
FateExprs =
case not lists:member(ContractName, not_yet_compilable(fate)) of
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]
++ [no_implicit_stdlib || not aeso_compiler_tests:wants_stdlib(ContractName)]);
false -> undefined
end,
case FateExprs == undefined orelse AevmExprs == undefined of
+28 -2
View File
@@ -73,7 +73,9 @@ check_errors(Expect, ErrorString) ->
end.
compile(Backend, Name) ->
compile(Backend, Name, [{include, {file_system, [aeso_test_utils:contract_path()]}}]).
compile(Backend, Name,
[{include, {file_system, [aeso_test_utils:contract_path()]}}]
++ [no_implicit_stdlib || not wants_stdlib(Name)]).
compile(Backend, Name, Options) ->
String = aeso_test_utils:read_contract(Name),
@@ -118,7 +120,12 @@ compilable_contracts() ->
"namespace_bug",
"bytes_to_x",
"aens",
"tuple_match"
"tuple_match",
"cyclic_include",
"stdlib_include",
"double_include",
"manual_stdlib_include",
"list_comp"
].
not_yet_compilable(fate) -> [];
@@ -354,4 +361,23 @@ failing_contracts() ->
<<"Use 'entrypoint' for declaration of foo (at line 6, column 3):\n entrypoint foo : () => unit">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 10, column 3):\n entrypoint foo() = ()">>,
<<"Use 'entrypoint' instead of 'function' for public function foo (at line 6, column 3):\n entrypoint foo : () => unit">>]}
, {"list_comp_not_a_list",
[<<"Cannot unify int\n and list('a)\nwhen checking rvalue of list comprehension binding at line 2, column 36\n 1 : int\nagainst type \n list('a)">>
]}
, {"list_comp_if_not_bool",
[<<"Cannot unify int\n and bool\nwhen checking the type of the expression at line 2, column 44\n 3 : int\nagainst the expected type\n bool">>
]}
, {"list_comp_bad_shadow",
[<<"Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">>
]}
].
wants_stdlib(Name) ->
lists:member
(Name,
[ "stdlib_include",
"list_comp",
"list_comp_not_a_list",
"list_comp_if_not_bool",
"list_comp_bad_shadow"
]).
+8 -6
View File
@@ -15,7 +15,7 @@ simple_contracts_test_() ->
?assertMatch(
[{contract, _, {con, _, "Identity"},
[{letfun, _, {id, _, "id"}, [{arg, _, {id, _, "x"}, {id, _, "_"}}], {id, _, "_"},
{id, _, "x"}}]}], parse_string(Text)),
{id, _, "x"}}]}], parse_string(Text, [no_implicit_stdlib])),
ok
end},
{"Operator precedence test.",
@@ -71,21 +71,23 @@ parse_contract(Name) ->
roundtrip_contract(Name) ->
round_trip(aeso_test_utils:read_contract(Name)).
parse_string(Text) ->
case aeso_parser:string(Text) of
parse_string(Text) -> parse_string(Text, []).
parse_string(Text, Opts) ->
case aeso_parser:string(Text, Opts) of
{ok, Contract} -> Contract;
Err -> error(Err)
end.
parse_expr(Text) ->
[{letval, _, _, _, Expr}] =
parse_string("let _ = " ++ Text),
parse_string("let _ = " ++ Text, [no_implicit_stdlib]),
Expr.
round_trip(Text) ->
Contract = parse_string(Text),
Contract = parse_string(Text, [no_implicit_stdlib]),
Text1 = prettypr:format(aeso_pretty:decls(Contract)),
Contract1 = parse_string(Text1),
Contract1 = parse_string(Text1, [no_implicit_stdlib]),
NoSrcLoc = remove_line_numbers(Contract),
NoSrcLoc1 = remove_line_numbers(Contract1),
?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)).
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_forth.aes"
contract CI =
entrypoint ci() = Back.back() + Forth.forth()
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_forth.aes"
namespace Back =
function back() = 2
+4
View File
@@ -0,0 +1,4 @@
include "cyclic_include_back.aes"
namespace Forth =
function forth() = 3
+3 -3
View File
@@ -1,5 +1,5 @@
namespace List =
namespace MyList =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
@@ -14,8 +14,8 @@ namespace List =
contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
MyList.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
MyList.map1((x) => x + 1, xs)
+7
View File
@@ -0,0 +1,7 @@
include "included.aes"
include "../contracts/included.aes"
contract Include =
entrypoint foo() =
Included.foo()
+23
View File
@@ -0,0 +1,23 @@
contract ListComp =
entrypoint sample1() = [1,2,3]
entrypoint sample2() = [4,5]
entrypoint l1() = [x | x <- sample1()]
entrypoint l1_true() = [1,2,3]
entrypoint l2() = [x + y | x <- sample1(), y <- sample2()]
entrypoint l2_true() = [5,6,6,7,7,8]
entrypoint l3() = [x ++ y | x <- [[":)"] | x <- [1,2]]
, y <- [[":("]]]
entrypoint l3_true() = [[":)", ":("], [":)", ":("]]
entrypoint l4() = [(a, b, c) | let is_pit(a, b, c) = a*a + b*b == c*c
, let base = [1,2,3,4,5,6,7,8,9,10]
, a <- base
, b <- base, if (b >= a)
, c <- base, if (c >= b)
, if (is_pit(a, b, c))
]
entrypoint l4_true() = [(3, 4, 5), (6, 8, 10)]
+2
View File
@@ -0,0 +1,2 @@
contract BadComp =
entrypoint failing() = [x + 1 | x <- [1,2,3], let x = "XD"]
+2
View File
@@ -0,0 +1,2 @@
contract BadComp =
entrypoint failing() = [x | x <- [], if (3)]
+2
View File
@@ -0,0 +1,2 @@
contract ListCompBad =
entrypoint failing() = [x | x <- 1]
+7
View File
@@ -0,0 +1,7 @@
// This contract should be compiled with no_implicit_stdlib option.
// It should include Lists.aes implicitly however, because Option.aes depends on it.
include "Option.aes"
contract Test =
entrypoint i_should_build() =
List.is_empty(Option.to_list(None))
+2 -2
View File
@@ -28,8 +28,8 @@ contract MultiSig =
let n = length(owners) + 1
{ nRequired = nRequired,
nOwners = n,
owners = Map.from_list(List.zip([1..n], caller() :: owners)),
ownerIndex = Map.from_list(List.zip(caller() :: owners, [1..n])) }
owners = Map.from_list(MyList.zip([1..n], caller() :: owners)),
ownerIndex = Map.from_list(MyList.zip(caller() :: owners, [1..n])) }
function lookup(map, key) =
switch(Map.get(key, map))
+3 -3
View File
@@ -1,5 +1,5 @@
namespace List =
namespace MyList =
function map1(f : 'a => 'b, xs : list('a)) =
switch(xs)
@@ -14,8 +14,8 @@ namespace List =
contract Deadcode =
entrypoint inc1(xs : list(int)) : list(int) =
List.map1((x) => x + 1, xs)
MyList.map1((x) => x + 1, xs)
entrypoint inc2(xs : list(int)) : list(int) =
List.map2((x) => x + 1, xs)
MyList.map2((x) => x + 1, xs)
+3 -3
View File
@@ -47,7 +47,7 @@ module Voting : Voting = {
let init(proposalNames: args): state =
{ chairPerson: caller(),
voters: AddrMap.empty,
proposals: List.map((name) => {name: name, voteCount: 0}, proposalNames)
proposals: MyList.map((name) => {name: name, voteCount: 0}, proposalNames)
};
/* Boilerplate */
@@ -73,7 +73,7 @@ module Voting : Voting = {
};
let addVote(candidate, weight) = {
let proposal = List.nth(state().proposals, candidate);
let proposal = MyList.nth(state().proposals, candidate);
proposal.voteCount = proposal.voteCount + weight;
};
@@ -121,6 +121,6 @@ module Voting : Voting = {
/* const */
let currentTally() =
List.map((p) => (p.name, p.voteCount), state().proposals);
MyList.map((p) => (p.name, p.voteCount), state().proposals);
}
+1 -1
View File
@@ -13,7 +13,7 @@ open Voting;
let print_tally() = {
let tally = call(other, () => currentTally());
List.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally);
MyList.map(((name, count)) => Printf.printf("%s: %d\n", name, count), tally);
let winner = call(other, () => winnerName());
Printf.printf("Winner: %s\n", winner);
};
+3
View File
@@ -0,0 +1,3 @@
contract StdInc =
entrypoint test() = List.map((x) => Func.id(x), [1,2,3,4])
+3 -3
View File
@@ -36,7 +36,7 @@ contract Voting =
function init(proposalNames: list(string)): state =
{ chairPerson = caller(),
voters = Map.empty,
proposals = List.map((name) => {name = name, voteCount = 0}, proposalNames) }
proposals = MyList.map((name) => {name = name, voteCount = 0}, proposalNames) }
function initVoter() = { weight = 1, vote = NotVoted}
@@ -53,7 +53,7 @@ contract Voting =
_ => delegate
function addVote(candidate, weight) =
let proposal = List.nth(state.proposals, candidate)
let proposal = MyList.nth(state.proposals, candidate)
proposal{ voteCount = proposal.voteCount + weight }
function delegateVote(delegateTo: address, weight: uint) =
@@ -93,5 +93,5 @@ contract Voting =
// const
function currentTally() =
List.map((p) => (p.name, p.voteCount), state.proposals)
MyList.map((p) => (p.name, p.voteCount), state.proposals)