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
This commit is contained in:
parent
df12f6af91
commit
18235e5b56
@ -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().
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
|
@ -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) ->
|
||||
|
@ -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}) ->
|
||||
|
48
src/aeso_stdlib.erl
Normal file
48
src/aeso_stdlib.erl
Normal file
@ -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)
|
||||
".
|
@ -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
|
||||
|
@ -118,7 +118,8 @@ compilable_contracts() ->
|
||||
"namespace_bug",
|
||||
"bytes_to_x",
|
||||
"aens",
|
||||
"tuple_match"
|
||||
"tuple_match",
|
||||
"list_comp"
|
||||
].
|
||||
|
||||
not_yet_compilable(fate) -> [];
|
||||
|
@ -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)
|
||||
|
||||
|
16
test/contracts/list_comp.aes
Normal file
16
test/contracts/list_comp.aes
Normal file
@ -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() = [[":)", ":("], [":)", ":("]]
|
@ -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))
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user