diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 725323d..57ad1e7 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -363,6 +363,8 @@ global_env() -> Pair = fun(A, B) -> {tuple_t, Ann, [A, B]} end, Fun = fun(Ts, T) -> {type_sig, Ann, [], Ts, T} end, Fun1 = fun(S, T) -> Fun([S], T) end, + %% Lambda = fun(Ts, T) -> {fun_t, Ann, [], Ts, T} end, + %% Lambda1 = fun(S, T) -> ArgFun([S], T) end, StateFun = fun(Ts, T) -> {type_sig, [stateful|Ann], [], Ts, T} end, TVar = fun(X) -> {tvar, Ann, "'" ++ X} end, SignId = {id, Ann, "signature"}, @@ -1074,6 +1076,23 @@ infer_expr(Env, {list, As, Elems}) -> ElemType = fresh_uvar(As), NewElems = [check_expr(Env, X, ElemType) || X <- Elems], {typed, As, {list, As, NewElems}, {app_t, As, {id, As, "list"}, [ElemType]}}; +infer_expr(Env, {list_comp, As, Yield, []}) -> + {typed, _, TypedYield, Type} = infer_expr(Env, Yield), + {typed, As, {list_comp, As, TypedYield, []}, {app_t, As, {id, As, "list"}, [Type]}}; +infer_expr(Env, {list_comp, As, Yield, [{comprehension_bind, Arg, BExpr}|Rest]}) -> + BindVarType = fresh_uvar(As), + TypedBind = {typed, As2, _, TypeBExpr} = infer_expr(Env, BExpr), + unify( Env + , TypeBExpr + , {app_t, As, {id, As, "list"}, [BindVarType]} + , {list_comp, TypedBind, TypeBExpr, {app_t, As2, {id, As, "list"}, [BindVarType]}}), + NewE = bind_var(Arg, BindVarType, Env), + {typed, _, {list_comp, _, TypedYield, TypedRest}, ResType} = + infer_expr(NewE, {list_comp, As, Yield, Rest}), + { typed + , As + , {list_comp, As, TypedYield, [{comprehension_bind, {typed, Arg, BindVarType}, TypedBind}|TypedRest]} + , ResType}; infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), @@ -2253,6 +2272,13 @@ pp_when({check_expr, Expr, Inferred0, Expected0}) -> pp_when({checking_init_type, Ann}) -> io_lib:format("when checking that 'init' returns a value of type 'state' at ~s\n", [pp_loc(Ann)]); +pp_when({list_comp, BindExpr, Inferred0, Expected0}) -> + {Inferred, Expected} = instantiate({Inferred0, Expected0}), + io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n" + "against type \n~s\n", + [pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)] + ); + pp_when(unknown) -> "". -spec pp_why_record(why_record()) -> iolist(). diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 7ce42ab..b3363fd 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -360,7 +360,6 @@ make_let(Expr, Body) -> {'let', X, Expr, Body({var, X})} end. --spec expr_to_fcode(env(), aeso_syntax:expr()) -> fexpr(). expr_to_fcode(Env, {typed, _, Expr, Type}) -> expr_to_fcode(Env, Type, Expr); expr_to_fcode(Env, Expr) -> @@ -444,6 +443,14 @@ expr_to_fcode(Env, _Type, {list, _, Es}) -> lists:foldr(fun(E, L) -> {op, '::', [expr_to_fcode(Env, E), L]} end, nil, Es); +expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) -> + {op, '::', [expr_to_fcode(Env, Yield), nil]}; +expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, {typed, {id, _, Arg}, _}, BindExpr}|Rest]}) -> + Env1 = bind_var(Env, Arg), + Bind = {lam, [Arg], expr_to_fcode(Env1, {list_comp, As, Yield, Rest})}, + {funcall, resolve_fun(Env, ["List", "flat_map"]), [expr_to_fcode(Env, BindExpr), Bind]}; + + %% Conditionals expr_to_fcode(Env, _Type, {'if', _, Cond, Then, Else}) -> make_if(expr_to_fcode(Env, Cond), diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index e611f8f..f06b02d 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -519,6 +519,18 @@ ast_body({app,As,Fun,Args}, Icode) -> #funcall{function=ast_body(Fun, Icode), args=[ast_body(A, Icode) || A <- Args]} end; +ast_body({list_comp, _, Yield, []}, Icode) -> + #list{elems = [ast_body(Yield, Icode)]}; +ast_body({list_comp, As, Yield, [{comprehension_bind, {typed, Arg, ArgType}, BindExpr}|Rest]}, Icode) -> + #funcall + { function = #var_ref{ name = ["List", "flat_map"] } + , args = + [ #lambda{ args=[#arg{name = ast_id(Arg), type = ast_type(ArgType, Icode)}] + , body = ast_body({list_comp, As, Yield, Rest}, Icode) + } + , ast_body(BindExpr, Icode) + ] + }; ast_body({'if',_,Dec,Then,Else}, Icode) -> #ifte{decision = ast_body(Dec, Icode) ,then = ast_body(Then, Icode) diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index cdcb2c8..25fc23a 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -230,11 +230,15 @@ exprAtom() -> , {bool, keyword(false), false} , ?LET_P(Fs, brace_list(?LAZY_P(field_assignment())), record(Fs)) , {list, [], bracket_list(Expr)} + , ?RULE(keyword('['), Expr, tok('|'), comma_sep(?LAZY_P(comprehension_bind())), tok(']'), list_comp_e(_1, _2, _4)) , ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4)) , ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2)) ]) end). +comprehension_bind() -> + ?RULE(id(), tok('<-'), expr(), {comprehension_bind, _1, _3}). + arg_expr() -> ?LAZY_P( choice([ ?RULE(id(), tok('='), expr(), {named_arg, [], _1, _3}) @@ -481,6 +485,8 @@ fun_t(Domains, Type) -> tuple_e(_Ann, [Expr]) -> Expr; %% Not a tuple tuple_e(Ann, Exprs) -> {tuple, Ann, Exprs}. +list_comp_e(Ann, Expr, Binds) -> {list_comp, Ann, Expr, Binds}. + -spec parse_pattern(aeso_syntax:expr()) -> aeso_parse_lib:parser(aeso_syntax:pat()). parse_pattern({app, Ann, Con = {'::', _}, Es}) -> {app, Ann, Con, lists:map(fun parse_pattern/1, Es)}; @@ -527,8 +533,15 @@ expand_includes(AST, Opts) -> expand_includes([], Acc, _Opts) -> {ok, lists:reverse(Acc)}; expand_includes([{include, _, S = {string, _, File}} | AST], Acc, Opts) -> - case read_file(File, Opts) of - {ok, Bin} -> + case {read_file(File, Opts), maps:find(File, aeso_stdlib:stdlib())} of + {_, {ok, Lib}} -> + case string(Lib) of + {ok, AST1} -> + expand_includes(AST1 ++ AST, Acc, Opts); + Err = {error, _} -> + Err + end; + {{ok, Bin}, _} -> Opts1 = lists:keystore(src_file, 1, Opts, {src_file, File}), case string(binary_to_list(Bin), Opts1) of {ok, AST1} -> @@ -536,7 +549,7 @@ expand_includes([{include, _, S = {string, _, File}} | AST], Acc, Opts) -> Err = {error, _} -> Err end; - {error, _} -> + {{error, _}, _} -> {error, {get_pos(S), include_error, File}} end; expand_includes([E | AST], Acc, Opts) -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index ea76b42..e368354 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -235,6 +235,8 @@ type(Type, Options) -> -spec type(aeso_syntax:type()) -> doc(). type({fun_t, _, Named, Args, Ret}) -> follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret)); +type({type_sig, _, Named, Args, Ret}) -> + follow(hsep(tuple_type(Named ++ Args), text("=>")), type(Ret)); type({app_t, _, Type, []}) -> type(Type); type({app_t, _, Type, Args}) -> diff --git a/src/aeso_stdlib.erl b/src/aeso_stdlib.erl new file mode 100644 index 0000000..4cbc768 --- /dev/null +++ b/src/aeso_stdlib.erl @@ -0,0 +1,48 @@ +%%%------------------------------------------------------------------- +%%% @author Radosław Rowicki +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Standard library for Sophia +%%% @end +%%% Created : 6 July 2019 +%%% +%%%------------------------------------------------------------------- + +-module(aeso_stdlib). + +-export([stdlib/0]). + +stdlib() -> + maps:from_list( + [ {<<"List.aes">>, std_list()} + ] + ). + +std_list() -> +" +namespace List = + function length(l) = length_(l, 0) + private function length_(l, n) = switch(l) + [] => n + _::t => length_(t, n + 1) + + function foldr(cons, nil, l) = switch(l) + [] => nil + h::t => cons(h, foldr(cons, nil, t)) + + function foldl(rcons, acc, l) = switch(l) + [] => acc + h::t => foldl(rcons, rcons(acc, h), t) + + function reverse(l) = + foldr((el, cont) => (lst) => cont(el::lst), (x) => x, l)([]) + + function map(f, l) = map_(f, l, []) + private function map_(f, l, acc) = switch(l) + [] => reverse(acc) + h::t => map_(f, t, f(h)::acc) + + function flat_map(f, l) = switch(l) + [] => [] + h::t => f(h) ++ flat_map(f, t) +". diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 5c9e81c..e40ae44 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -92,6 +92,7 @@ | {proj, ann(), expr(), id()} | {tuple, ann(), [expr()]} | {list, ann(), [expr()]} + | {list_comp, ann(), expr(), [comprehension_bind()]} | {typed, ann(), expr(), type()} | {record, ann(), [field(expr())]} | {record, ann(), expr(), [field(expr())]} %% record update @@ -104,6 +105,8 @@ | id() | qid() | con() | qcon() | constant(). +-type comprehension_bind() :: [{comprehension_bind, ann(), id(), type()}]. + -type arg_expr() :: expr() | {named_arg, ann(), id(), expr()}. %% When lvalue is a projection this is sugar for accessing fields in nested diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 911e635..36af710 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -118,7 +118,8 @@ compilable_contracts() -> "namespace_bug", "bytes_to_x", "aens", - "tuple_match" + "tuple_match", + "list_comp" ]. not_yet_compilable(fate) -> []; diff --git a/test/contracts/deadcode.aes b/test/contracts/deadcode.aes index 450e52a..c94ef6d 100644 --- a/test/contracts/deadcode.aes +++ b/test/contracts/deadcode.aes @@ -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) diff --git a/test/contracts/list_comp.aes b/test/contracts/list_comp.aes new file mode 100644 index 0000000..de3359e --- /dev/null +++ b/test/contracts/list_comp.aes @@ -0,0 +1,16 @@ +include "List.aes" + +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() = [[":)", ":("], [":)", ":("]] diff --git a/test/contracts/multi_sig.aes b/test/contracts/multi_sig.aes index 028b621..cb1363f 100644 --- a/test/contracts/multi_sig.aes +++ b/test/contracts/multi_sig.aes @@ -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)) diff --git a/test/contracts/nodeadcode.aes b/test/contracts/nodeadcode.aes index 76f6f3b..b7df43f 100644 --- a/test/contracts/nodeadcode.aes +++ b/test/contracts/nodeadcode.aes @@ -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) diff --git a/test/contracts/reason/voting.re b/test/contracts/reason/voting.re index 5736350..6739af7 100644 --- a/test/contracts/reason/voting.re +++ b/test/contracts/reason/voting.re @@ -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); } diff --git a/test/contracts/reason/voting_test.re b/test/contracts/reason/voting_test.re index 85cd587..c0b7420 100644 --- a/test/contracts/reason/voting_test.re +++ b/test/contracts/reason/voting_test.re @@ -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); }; diff --git a/test/contracts/voting.aes b/test/contracts/voting.aes index 0154e28..d786f6d 100644 --- a/test/contracts/voting.aes +++ b/test/contracts/voting.aes @@ -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)