File 1831-compiler-Add-zip-generators-for-comprehensions.patch of Package erlang
From 899322d9f8814900b97b362bab09b2c0e85cd62b Mon Sep 17 00:00:00 2001
From: lucioleKi <isabell@erlang.org>
Date: Fri, 16 Aug 2024 09:10:26 +0200
Subject: [PATCH] compiler: Add zip generators for comprehensions
We introduce zip generators for comprehensions to reduce the need for
users to use lists:zip.
Zip generators have the syntax of generator1 && ... && generatorN,
where each generator can be a list, binary, or map generator.
Zip generators are evaluated as if they are zipped to be a list of
tuples. They can be mixed with all existing generators and filters
freely.
Two examples to show how zip generators is used and evaluated:
1> [{X,Y} || X <- [a,b,c] && Y <- [1,2,3]].
[{a,1},{b,2},{c,3}]
2> << <<(X+Y)/integer>> || X <- [1,2,3] && <<Y>> <= <<1,1,1>>, X < 3 >>.
<<2,3>>
---
erts/doc/guides/absform.md | 3 +
lib/compiler/src/sys_coverage.erl | 6 +
lib/compiler/src/v3_core.erl | 251 +++++++++-
lib/compiler/test/Makefile | 4 +-
lib/compiler/test/zlc_SUITE.erl | 452 ++++++++++++++++++
lib/debugger/src/dbg_ieval.erl | 285 +++++++++++
lib/debugger/src/dbg_iload.erl | 20 +-
lib/debugger/src/debugger.app.src | 2 +-
lib/debugger/test/Makefile | 1 +
lib/debugger/test/erl_eval_SUITE.erl | 160 ++++++-
lib/debugger/test/zlc_SUITE.erl | 410 ++++++++++++++++
lib/stdlib/examples/erl_id_trans.erl | 3 +
lib/stdlib/src/erl_error.erl | 2 +
lib/stdlib/src/erl_eval.erl | 337 ++++++++++++-
lib/stdlib/src/erl_expand_records.erl | 4 +
lib/stdlib/src/erl_lint.erl | 40 ++
lib/stdlib/src/erl_parse.yrl | 15 +-
lib/stdlib/src/erl_pp.erl | 2 +
lib/stdlib/src/erl_scan.erl | 8 +-
lib/stdlib/test/erl_eval_SUITE.erl | 161 ++++++-
lib/stdlib/test/erl_expand_records_SUITE.erl | 13 +-
lib/stdlib/test/erl_lint_SUITE.erl | 24 +-
lib/syntax_tools/src/erl_prettypr.erl | 6 +
lib/syntax_tools/src/erl_syntax.erl | 58 ++-
lib/syntax_tools/src/erl_syntax_lib.erl | 32 ++
lib/syntax_tools/test/syntax_tools_SUITE.erl | 6 +
.../syntax_tools_SUITE_test_module.erl | 9 +-
lib/tools/emacs/erlang.el | 4 +-
28 files changed, 2277 insertions(+), 41 deletions(-)
create mode 100644 lib/compiler/test/zlc_SUITE.erl
create mode 100644 lib/debugger/test/zlc_SUITE.erl
diff --git a/erts/doc/guides/absform.md b/erts/doc/guides/absform.md
index 716dd132eb..1f814d8d48 100644
--- a/erts/doc/guides/absform.md
+++ b/erts/doc/guides/absform.md
@@ -292,6 +292,9 @@ An expression E is one of the following:
A qualifier Q is one of the following:
- If Q is a filter `E`, where `E` is an expression, then Rep(Q) = `Rep(E)`.
+- If Q is a zip generator `Q_1 && ...&& Q_k]`, where each `Q_i` is
+ a non-zip generator, then Rep(E) = `{zip,ANNO,[Rep(Q_1), ..., Rep(Q_k)]}`.
+ For Rep(Q), see below.
- If Q is a list generator `P <- E`, where `P` is a pattern and `E` is an
expression, then Rep(Q) = `{generate,ANNO,Rep(P),Rep(E)}`.
- If Q is a list generator `P <:- E`, where `P` is a pattern and `E` is an
diff --git a/lib/compiler/src/sys_coverage.erl b/lib/compiler/src/sys_coverage.erl
index 9ad9899304..b3515f83e8 100644
--- a/lib/compiler/src/sys_coverage.erl
+++ b/lib/compiler/src/sys_coverage.erl
@@ -573,6 +573,12 @@ munge_qs([{m_generate_strict,Anno,Pattern,Expr}|Qs], Vars0, MQs) ->
A = element(2, Expr),
{MExpr, Vars1} = munge_expr(Expr, Vars0),
munge_qs1(Qs, A, {m_generate_strict,Anno,Pattern,MExpr}, Vars0, Vars1, MQs);
+munge_qs([{zip,Anno,Gs0}|Qs], Vars0, MQs) ->
+ {Gs1, Vars1} = munge_qualifiers(Gs0, Vars0),
+ %% Get rid of dummy filters inserted by munge_qualifiers/2 --
+ %% they are not allowed in the zip construct.
+ Gs = [G || G <- Gs1, element(1, G) =/= block],
+ munge_qs1(Qs, Anno, {zip,Anno,Gs}, Vars0, Vars1, MQs);
munge_qs([Expr|Qs], Vars0, MQs) ->
A = element(2, Expr),
{MungedExpr, Vars1} = munge_expr(Expr, Vars0),
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 448b321326..1acd6ed9b0 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -82,9 +82,12 @@
-export([module/2,format_error/1]).
--import(lists, [any/2,reverse/1,reverse/2,map/2,member/2,foldl/3,foldr/3,mapfoldl/3,
- splitwith/2,keydelete/3,keyfind/3,keymember/3,sort/1,droplast/1,last/1,
- duplicate/2]).
+-import(lists, [all/2,any/2,append/1,droplast/1,duplicate/2,
+ foldl/3,foldr/3,
+ keydelete/3,keyfind/3,keymember/3,
+ last/1,map/2,member/2,mapfoldl/3,
+ reverse/1,reverse/2,
+ split/2,splitwith/2,sort/1]).
-import(ordsets, [add_element/2,del_element/2,is_element/2,
union/1,union/2,intersection/2,subtract/2]).
-import(cerl, [ann_c_cons/3,ann_c_tuple/2,c_tuple/1,
@@ -124,6 +127,10 @@
nomatch_pat,nomatch_mode,
tail,tail_pat,arg,
refill={nomatch,ignore}}).
+-record(izip, {anno=#a{},acc_pats=[],acc_guard,
+ nomatch_pats=[],nomatch_total=[],skip_pats=[],
+ tails=[],tail_pats=[],pres=[],args=[],
+ refill_pats=[],refill_as=[]}).
-record(isimple, {anno=#a{},term :: cerl:cerl()}).
-type iapply() :: #iapply{}.
@@ -144,13 +151,14 @@
-type itry() :: #itry{}.
-type ifilter() :: #ifilter{}.
-type igen() :: #igen{}.
+-type izip() :: #izip{}.
-type isimple() :: #isimple{}.
-type i() :: iapply() | ibinary() | icall() | icase() | icatch()
| iclause() | ifun() | iletrec() | imatch() | imap()
| iprimop() | iprotect() | ireceive1() | ireceive2()
| iset() | itry() | ifilter()
- | igen() | isimple().
+ | igen() | izip() | isimple().
-type warning() :: {file:filename(), [{integer(), module(), term()}]}.
@@ -1602,7 +1610,20 @@ fun_tq(Cs0, L, St0, NameInfo) ->
%% lc_tq(Line, Exp, [Qualifier], Mc, State) -> {LetRec,[PreExp],State}.
%% This TQ from Simon PJ pp 127-138.
-lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
+lc_tq(Line, E, [#igen{}|_T] = Qs, Mc, St) ->
+ lc_tq1(Line, E, Qs, Mc, St);
+lc_tq(Line, E, [#izip{}=Zip|Qs], Mc, St) ->
+ zip_tq(Line, E, Zip, Mc, St, Qs);
+lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
+ filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
+lc_tq(Line, E0, [], Mc0, St0) ->
+ {H1,Hps,St1} = safe(E0, St0),
+ {T1,Tps,St} = force_safe(Mc0, St1),
+ Anno = lineno_anno(Line, St),
+ E = ann_c_cons(Anno, H1, T1),
+ {set_anno(E, [compiler_generated|Anno]),Hps ++ Tps,St}.
+
+lc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
acc_pat=AccPat,acc_guard=AccGuard,
nomatch_pat=NomatchPat,
nomatch_mode=NomatchMode,
@@ -1645,15 +1666,61 @@ lc_tq(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
Fun = #ifun{anno=GAnno,id=[],vars=[Var],clauses=Cs,fc=Fc},
{#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},defs=[{{Name,1},Fun}],
body=Pre ++ [#iapply{anno=GAnno,op=F,args=[Arg]}]},
- [],St3};
-lc_tq(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
- filter_tq(Line, E, Filter, Mc, St, Qs, fun lc_tq/5);
-lc_tq(Line, E0, [], Mc0, St0) ->
- {H1,Hps,St1} = safe(E0, St0),
- {T1,Tps,St} = force_safe(Mc0, St1),
- Anno = lineno_anno(Line, St),
- E = ann_c_cons(Anno, H1, T1),
- {set_anno(E, [compiler_generated|Anno]),Hps ++ Tps,St}.
+ [],St3}.
+
+%% zip_tq(Line, Exp, [Qualifier], Mc, State, TqFun) -> {LetRec,[PreExp],State}.
+zip_tq(Line, E, #izip{anno=#a{anno=GA}=GAnno,
+ acc_pats=AccPats,acc_guard=AccGuard,
+ nomatch_total=NomatchTotal,
+ skip_pats=SkipPats,
+ tails=TailVars,tail_pats=TailPats,
+ refill_pats=RefillPats0,
+ refill_as=RefillAs,pres=Pres,args=Args}, Mc, St0, Qs) ->
+ {Name,St1} = new_fun_name("zlc", St0),
+ LA = lineno_anno(Line, St1),
+ NumGenerators = length(AccPats),
+
+ %% Generate new vars for each generator, 1 for the regular call, and 1 for
+ %% the bad generator case.
+ {CallVars,St2} = new_vars(NumGenerators, St1),
+ {FcVars, St3} = new_vars(NumGenerators, St2),
+
+ %% Generate the name for the letrec.
+ F = #c_var{anno=LA,name={Name,NumGenerators}},
+
+ %% Generate the clauses for the letrec. First, the accumulating
+ %% clause.
+ Sc = #iapply{anno=GAnno,op=F,args=TailVars},
+ {Lc,Lps,St4} = lc_tq(Line, E, Qs, Sc, St3),
+ AccClause = make_clause(LA, AccPats, AccGuard, Lps++[Lc]),
+
+ %% Generate the skip clause unless all generators are strict, in
+ %% which case no skipping is possible.
+ AccClauseNoGuards =
+ case NomatchTotal of
+ strict ->
+ nomatch;
+ _ ->
+ make_clause([skip_clause,compiler_generated|LA],
+ SkipPats, [], [Sc])
+ end,
+
+ %% Generate the clause testing for empty generators.
+ TailClause = make_clause(LA, TailPats, [], [Mc]),
+
+ %% Generate refill clauses for map generators.
+ RefillClauses = make_refill(RefillPats0, 0, RefillAs, {TailVars, LA, [], Sc}),
+
+ %% Gather clauses.
+ Cs0 = [AccClause, AccClauseNoGuards, TailClause | RefillClauses],
+ Cs = [C || C <- Cs0, C =/= nomatch],
+
+ Fc = bad_generators(FcVars, hd(Args), lc, bad_generators),
+ Fun = #ifun{anno=GAnno,id=[],vars=CallVars,clauses=Cs,fc=Fc},
+ {#iletrec{anno=GAnno#a{anno=[list_comprehension|GA]},
+ defs=[{{Name,NumGenerators},Fun}],
+ body=append(Pres) ++
+ [#iapply{anno=GAnno,op=F,args=Args}]},[],St4}.
%% bc_tq(Line, Exp, [Qualifier], More, State) -> {LetRec,[PreExp],State}.
%% This TQ from Gustafsson ERLANG'05.
@@ -1729,6 +1796,8 @@ bc_tq1(Line, E, [#igen{anno=#a{anno=GA}=GAnno,
defs=[{{Name,2},Fun}],
body=Pre ++ [#iapply{anno=LAnno,op=F,args=[Arg,Mc]}]},
[],St5};
+bc_tq1(Line, E, [#izip{}=Zip|Qs], Mc, St) ->
+ bzip_tq1(Line, E, Zip, Mc, St, Qs);
bc_tq1(Line, E, [#ifilter{}=Filter|Qs], Mc, St) ->
filter_tq(Line, E, Filter, Mc, St, Qs, fun bc_tq1/5);
bc_tq1(_, {bin,Bl,Elements}, [], AccVar, St0) ->
@@ -1765,6 +1834,66 @@ bc_tq_build(Line, Pre0, #c_var{name=AccVar}, Elements0, St0) ->
Anno = Anno0#a{anno=[compiler_generated,single_use|A]},
{set_anno(E, Anno),Pre0++Pre,St}.
+bzip_tq1(Line, E, #izip{anno=#a{anno=_GA}=GAnno,
+ acc_pats=AccPats,acc_guard=AccGuard,
+ nomatch_total=NomatchTotal,
+ skip_pats=SkipPats,
+ tails=TailVars,tail_pats=TailPats,
+ refill_pats=RefillPats0,
+ refill_as=RefillAs,pres=Pres,args=Args}, Mc, St0, Qs) ->
+ {Name,St1} = new_fun_name("bzip", St0),
+ LA = lineno_anno(Line, St1),
+ LAnno = #a{anno=LA},
+ Arity = length(AccPats) + 1,
+
+ %% Generate new vars for each generator, 1 for the regular call, and 1 for
+ %% the bad generator case. last(CallVars) is used as the accumulator var
+ %% when constructing the new binary.
+ {CallVars, St2} = new_vars(LA, Arity, St1),
+ {FcVars, St3} = new_vars(LA, Arity, St2),
+
+ %% Generate the name for the letrec.
+ F = #c_var{anno=LA,name={Name,Arity}},
+
+ %% Generate the clauses for the letrec. First, the accumulating
+ %% clause.
+ BinAccVar = last(CallVars),
+ Sc = #iapply{anno=GAnno,op=F,args=TailVars++[BinAccVar]},
+ {Bc,Bps,St4} = bc_tq1(Line, E, Qs, BinAccVar, St3),
+ Body = Bps++[#iset{var=hd(CallVars), arg=Bc}, Sc],
+ AccClause = make_clause(LA, AccPats++[Mc], AccGuard, Body),
+
+ %% Generate the skip clause unless all generators are strict, in
+ %% which case no skipping is possible.
+ AccClauseNoGuards =
+ case NomatchTotal of
+ strict ->
+ nomatch;
+ _ ->
+ make_clause([skip_clause,compiler_generated|LA],
+ SkipPats++[Mc], [], [Sc])
+ end,
+
+ %% Generate the clause testing for empty generators.
+ TailClause = make_clause(LA, TailPats++[Mc], [], [Mc]),
+
+ %% Generate refill clauses for map generators.
+ RefillClauses = make_refill(RefillPats0, 0, RefillAs, {TailVars, LA, [Mc], Sc}),
+
+ %% Gather clauses.
+ Cs0 = [AccClause, AccClauseNoGuards, TailClause | RefillClauses],
+ Cs = [C || C <- Cs0, C =/= nomatch],
+
+ Fc = bad_generators(FcVars, hd(Args), bc, bad_generators),
+ Fun = #ifun{anno=GAnno,id=[],vars=CallVars,clauses=Cs,fc=Fc},
+
+ %% Inlining would disable the size calculation optimization for
+ %% bs_init_writable.
+ {#iletrec{anno=LAnno#a{anno=[list_comprehension,no_inline|LA]},
+ defs=[{{Name,Arity},Fun}],
+ body=append(Pres) ++
+ [#iapply{anno=LAnno,op=F,args=Args++[Mc]}]},[],St4}.
+
mc_tq(Line, {map_field_assoc,Lf,K,V}, Qs, Mc, St0) ->
E = {tuple,Lf,[K,V]},
{Lc,Pre0,St1} = lc_tq(Line, E, Qs, Mc, St0),
@@ -1775,6 +1904,19 @@ mc_tq(Line, {map_field_assoc,Lf,K,V}, Qs, Mc, St0) ->
args=[LcVar]},
{Call,Pre,St2}.
+make_refill([nomatch|RefillPats], Index, [_|Bodies], Args) ->
+ make_refill(RefillPats, Index + 1, Bodies, Args);
+make_refill([RefillPat0|RefillPats], Index, [RefillBody|Bodies], {TailVars, LA, Mc, Sc}=Args) ->
+ {H, [_|T]} = split(Index, TailVars),
+ RefillPat1 = H ++ [RefillPat0|T] ++ Mc,
+ RefillClause = make_clause(LA, RefillPat1, [], [RefillBody,Sc]),
+ [RefillClause|make_refill(RefillPats, Index + 1, Bodies, Args)];
+make_refill([], _Index, [], _Args) ->
+ [].
+
+make_clause(Anno, [Pat|PatExtra], Guard, Body) ->
+ make_clause(Anno, Pat, PatExtra, Guard, Body).
+
make_clause(_Anno, nomatch, _PatExtra, _Guard, _Body) ->
nomatch;
make_clause(Anno, Pat, PatExtra, Guard, Body) ->
@@ -1825,6 +1967,31 @@ filter_tq(Line, E, #ifilter{anno=#a{anno=LA}=LAnno,arg=Guard},
preprocess_quals(Line, Qs, St) ->
preprocess_quals(Line, Qs, St, []).
+preprocess_quals(Line, [{zip,Anno,Gens}|Qs], St, Acc) ->
+ LAnno = #a{anno=lineno_anno(Anno, St)},
+ {Gens1, St1} = preprocess_quals(Line, Gens, St, []),
+ [#igen{acc_guard=AccGuard}|_] = Gens1,
+ Zip0 = #izip{anno=LAnno,
+ acc_guard=AccGuard},
+ Zip1 = preprocess_zip_generators(Gens1, Zip0),
+ Zip2 = Zip1#izip{skip_pats=[case NomatchMode of
+ skip -> NomatchPat;
+ _ -> AccPat
+ end ||
+ {NomatchMode, NomatchPat, AccPat} <:-
+ lists:zip3(Zip1#izip.nomatch_total,
+ Zip1#izip.nomatch_pats,
+ Zip1#izip.acc_pats)],
+ tail_pats=[case {NomatchMode,AccPat} of
+ {skip,_} -> AccPat;
+ {_,#ibinary{}} -> AccPat#ibinary{segments=[]};
+ {_,_} -> AccPat
+ end ||
+ {NomatchMode, AccPat} <:-
+ lists:zip(Zip1#izip.nomatch_total,
+ Zip1#izip.tail_pats)],
+ nomatch_total=get_nomatch_total(Zip1#izip.nomatch_total)},
+ preprocess_quals(Line, Qs, St1, [Zip2|Acc]);
preprocess_quals(Line, [Q|Qs0], St0, Acc) ->
case is_generator(Q) of
true ->
@@ -1853,6 +2020,39 @@ preprocess_quals(Line, [Q|Qs0], St0, Acc) ->
preprocess_quals(_, [], St, Acc) ->
{reverse(Acc),St}.
+preprocess_zip_generators([#igen{}=Igen | Rest], #izip{}=Zip0) ->
+ Zip = preprocess_zip_generators(Rest, Zip0),
+
+ #igen{arg={Pre,Arg},
+ tail=Tail,
+ acc_pat=AccPat,
+ tail_pat=TailPat,
+ nomatch_mode=NomatchMode,
+ nomatch_pat=NomatchPat,
+ refill={RefillPat, RefillArg}} = Igen,
+
+ Zip#izip{acc_pats=[AccPat | Zip#izip.acc_pats],
+ tails=[Tail | Zip#izip.tails],
+ tail_pats=[TailPat | Zip#izip.tail_pats],
+ nomatch_total = [NomatchMode | Zip#izip.nomatch_total],
+ nomatch_pats = [NomatchPat | Zip#izip.nomatch_pats],
+ refill_pats=[RefillPat | Zip#izip.refill_pats],
+ refill_as=[RefillArg | Zip#izip.refill_as],
+ pres=[Pre | Zip#izip.pres],
+ args=[Arg | Zip#izip.args]};
+preprocess_zip_generators([], Zip) ->
+ Zip.
+
+get_nomatch_total(NomatchModes) ->
+ case all(fun(X) -> X =:= skip end, NomatchModes) of
+ true -> skip;
+ false ->
+ case any(fun(X) -> X =:= skip end, NomatchModes) of
+ true -> mixed;
+ false -> strict
+ end
+ end.
+
is_generator({generate,_,_,_}) -> true;
is_generator({generate_strict,_,_,_}) -> true;
is_generator({b_generate,_,_,_}) -> true;
@@ -2023,7 +2223,7 @@ generator(Line, {Generate,Lg,{map_field_exact,_,K0,V0},E}, Gs, St0) when
%% call 'erlang':'error'({'badmatch',{K,V}})
%% <'none'> when 'true' ->
%% []
- %% <Iter> when 'true' ->
+ %% <Iter=[_|_]> when 'true' ->
%% let NextIter =
%% call 'erts_internal':'mc_refill'(Iter)
%% in apply 'lc$^0'/1(NextIter)
@@ -2062,12 +2262,12 @@ generator(Line, {Generate,Lg,{map_field_exact,_,K0,V0},E}, Gs, St0) when
m_generate_strict ->
#c_tuple{es=[NomatchK,NomatchV]}
end,
- Refill = {NomatchK,
+ Refill = {ann_c_cons(GA,NomatchK,NomatchV),
#iset{var=IterVar,
arg=#icall{anno=#a{anno=GA},
module=#c_literal{val=erts_internal},
name=#c_literal{val=mc_refill},
- args=[NomatchK]}}},
+ args=[ann_c_cons(GA,NomatchK,NomatchV)]}}},
InitIter = #icall{anno=#a{anno=GA},
module=#c_literal{val=erts_internal},
@@ -2537,8 +2737,21 @@ new_vars_1(N, Anno, St0, Vs) when N > 0 ->
new_vars_1(0, _, St, Vs) -> {Vs,St}.
bad_generator(Ps, Generator, Arg) ->
+ L = [#c_literal{val=bad_generator}, Generator],
+ bad_generator_common(L, Ps, Arg).
+
+bad_generators(Ps, Arg, bc, ErrorType) ->
+ T1 = #c_tuple{es=droplast(Ps)},
+ L = [#c_literal{val=ErrorType}, T1],
+ bad_generator_common(L, Ps, Arg);
+bad_generators(Ps, Arg, lc, ErrorType) ->
+ T = #c_tuple{es=Ps},
+ L = [#c_literal{val=ErrorType}, T],
+ bad_generator_common(L, Ps, Arg).
+
+bad_generator_common(L, Ps, Arg) ->
Anno = get_anno(Arg),
- Tuple = ann_c_tuple(Anno, [#c_literal{val=bad_generator},Generator]),
+ Tuple = ann_c_tuple(Anno, L),
Call = #icall{anno=#a{anno=Anno}, %Must have an #a{}
module=#c_literal{anno=Anno,val=erlang},
name=#c_literal{anno=Anno,val=error},
@@ -4224,7 +4437,7 @@ is_simple(_) -> false.
-spec is_simple_list([cerl:cerl()]) -> boolean().
-is_simple_list(Es) -> lists:all(fun is_simple/1, Es).
+is_simple_list(Es) -> all(fun is_simple/1, Es).
insert_nif_start([VF={V,F=#c_fun{body=Body}}|Funs]) ->
case Body of
diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile
index 9ad903f0a2..dc3cbe128b 100644
--- a/lib/compiler/test/Makefile
+++ b/lib/compiler/test/Makefile
@@ -52,6 +52,7 @@ MODULES= \
trycatch_SUITE \
warnings_SUITE \
z_SUITE \
+ zlc_SUITE \
test_lib
NO_OPT= \
@@ -87,7 +88,8 @@ NO_OPT= \
overridden_bif \
receive \
record \
- trycatch
+ trycatch \
+ zlc
INLINE= \
andor \
diff --git a/lib/compiler/test/zlc_SUITE.erl b/lib/compiler/test/zlc_SUITE.erl
new file mode 100644
index 0000000000..eb043d79a5
--- /dev/null
+++ b/lib/compiler/test/zlc_SUITE.erl
@@ -0,0 +1,452 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2024. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(zlc_SUITE).
+
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ init_per_testcase/2,end_per_testcase/2,
+ basic/1,mixed_zlc/1,zmc/1,filter_guard/1,
+ filter_pattern/1,cartesian/1,nomatch/1,bad_generators/1,
+ strict_list/1,strict_binary/1]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ [{group,p}].
+
+groups() ->
+ [{p,test_lib:parallel(),
+ [basic,
+ mixed_zlc,
+ zmc,
+ filter_guard,
+ filter_pattern,
+ cartesian,
+ nomatch,
+ bad_generators,
+ strict_list,
+ strict_binary
+ ]}].
+
+init_per_suite(Config) ->
+ test_lib:recompile(?MODULE),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ Config.
+
+end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ ok.
+
+basic(Config) when is_list(Config) ->
+ [6, 7, 8] = [X + Y + Z || X <- [1, 2, 3] && Y <- [2, 2, 2] && Z <- [3,3,3]],
+ [{1, 2, 3}, {2, 2, 3}, {3, 2, 3}] =
+ [{X, Y, Z} || X <- [1, 2, 3] && Y <- [2, 2, 2] && Z <- [3,3,3]],
+ [6, 24] = zipwith4(fun(A, B, C, D) -> (A + B + C) * D end,
+ [1, 5], [2, 2], [0, 1], [2, 3]),
+ 96 = dot([1, 2, 3, 4], [24, 12, 8, 6]),
+
+ [1, 4, 5] = ifelse([true, false, true], [1, 3, 5], [2, 4, 6]),
+
+ [8, 14, 20] = [X + Y + Z || <<X>> <= <<5, 10, 15>> && <<Y>> <= <<1, 2, 3>>
+ && <<Z>> <= <<2, 2, 2>>].
+
+
+zipwith4(F, As, Bs, Cs, Ds) ->
+ [F(A,B,C,D) || A <- As && B <- Bs && C <- Cs && D <- Ds].
+
+dot(Xs, Ys) ->
+ lists:sum([X*Y || X <- Xs && Y <- Ys]).
+
+ifelse(Tests, Xs, Ys) ->
+ %% Simulate R's ifelse(,,)
+ [case T of
+ true -> X;
+ false -> Y
+ end || T <- Tests && X <- Xs && Y <- Ys
+ ].
+
+mixed_zlc(Config) when is_list(Config) ->
+ [{a, 2}, {b, 4}, {c, 6}] = [{X,Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>],
+ [{a, 2}, {b, 4}, {c, 6}] = [{X,Y} || <<Y>> <= <<2,4,6>> && X <- [a,b,c]],
+ [{a,c,1,3}, {b,d,2,4}] = [{K1,K2,V1,V2}||
+ K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ K2 := V2 <- maps:iterator(#{c=>3, d=>4}, ordered)],
+ [{a,1,2}, {b,2,4}] =
+ [{K1,V1,Y} || K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ <<Y>> <= <<2,4>>],
+ [{a,1,2}, {b,2,4}] = [{K1,V1,Y} ||
+ K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ <<Y>> <= <<2,4>>],
+ <<3,4,5>> = << <<(X+Y)/integer>> || X <- [1,2,3] && Y <- [2,2,2]>>,
+ <<3,4,5>> = << <<(X+V1)/integer>> ||
+ X <- [1,2,3] &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ <<3,4,5>> = << <<(X+V1)/integer>> ||
+ <<X>> <= <<1,2,3>> &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ <<3,4,5>> = << <<(V1+V2)/integer>> ||
+ _K1 := V1 <- maps:iterator(#{a=>1, b=>2, c=>3}, ordered) &&
+ _K2 := V2 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ #{c := 3,b := 2,a := 1} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3]},
+ #{c := 3,b := 2,a := 1} = #{X => Y || X <- [a,b,c] && <<Y>> <= <<1,2,3>>},
+ ok.
+
+zmc(Config) when is_list(Config) ->
+ [{a,b,1,3}] = [{K1, K2, V1, V2} || K1 := V1 <- #{a=>1} && K2 := V2 <- #{b=>3}],
+ Seq = lists:seq(1, 50),
+ M1 = maps:iterator(#{X=>X || X <- Seq}, ordered),
+ M2 = maps:iterator(#{X=>X || X <- lists:seq(1,50)}, ordered),
+ true = [A * 4 || A <- Seq] =:=
+ [X+Y+Z+W || X := Y <- M1 && Z := W <- M2],
+ true = << <<(A * 4):64>> || A <- Seq>> =:=
+ << <<(X+Y+Z+W):64>> || X := Y <- M1 && Z := W <- M2>>,
+
+ M3 = maps:iterator(#{X=>X*3 || X <- Seq}, ordered),
+ M4 = maps:iterator(#{X*2=>X*4 || X <- Seq}, ordered),
+ true = [{A, A*3, A*2, A*4} || A <- Seq] =:=
+ [{X, Y, Z, W} || X := Y <- M3 && Z := W <- M4],
+ true = [A * 3 || A <- Seq] =:= [X+Y+Z || X := Y <- M1 && Z <- Seq],
+ true = << <<A:64, (A*3):64, (A*2):64, (A*4):64>> || A <- Seq>> =:=
+ << <<X:64, Y:64, Z:64, W:64>> || X := Y <- M3 && Z := W <- M4>>,
+ true = << <<(A*3):64>> || A <- Seq>> =:=
+ << <<(X+Y+Z):64>> || X := Y <- M1 && Z <- Seq>>,
+
+ M5 = maps:iterator(#{X =>
+ case X rem 2 of
+ 0 -> {ok,X};
+ 1 -> {error,X}
+ end || X <- Seq}, ordered),
+ M6 = maps:iterator(#{X*2 => X*4 || X <- Seq}, ordered),
+ [] = [X || {{X,{ok,X}}, {_,X}} <- lists:zip(maps:to_list(M5), maps:to_list(M6))],
+ [] = [X || X := {ok,X} <- M5 && _ := X <- M6],
+ [] = [X || X := {e,X} <- M5 && X := {ok,X} <- M5],
+ ok.
+
+filter_guard(Config) when is_list(Config) ->
+ [[1,2,1]] = [X++Y || X <- [[1,2], [2,-3]] && Y <- [[1], [2]], lists:sum(X)>0],
+ [{a,2}, {b,4}, {c,6}] = [{X,Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>,
+ Y rem 2 == 0],
+ [{b,4}, {c,6}] = [{X, Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>, Y =/= 2],
+ [] = [{X,Y} || X <- [a, b, c] && <<Y>> <= <<2,4,6>>, Y rem 2 == 1],
+ [{b,4}] = [{X,Y} || <<Y>> <= <<2,4,6>> && X <- [a,b,c], X>a, X<c],
+ [{b,d,2}] = [{K1,K2,V1} || K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ K2 := V2 <- maps:iterator(#{c=>3, d=>4}, ordered),
+ V2 rem 2 == 0],
+ <<5>> = << <<(X+Y)/integer>> || X <- [1,2,3] && Y <- [2,2,2], X rem 2 == 1, X+Y>4>>,
+ #{c := 3,a := 1} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3], Y rem 2 == 1},
+ #{c := 3} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3], Y rem 2 == 1, Y > 1},
+ #{c := 3,a := 1} = #{X => Y || X <- [a,b,c] && <<Y>> <= <<1,2,3>>, Y rem 2 == 1}.
+
+filter_pattern(Config) when is_list(Config) ->
+ [] = do_filter_pat_1([], []),
+ [] = do_filter_pat_1([a], [a]),
+ [] = do_filter_pat_1([{ok,a}], [{error,e}]),
+
+ [] = do_filter_pat_2([], []),
+ [] = do_filter_pat_2([a], [b]),
+ [] = do_filter_pat_2([{a,1}], [{b,1}]),
+ [{1,7}] = do_filter_pat_2([{a,1}], [{a,7}]),
+ [{1,7},{10,20}] = do_filter_pat_2([{a,1},{b,9},{x,10}],
+ [{a,7},{wrong,8},{x,20}]),
+
+ ok.
+
+do_filter_pat_1(L1, L2) ->
+ Res = [{A,B} || {ok,A} <- L1 && {ok,B} <- L2],
+ Res = [{A,B} || {{ok,A},{ok,B}} <- lists:zip(L1,L2)],
+ Res.
+
+do_filter_pat_2(L1, L2) ->
+ Res = [{A,B} || {Same,A} <- L1 && {Same,B} <- L2],
+ Res = [{A,B} || {{Same,A},{Same,B}} <- lists:zip(L1,L2)],
+ Res.
+
+cartesian(Config) when is_list(Config) ->
+ [{a,3}, {b,5}, {c,7}, {a,4}, {b,6}, {c,8}] =
+ [{X, W+Y} || W <- [1,2],
+ X <- [a,b,c] && <<Y>> <= <<2,4,6>>],
+ [{a,3}, {a,4}, {b,5}, {b,6}, {c,7}, {c,8}] =
+ [{X, W+Y} || X <- [a,b,c] &&
+ <<Y>> <= <<2,4,6>>, W <- [1,2]],
+ [{a,4}, {b,6}, {c,8}] =
+ [{X, W+Y} || X <- [a,b,c] &&
+ <<Y>> <= <<2,4,6>>, W <- [1,2], (W + Y) rem 2 == 0],
+ <<4,2,5,3,6,4>> = << <<(X+V1+Y)/integer>> ||
+ X <- [1,2,3] &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered),
+ <<Y>> <= <<1,-1>> >>,
+ ok.
+
+strict_list(Config) when is_list(Config) ->
+ Seq100 = lists:seq(1, 100),
+
+ [2,3,4] = [X+Y || X <:- [1,2,3] && Y <- [1,1,1]],
+ [3,4] = [X+Y || X <:- [1,2,3] && Y <:- [1,1,1], X > 1],
+
+ [] = strict_list_mixed_1([], []),
+ [11,22] = strict_list_mixed_1([{i,1},{i,2}], [{i,10},{i,20}]),
+ [13,25] = strict_list_mixed_1([{i,3},{i,4},{i,5}], [{i,10},bad,{i,20}]),
+ {'EXIT',{{bad_generators,{[bad,{i,5}],[{i,15},{i,20}]}},_}} =
+ catch strict_list_mixed_1([{i,3},bad,{i,5}], [{i,10},{i,15},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,5}],[]}},_}} =
+ catch strict_list_mixed_1([{i,3},{i,5}], [bad]),
+
+ [] = strict_list_mixed_2([], #{}),
+ [15] = strict_list_mixed_2([{i,3}], #{{k,4} => {v,3}}),
+ [15] = strict_list_mixed_2([{i,0},{i,3}], #{{a,0} => {a,0},
+ {k,4} => {v,3}}),
+
+ ?assertEqual([I * 3*I + 7*I || I <- Seq100],
+ strict_list_mixed_2([{i,I} || I <- Seq100],
+ #{{k,3*I} => {v,7*I} || I <- Seq100})),
+ SimpleMap = #{{k,1} => {v,2}},
+ {'EXIT',{{bad_generators,{[{a,3}],{{k,1},{v,2},none}}},_}} =
+ catch strict_list_mixed_2([{a,3}], SimpleMap),
+ {'EXIT',{{bad_generators,{[],{{k,1},{v,2},none}}},_}} =
+ catch strict_list_mixed_2([], SimpleMap),
+
+ [] = strict_list_strict_1([], []),
+ [11,22] = strict_list_strict_1([{i,1},{i,2}], [{i,10},{i,20}]),
+ {'EXIT',{{bad_generators,{[bad,{i,5}],[{i,15},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},bad,{i,5}], [{i,10},{i,15},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,4},{i,5}],[{wrong_tag,7},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},{i,4},{i,5}], [{i,10},{wrong_tag,7},{i,20}]),
+ {'EXIT',{{bad_generators,{[{a,b,c},{i,5}],[{wrong_tag,7},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},{a,b,c},{i,5}], [{i,10},{wrong_tag,7},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,5}],[]}},_}} =
+ catch strict_list_strict_1([{i,3},{i,5}], [{i,7}]),
+
+ [] = strict_list_strict_2([], [], <<>>),
+ [5,23] = strict_list_strict_2([{i,1},{i,2}], [{i,2},{i,7}], <<3,9>>),
+ ?assertEqual([2*I * 3*I + I || I <- Seq100],
+ strict_list_strict_2([{i,2*I} || I <- Seq100],
+ [{i,3*I} || I <- Seq100],
+ list_to_binary(Seq100))),
+ {'EXIT',{{bad_generators,{[{i,2}],[{i,7}],<<9:7>>}},_}} =
+ catch strict_list_strict_2([{i,1},{i,2}], [{i,2},{i,7}], <<3,9:7>>),
+ {'EXIT',{{bad_generators,{[],[],[]}},_}} =
+ catch strict_list_strict_2([], [], []),
+ {'EXIT',{{bad_generators,{[{i,0}],[],<<>>}},_}} =
+ catch strict_list_strict_2([{i,0}], [], <<>>),
+ {'EXIT',{{bad_generators,{[{i,0}],[{bad,5}],<<99>>}},_}} =
+ catch strict_list_strict_2([{i,0}], [{bad,5}], <<99>>),
+ {'EXIT',{{bad_generators,{[{i,20}],[{i,21}],<<42:7>>}},_}} =
+ catch strict_list_strict_2([{i,20}], [{i,21}], <<42:7>>),
+
+ [] = strict_list_strict_3([], <<>>),
+ [45] = strict_list_strict_3([{i,42}], <<3>>),
+ {'EXIT',{{bad_generators,{[],<<2>>}},_}} =
+ catch strict_list_strict_3([{i,1}], <<1,2>>),
+ {'EXIT',{{bad_generators,{[],<<0:7>>}},_}} =
+ catch strict_list_strict_3([], <<0:7>>),
+ {'EXIT',{{bad_generators,{[{i,1}],<<0:7>>}},_}} =
+ catch strict_list_strict_3([{i,1}], <<0:7>>),
+
+ [] = strict_list_strict_4([], <<>>),
+ [100] = strict_list_strict_4([{i,100}], <<42>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<0>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<0>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<0:8,1:1>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<0:8,1:1>>),
+
+ NaN = <<-1:64>>,
+ [] = strict_list_5(<<>>, <<>>),
+ [3.14] = strict_list_5(<<0:1,1:1>>, <<32,0.0:32/float, 64,3.14:64/float>>),
+ [0.0,3.14] = strict_list_5(<<1:1,1:1>>, <<32,0.0:32/float, 64,3.14:64/float>>),
+ {'EXIT',{{bad_generators,{<<>>,<<64,42.0/float>>}},_}} =
+ catch strict_list_5(<<>>, <<64,42.0/float>>),
+ {'EXIT',{{bad_generators,{<<0:1,1:1>>,
+ <<117,-1:117/signed,32,17.0:32/float>>}},_}} =
+ catch strict_list_5(<<0:1,1:1>>, <<117,-1:117, 32,17.0:32/float>>),
+ {'EXIT',{{bad_generators,{<<0:1>>,<<64,NaN/binary>>}},_}} =
+ catch strict_list_5(<<1:1,0:1>>, <<32,42.0:32/float, 64,NaN/binary>>),
+ {'EXIT',{{bad_generators,{<<1:1>>,<<64,NaN/binary>>}},_}} =
+ catch strict_list_5(<<1:1,1:1>>, <<32,42.0:32/float, 64,NaN/binary>>),
+
+ ok.
+
+strict_list_mixed_1(X, Y) ->
+ [A + B || {i,A} <:- X && {i,B} <- Y].
+
+strict_list_mixed_2(L, Map0) ->
+ Map = maps:iterator(Map0, ordered),
+ [A * B + C || {i,A} <:- L && {k,B} := {v,C} <- Map].
+
+strict_list_strict_1(X, Y) ->
+ [A + B || {i,A} <:- X && {i,B} <:- Y].
+
+strict_list_strict_2(X, Y, Z) ->
+ [A * B + C || {i,A} <:- X && {i,B} <:- Y && <<C:8>> <:= Z].
+
+strict_list_strict_3(List, Bin) ->
+ [A + B || {i,A} <:- List && <<B:8>> <:= Bin].
+
+strict_list_strict_4(List, Bin) ->
+ [A || {i,A} <:- List && <<42:8>> <:= Bin].
+
+strict_list_5(Wanted, Floats) ->
+ Res = [F || <<W:1>> <:= Wanted && <<Size:8,F:Size/float>> <:= Floats, W =:= 1],
+ Res = [F || <<1:1>> <= Wanted && <<Size:8,F:Size/float>> <:= Floats],
+ Res.
+
+strict_binary(Config) when is_list(Config) ->
+ Seq100 = lists:seq(1, 100),
+
+ <<2,4,6>> = << <<(X+Y)>> || X <:- [1,2,3] && <<Y>> <= <<1,2,3>>>>,
+ <<2,4>> = << <<(X+Y)>> || <<X>> <:= <<1,2,3>> && {X, Y} <- [{1,1},{2,2},{2,3}]>>,
+ <<2,24>> = << <<(X*Y*Z)>> || X := Y <:- #{1 => 2, 3 => 4} && <<Z>> <:= <<1,2>> >>,
+
+ <<>> = strict_binary_1(#{}, <<>>),
+ <<24:64>> = strict_binary_1(#{2 => {val,3}}, <<4:8>>),
+ ?assertEqual(<< <<(5*I * 3*I * I):64>> || I <- Seq100 >>,
+ strict_binary_1(maps:iterator(#{5*I => {val,3*I} || I <- Seq100}, ordered),
+ list_to_binary(Seq100))),
+ {'EXIT',{{bad_generators,{none,<<42:8>>}},_}} = catch strict_binary_1(#{}, <<42:8>>),
+ {'EXIT',{{bad_generators,{none,<<42:7>>}},_}} = catch strict_binary_1(#{}, <<42:7>>),
+ {'EXIT',{{bad_generators,{none,<<0:4>>}},_}} = catch strict_binary_1(#{2 => {val,3}}, <<0,0:4>>),
+
+ <<>> = strict_binary_mixed_1(<<>>, #{}, #{}),
+ <<>> = strict_binary_mixed_1(<<1:2>>, #{}, #{}),
+ <<999:64>> = strict_binary_mixed_1(<<1:1>>, #{0 => {v,0}}, #{1 => {v,999}}),
+ ?assertEqual(<< <<I:64>> || I <- Seq100>>,
+ strict_binary_mixed_1(<<0:100>>,
+ #{I => {v,I} || I <- Seq100},
+ #{I => {v,-I} || I <- Seq100})),
+ ?assertEqual(<< <<-I:64>> || I <- Seq100>>,
+ strict_binary_mixed_1(<<-1:100>>,
+ #{I => {v,I} || I <- Seq100},
+ #{I => {v,-I} || I <- Seq100})),
+ {'EXIT',{{bad_generators,{<<0:1>>,{0,0,none},{0,{v,7},none}}},_}} =
+ catch strict_binary_mixed_1(<<0:1>>, #{0 => 0}, #{0 => {v,7}}),
+
+ Island = ~"skärgårdsö",
+ IslandSeq = lists:seq(1, length([C || <<C/utf8>> <= Island])),
+ ?assertEqual(<< <<I:8,C:32>> ||
+ {I,C} <:- lists:zip(IslandSeq, [C || <<C/utf8>> <= Island]) >>,
+ strict_binary_utf8(IslandSeq, Island)),
+ {'EXIT',{{bad_generators,{[4,5,6,7,8],<<16#ff,16#ff,"def">>}},_}} =
+ catch strict_binary_utf8(lists:seq(1, 8), <<"abc",16#ff,16#ff,"def">>),
+
+ ok.
+
+strict_binary_1(Map, Bin) ->
+ << <<(X*Y*Z):64>> || X := {val,Y} <:- Map && <<Z:8>> <:= Bin >>.
+
+strict_binary_utf8(List, Bin) ->
+ << <<I:8,C:32>> || I <:- List && <<C/utf8>> <:= Bin >>.
+
+strict_binary_mixed_1(Bin, MapA0, MapB0) ->
+ MapA = maps:iterator(MapA0, ordered),
+ MapB = maps:iterator(MapB0, ordered),
+ <<case N of
+ 0 -> <<V1:64>>;
+ 1 -> <<V2:64>>
+ end || <<N:1>> <= Bin && _ := {v,V1} <:- MapA && _ := {v,V2} <- MapB>>.
+
+nomatch(Config) when is_list(Config) ->
+ [] = do_nomatch_1([], []),
+ [] = do_nomatch_1([1], [a]),
+ [] = do_nomatch_1([1,2], [a,b]),
+ {'EXIT',{{bad_generators,{[1,2,3],[]}},_}} = do_nomatch_1([1,2,3], []),
+ {'EXIT',{{bad_generators,{[3],[]}},_}} = do_nomatch_1([1,2,3], [a,b]),
+
+ <<>> = do_nomatch_2([], <<>>),
+ <<>> = do_nomatch_2([a], <<1>>),
+ {'EXIT',{{bad_generators,{[2],<<>>}},_}} = do_nomatch_2([1,2], <<3>>),
+ ok.
+
+do_nomatch_1(L1, L2) ->
+ catch [{X, Y} || a=b=X <- L1 && Y <- L2].
+
+do_nomatch_2(L, Bin) ->
+ catch << <<(X+Y)/integer>> || a=b=X <- L && <<Y>> <= Bin >>.
+
+bad_generators(Config) when is_list(Config) ->
+ {'EXIT',{{bad_generators,{x,[1,2]}},_}} =
+ catch [{X,Y} || X <- x && Y <- [1,2]],
+ {'EXIT',{{bad_generators,{[],[4]}},_}} =
+ catch [{X,Y} || X <- [1,2,3] && Y <- [1,2,3,4]],
+ {'EXIT',{{bad_generators,{[3,4],[]}},_}} =
+ catch [{X,Y} || X <- [1,2,3,4] && Y <- [1,2], X < 3],
+ {'EXIT',{{bad_generators,{[3,4],[]}},_}} =
+ catch << <<(X+Y)/integer>> || X <- [1,2,3,4] && Y <- [1,2], X < 3>>,
+ {'EXIT',{{bad_generators,{<<1,2>>,a}},_}} =
+ catch << <<X:16>> || <<X:16>> <= <<1:8,2:8>> && <<X:8>> <= a>>,
+ {'EXIT',{{bad_generator,a},_}} = catch [X || X := X <- a && _Y <- [1]],
+ {'EXIT',{{bad_generators,{[d],[]}},_}} =
+ catch #{X => Y || X <- [a,b,c,d] && Y <- [1,2,3], Y > 1},
+
+ %% Make sure that line numbers point out the generator.
+ case ?MODULE of
+ zlc_inline_SUITE ->
+ %% No inline suite for now. Just a guard in case we add it later.
+ ok;
+ _ ->
+ {'EXIT',{{bad_generators,{[],[4]}},
+ [{?MODULE,_,_,
+ [{file,"bad_zlc.erl"},{line,4}]}|_]}} =
+ catch bad_generators([1,2,3],[1,2,3,4]),
+
+ {'EXIT',{{bad_generators,{a,[2,3]}},
+ [{?MODULE,_,_,
+ [{file,"bad_zlc.erl"},{line,7}]}|_]}} =
+ catch bad_generators_bc(a,[2,3]),
+
+ {'EXIT',{{bad_generators,{[2],[]}},
+ [{?MODULE,_,_,
+ [{file,"bad_zlc.erl"},{line,10}]}|_]}} =
+ catch bad_generators_mc([1,2],[1]),
+
+ %% List comprehensions with improper lists.
+ {'EXIT',{{bad_generators,{d,[d]}},
+ [{?MODULE,_,_,
+ [{file,"bad_zlc.erl"},{line,4}]}|_]}} =
+ catch bad_generators([a,b,c|d],[a,b,c,d])
+ end,
+ ok.
+
+-file("bad_zlc.erl", 1).
+bad_generators(L1,L2) -> %Line 2
+ [{I1, I2} || %Line 3
+ I1 <- L1 && I2 <- L2]. %Line 4
+bad_generators_bc(L1,L2) -> %Line 5
+ << <<I1:4,I2:4>> || %Line 6
+ I1 <- L1 && I2 <- L2>>. %Line 7
+bad_generators_mc(L1,L2) -> %Line 8
+ #{I1 => I2 || %Line 9
+ I1 <- L1 && I2 <- L2}. %Line 10
diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl
index 6011dd0c97..3681edcc66 100644
--- a/lib/debugger/src/dbg_ieval.erl
+++ b/lib/debugger/src/dbg_ieval.erl
@@ -1112,6 +1112,9 @@ eval_named_fun(As, RF, {Info,Bs,Cs,FName}) ->
eval_lc(E, Qs, Bs, Ieval) ->
{value,eval_lc1(E, Qs, Bs, Ieval),Bs}.
+eval_lc1(E, [{zip, Anno, Gens}|Qs], Bs0, Ieval) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Ieval),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, fun eval_lc1/4, Ieval);
eval_lc1(E, [{generator,G}|Qs], Bs, Ieval) ->
CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end,
eval_generator(G, Bs, CompFun, Ieval);
@@ -1130,6 +1133,238 @@ eval_lc1(E, [], Bs, Ieval) ->
{value,V,_} = expr(E, Bs, Ieval#ieval{top=false}),
[V].
+%% convert values for generator vars from abstract form to flattened lists
+convert_gen_values([{generator,{Generate, Line, P, L0}}|Qs], Acc, Bs0, Ieval0)
+ when Generate =:= generate;
+ Generate =:= generate_strict ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,L1,_Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}),
+ convert_gen_values(Qs, [{Generate, Line, P, L1}|Acc], Bs0, Ieval);
+convert_gen_values([{generator,{Generate, Line, P, L0}}|Qs], Acc, Bs0, Ieval0)
+ when Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ Ieval = Ieval0#ieval{line=Line},
+ {value,L1,_Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}),
+ convert_gen_values(Qs, [{Generate, Line, P, L1}|Acc], Bs0, Ieval);
+convert_gen_values([{generator,{Generate, Line, P, Map0}}|Qs], Acc, Bs0, Ieval0)
+ when Generate =:= m_generate;
+ Generate =:= m_generate_strict ->
+ Ieval = Ieval0#ieval{line=Line},
+ {map_field_exact,_,K,V} = P,
+ {value,Map,_Bs1} = expr(Map0, Bs0, Ieval#ieval{top=false}),
+ Iter = case is_map(Map) of
+ true ->
+ maps:iterator(Map);
+ false ->
+ %% Validate iterator.
+ try maps:foreach(fun(_, _) -> ok end, Map) of
+ _ ->
+ Map
+ catch
+ _:_ ->
+ exception(error,{bad_generator,Map}, Bs0, Ieval)
+ end
+ end,
+ convert_gen_values(Qs, [{Generate, Line, {tuple, Line, [K, V]}, Iter}|Acc], Bs0, Ieval);
+convert_gen_values([], Acc, Bs0, _Ieval) ->
+ {lists:reverse(Acc), Bs0}.
+
+bind_all_generators(Gens, Bs0, Ieval) ->
+ bind_all_generators1(Gens, [], erl_eval:new_bindings(Bs0), Ieval, continue).
+
+bind_all_generators1([{Generate, Anno, P, <<_/bitstring>>=Bin}|Qs],
+ Acc, Bs0, Ieval, continue)
+ when Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ Mfun = match_fun(Bs0),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end,
+ case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(Bs0), Bs0, Mfun, Efun) of
+ {match, Rest, Bs1} ->
+ Bs2 = zip_add_bindings(Bs1, Bs0),
+ case Bs2 of
+ nomatch when Generate =:= b_generate ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc],
+ Bs0, Ieval, skip);
+ nomatch -> {Acc, error};
+ _ ->
+ bind_all_generators1(Qs, [{Generate, Anno, P, Rest}|Acc],
+ Bs2, Ieval, continue)
+ end;
+ {nomatch, Rest} when Generate =:= b_generate ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc], Bs0, Ieval, skip);
+ {nomatch, _Rest} ->
+ {Acc, error};
+ done when Generate =:= b_generate_strict, Bin =/= <<>> ->
+ {Acc, error};
+ done ->
+ {[], done}
+ end;
+bind_all_generators1([{Generate, Anno, P, <<_/bitstring>>=Bin}|Qs], Acc, Bs0, Ieval, skip)
+ when Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ Mfun = match_fun(Bs0),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end,
+ case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(Bs0), Bs0, Mfun, Efun) of
+ {match, Rest, _} ->
+ bind_all_generators1(Qs, [{Generate, Anno, P, Rest}|Acc], Bs0, Ieval, skip);
+ {nomatch, Rest} when Generate =:= b_generate ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc], Bs0, Ieval, skip);
+ {nomatch, _Rest} ->
+ {Acc, error};
+ done when Generate =:= b_generate_strict, Bin =/= <<>> ->
+ {Acc, error};
+ done ->
+ {[], skip}
+ end;
+bind_all_generators1([{Generate, Anno, P, [H|T]}|Qs], Acc, Bs0, Ieval, continue)
+ when Generate =:= generate;
+ Generate =:= generate_strict ->
+ case catch match1(P, H, erl_eval:new_bindings(Bs0), Bs0) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch when Generate =:= generate ->
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc], Bs0, Ieval, skip);
+ nomatch -> {Acc, error};
+ _ ->
+ bind_all_generators1(Qs,[{Generate, Anno, P, T}|Acc], Bs2, Ieval, continue)
+ end;
+ nomatch when Generate =:= generate ->
+ %% match/6 returns nomatch. Skip this value
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc], Bs0, Ieval, skip);
+ nomatch ->
+ {Acc, error}
+ end;
+bind_all_generators1([{generate, Anno, P, [_H|T]}|Qs], Acc, Bs0, Ieval, skip) ->
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc], Bs0, Ieval, skip);
+bind_all_generators1([{generate_strict, Anno, P, [H|T]}|Qs], Acc, Bs0, Ieval, continue) ->
+ case catch match1(P, H, erl_eval:new_bindings(Bs0), Bs0) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch -> {Acc, error};
+ _ ->
+ bind_all_generators1(Qs,[{generate_strict, Anno, P, T}|Acc], Bs2, Ieval, continue)
+ end;
+ nomatch ->
+ {Acc, error}
+ end;
+bind_all_generators1([{Generate, Anno, P, Iter0}|Qs], Acc, Bs0, Ieval, continue)
+ when Generate =:= m_generate;
+ Generate =:= m_generate_strict ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case catch match1(P, {K,V}, erl_eval:new_bindings(Bs0), Bs0) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch when Generate =:= m_generate ->
+ bind_all_generators1(Qs,[{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Ieval, skip);
+ nomatch ->
+ {Acc, error};
+ _ ->
+ bind_all_generators1(Qs,[{Generate, Anno, P, Iter}|Acc],
+ Bs2, Ieval, continue)
+ end;
+ nomatch when Generate =:= m_generate ->
+ bind_all_generators1(Qs, [{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Ieval, skip);
+ nomatch ->
+ {Acc, error}
+ end;
+ none ->
+ {[], done}
+ end;
+bind_all_generators1([{m_generate, Anno, P, Iter0}|Qs], Acc, Bs0, Ieval, skip) ->
+ case maps:next(Iter0) of
+ {_K,_V,Iter} ->
+ bind_all_generators1(Qs, [{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Ieval, skip);
+ none ->
+ {[], skip}
+ end;
+bind_all_generators1([{m_generate_strict, Anno, P, Iter0}|Qs], Acc, Bs0, Ieval, continue) ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case catch match1(P, {K,V}, erl_eval:new_bindings(Bs0), Bs0) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch ->
+ {Acc, error};
+ _ ->
+ bind_all_generators1(Qs,[{m_generate_strict, Anno, P, Iter}|Acc],
+ Bs2, Ieval, continue)
+ end;
+ nomatch -> {Acc, error}
+ end;
+ none ->
+ {[], done}
+ end;
+bind_all_generators1([{generate,_,_,[]}|_], _, _, _, _) ->
+ %% no more values left for a var, time to return
+ {[],done};
+bind_all_generators1([{generate_strict,_,_,[]}|_], _, _, _, _) ->
+ %% no more values left for a var, time to return
+ {[],done};
+bind_all_generators1([{Generate, _Anno, _P, _Term}|_Qs], Acc, _Bs0, _Ieval,_)
+ when Generate =:= generate;
+ Generate =:= generate_strict;
+ Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ {Acc, error};
+bind_all_generators1([], [_H|_T] = Acc, Bs0, _Ieval, continue) ->
+ %% all vars are bind for this round
+ {Acc, Bs0};
+bind_all_generators1([], [_H|_T] = Acc, _Bs0, _Ieval, skip) ->
+ {Acc, skip}.
+
+check_bad_generators([{Generate,_,_,V}|T], Env, Acc)
+ when Generate =:= generate;
+ Generate =:= generate_strict ->
+ check_bad_generators(T, Env, [V|Acc]);
+check_bad_generators([{Generate,_,_,Iter0}|T], Env, Acc)
+ when Generate =:= m_generate;
+ Generate =:= m_generate_strict ->
+ case maps:next(Iter0) of
+ none -> check_bad_generators(T, Env, [#{}|Acc]);
+ _ -> check_bad_generators(T, Env, [#{K => V || K := V <- Iter0}|Acc])
+ end;
+check_bad_generators([{Generate,_,P,<<_/bitstring>>=Bin}|T], Bs0, Acc)
+ when Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ Mfun = match_fun(Bs0),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, #ieval{}) end,
+ case eval_bits:bin_gen(P, Bin, erl_eval:new_bindings(Bs0), Bs0, Mfun, Efun) of
+ done ->
+ check_bad_generators(T, Bs0, [<<>>|Acc]);
+ _ ->
+ check_bad_generators(T, Bs0, [Bin|Acc])
+ end;
+check_bad_generators([{b_generate,_,_,Term}|T], Env, Acc) ->
+ check_bad_generators(T, Env, [Term|Acc]);
+check_bad_generators([{b_generate_strict,_,_,Term}|T], Env, Acc) ->
+ check_bad_generators(T, Env, [Term|Acc]);
+check_bad_generators([], _, Acc)->
+ case lists:any(fun is_generator_end/1, Acc) of
+ false ->
+ %% None of the generators has reached its end.
+ {ok, list_to_tuple(lists:reverse(Acc))};
+ true ->
+ case lists:all(fun(V) -> is_generator_end(V) end, Acc) of
+ true ->
+ %% All generators have reached their end.
+ {ok, list_to_tuple(lists:reverse(Acc))};
+ false ->
+ {error, {bad_generators,list_to_tuple(lists:reverse(Acc))}}
+ end
+ end.
+
+is_generator_end([]) -> true;
+is_generator_end(<<>>) -> true;
+is_generator_end(Other) -> Other =:= #{}.
+
%% eval_bc(Expr,[Qualifier],Bindings,IevalState) ->
%% {value,Value,Bindings}.
%% This is evaluating list comprehensions "straight out of the book".
@@ -1138,6 +1373,9 @@ eval_bc(E, Qs, Bs, Ieval) ->
Val = erlang:list_to_bitstring(eval_bc1(E, Qs, Bs, Ieval)),
{value,Val,Bs}.
+eval_bc1(E, [{zip, Anno, Gens}|Qs], Bs0, Ieval) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Ieval),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, fun eval_bc1/4, Ieval);
eval_bc1(E, [{generator,G}|Qs], Bs, Ieval) ->
CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end,
eval_generator(G, Bs, CompFun, Ieval);
@@ -1160,6 +1398,9 @@ eval_mc(E, Qs, Bs, Ieval) ->
Map = eval_mc1(E, Qs, Bs, Ieval),
{value,maps:from_list(Map),Bs}.
+eval_mc1(E, [{zip, Anno, Gens}|Qs], Bs0, Ieval) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Ieval),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, fun eval_mc1/4, Ieval);
eval_mc1(E, [{generator,G}|Qs], Bs, Ieval) ->
CompFun = fun(NewBs) -> eval_mc1(E, Qs, NewBs, Ieval) end,
eval_generator(G, Bs, CompFun, Ieval);
@@ -1179,6 +1420,25 @@ eval_mc1({map_field_assoc,_,K0,V0}, [], Bs, Ieval) ->
{value,V,_} = expr(V0, Bs, Ieval#ieval{top=false}),
[{K,V}].
+eval_zip(E, [{zip, Anno, VarList}|Qs], Bs0, Fun, Ieval) ->
+ Gens = case check_bad_generators(VarList, Bs0, []) of
+ {ok, Acc} -> Acc;
+ {error, Reason} ->
+ exception(error, Reason, Bs0, Ieval)
+ end,
+ {Rest, Bs1} = bind_all_generators(VarList, Bs0, Ieval),
+ case {Rest, Qs, Bs1} of
+ {_, _, error} -> exception(error,{bad_generators,Gens}, Bs0, Ieval);
+ {[], [], _} -> [];
+ {[], _, _} -> [];
+ {_,_,done} -> [];
+ {_, _, skip} ->
+ eval_zip(E, [{zip, Anno, lists:reverse(Rest)}|Qs], Bs0, Fun, Ieval);
+ {_, _, _} ->
+ Fun(E, Qs, add_bindings(Bs1, Bs0), Ieval) ++
+ eval_zip(E, [{zip, Anno, lists:reverse(Rest)}|Qs], Bs0, Fun, Ieval)
+ end.
+
eval_generator({Generate,Line,P,L0}, Bs0, CompFun, Ieval0) when Generate =:= generate;
Generate =:= generate_strict ->
Ieval = Ieval0#ieval{line=Line},
@@ -1818,6 +2078,31 @@ merge_bindings([{Name,V}|B1s], B2s, Ieval) ->
merge_bindings([], B2s, _Ieval) ->
B2s.
+zip_add_bindings(Bs1, Bs2) when is_map(Bs1), is_map(Bs2) ->
+ zip_add_bindings_map(maps:keys(Bs1), Bs1, Bs2);
+zip_add_bindings(Bs1, Bs2) when is_list(Bs1), is_list(Bs2) ->
+ zip_add_bindings1(orddict:to_list(Bs1), Bs2).
+
+zip_add_bindings_map([Key | Keys], Bs1, Bs2) ->
+ case {Bs1, Bs2} of
+ {#{Key := Same}, #{Key := Same}} -> zip_add_bindings_map(Keys, Bs1, Bs2);
+ {_, #{Key := _}} -> nomatch;
+ {#{Key := Value},_} -> zip_add_bindings_map(Keys, Bs1, Bs2#{Key => Value})
+ end;
+zip_add_bindings_map([], _, Bs2) ->
+ Bs2.
+
+zip_add_bindings1([{Name,Val}|Bs1], Bs2) ->
+ case orddict:find(Name, Bs2) of
+ {ok, Val} ->
+ zip_add_bindings1(Bs1, Bs2);
+ {ok, _Value} -> nomatch;
+ error ->
+ zip_add_bindings1(Bs1, orddict:store(Name, Val, Bs2))
+ end;
+zip_add_bindings1([], Bs2) ->
+ Bs2.
+
%% add_bindings(Bindings1,Bindings2)
%% Add Bindings1 to Bindings2. Bindings in
%% Bindings1 hides bindings in Bindings2.
diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl
index 597ec4e94c..accb744bf7 100644
--- a/lib/debugger/src/dbg_iload.erl
+++ b/lib/debugger/src/dbg_iload.erl
@@ -704,13 +704,31 @@ expr_comprehension({Tag,Anno,E0,Gs0}, St) ->
{generator,{m_generate,L,mc_pattern(P0, St),expr(Qs, false, St)}};
({m_generate_strict,L,P0,Qs}) -> %OTP 26
{generator,{m_generate_strict,L,mc_pattern(P0, St),expr(Qs, false, St)}};
+ ({zip,L,Gens}) ->
+ expr_comprehension({zip,L,Gens}, St);
(Expr) ->
case is_guard_test(Expr, St) of
true -> {guard,guard([[Expr]], St)};
false -> expr(Expr, false, St)
end
end || G <- Gs0],
- {Tag,ln(Anno),expr(E0, false, St),Gs}.
+ {Tag,ln(Anno),expr(E0, false, St),Gs};
+expr_comprehension({zip,Anno,Gens}, St) ->
+ Gs = [case G of
+ ({generate,L,P0,Qs}) ->
+ {generator,{generate,L,pattern(P0, St),expr(Qs, false, St)}};
+ ({generate_strict,L,P0,Qs}) ->
+ {generator,{generate_strict,L,pattern(P0, St),expr(Qs, false, St)}};
+ ({b_generate,L,P0,Qs}) -> %R12.
+ {generator,{b_generate,L,pattern(P0, St),expr(Qs, false, St)}};
+ ({b_generate_strict,L,P0,Qs}) -> %R12.
+ {generator,{b_generate_strict,L,pattern(P0, St),expr(Qs, false, St)}};
+ ({m_generate,L,P0,Qs}) -> %OTP 26
+ {generator,{m_generate,L,mc_pattern(P0, St),expr(Qs, false, St)}};
+ ({m_generate_strict,L,P0,Qs}) -> %OTP 26
+ {generator,{m_generate_strict,L,mc_pattern(P0, St),expr(Qs, false, St)}}
+ end || G <- Gens],
+ {zip,ln(Anno),Gs}.
mc_pattern({map_field_exact,L,KeyP0,ValP0}, St) ->
KeyP1 = pattern(KeyP0, St),
diff --git a/lib/debugger/src/debugger.app.src b/lib/debugger/src/debugger.app.src
index 981e91f73b..56b80b6c84 100644
--- a/lib/debugger/src/debugger.app.src
+++ b/lib/debugger/src/debugger.app.src
@@ -49,5 +49,5 @@
{registered, [dbg_iserver, dbg_wx_mon, dbg_wx_winman]},
{applications, [kernel, stdlib, compiler]},
{optional_applications, [compiler, wx]},
- {runtime_dependencies, ["wx-2.0","stdlib-3.15","kernel-10.0","erts-15.0",
+ {runtime_dependencies, ["wx-2.0","stdlib-6.2","kernel-10.0","erts-15.0",
"compiler-8.0"]}]}.
diff --git a/lib/debugger/test/Makefile b/lib/debugger/test/Makefile
index bf464f8d76..4ecb3652ed 100644
--- a/lib/debugger/test/Makefile
+++ b/lib/debugger/test/Makefile
@@ -53,6 +53,7 @@ MODULES= \
record_SUITE \
trycatch_SUITE \
test_lib \
+ zlc_SUITE \
cleanup
ERL_FILES= $(MODULES:%=%.erl)
diff --git a/lib/debugger/test/erl_eval_SUITE.erl b/lib/debugger/test/erl_eval_SUITE.erl
index 4977b33ee7..bfff31ef01 100644
--- a/lib/debugger/test/erl_eval_SUITE.erl
+++ b/lib/debugger/test/erl_eval_SUITE.erl
@@ -29,6 +29,9 @@
pattern_expr/1,
guard_3/1, guard_4/1,
lc/1,
+ zlc/1,
+ zbc/1,
+ zmc/1,
simple_cases/1,
unary_plus/1,
apply_atom/1,
@@ -64,7 +67,8 @@ suite() ->
all() ->
[guard_1, guard_2, match_pattern, string_plusplus,
- pattern_expr, match_bin, guard_3, guard_4, lc,
+ pattern_expr, match_bin, guard_3, guard_4,
+ lc, zlc, zbc, zmc,
simple_cases, unary_plus, apply_atom, otp_5269,
otp_6539, otp_6543, otp_6787, otp_6977, otp_7550,
otp_8133, funs, try_catch, eval_expr_5, eep37].
@@ -238,6 +242,160 @@ lc(Config) when is_list(Config) ->
"[X || X <- [true,false], X].", [true]),
ok.
+zlc(Config) when is_list(Config) ->
+ check(fun() ->
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4,5,6]]
+ end,
+ "begin X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4,5,6]] end.",
+ [{1,4},{2,5},{3,6}]),
+ check(fun() ->
+ S1 = [x, y, z], S2 = [5, 10, 15], X = 32, Y = 32,
+ [{X, Y} || X <- S1 && Y <- S2]
+ end,
+ "begin
+ S1 = [x, y, z], S2 = [5, 10, 15], X = 32, Y = 32,
+ [{X, Y} || X <- S1 && Y <- S2]
+ end.",
+ [{x,5}, {y,10}, {z,15}]),
+ check(fun() ->
+ [{X, Y, K} || X <- [1,2,3] && Y:=K <- #{1=>a, 2=>b, 3=>c}]
+ end,
+ "begin [{X, Y, K} || X <- [1,2,3] && Y:=K <- #{1=>a, 2=>b, 3=>c}] end.",
+ [{1,1,a},{2,2,b},{3,3,c}]),
+ check(fun() ->
+ [{X, W+Y} || X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, W <- [1,2]]
+ end,
+ "begin [{X, W+Y} || X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, W <- [1,2]] end.",
+ [{a,3}, {a,4}, {b,5}, {b,6}, {c,7}, {c,8}]),
+ check(fun() ->
+ [{X, W+Y} || W <- [0], X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, Y<4]
+ end,
+ "begin [{X, W+Y} || W <- [0], X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, Y<4] end.",
+ [{a,2}]),
+ check(fun() ->
+ [{X,Y}|| a=b=X <- [1,2] && Y <-[1,2]] end,
+ "begin [{X,Y}|| a=b=X <- [1,2] && Y <-[1,2]] end.",
+ []),
+ check(fun() ->
+ [{A,B,W} || {Same,W} <- [{a,1}],
+ {Same,A} <- [{a,1},{b,9},{x,10}] && {Same,B} <- [{a,7},{wrong,8},{x,20}]]
+ end,
+ "begin [{A,B,W} || {Same,W} <- [{a,1}],
+ {Same,A} <- [{a,1},{b,9},{x,10}] && {Same,B} <- [{a,7},{wrong,8},{x,20}]]
+ end.",
+ [{1,7,1},{10,20,1}]),
+ error_check("[X || X <- a && Y <- [1]].",{bad_generators,{a,[1]}}),
+ error_check("[{X,Y} || X <- a && <<Y>> <= <<1,2>>].",{bad_generators,{a,<<1,2>>}}),
+ error_check("[{X,V} || X <- a && _K := V <- #{b=>3}].",{bad_generators,{a,#{b=>3}}}),
+ error_check("begin
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4]] end.",
+ {bad_generators,{[2,3],[]}}),
+ error_check("begin
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y:=_V <- #{1=>1}] end.",
+ {bad_generators,{[2,3],#{}}}),
+ ok.
+
+zbc(Config) when is_list(Config) ->
+ check(fun() ->
+ <<3, 4, 5>>
+ end,
+ "begin
+ X = 32, Y = 32,
+ << <<(X+Y)/integer>> || <<X>> <= <<1,2,3>> && <<Y>> <= <<2,2,2>> >>
+ end.",
+ <<3, 4, 5>>),
+ check(fun() ->
+ <<4,5,6,5,6,7,6,7,8>>
+ end,
+ "begin
+ X = 32, Y = 32, Z = 32,
+ << <<(X+Y+Z)/integer>> || <<X>> <= <<1,2,3>> && <<Y>> <= <<2,2,2>>, Z<-[1,2,3] >>
+ end.",
+ <<4,5,6,5,6,7,6,7,8>>),
+ check(fun() ->
+ <<4, 5, 6>>
+ end,
+ "begin
+ L1 = <<1, 2, 3>>, L2 = <<1, 1, 1>>, L3 = <<2, 2, 2>>,
+ << <<(X+Y+Z)/integer>> || <<X>> <= L1 && <<Y>> <= L2 && <<Z>> <= L3 >>
+ end.",
+ <<4, 5, 6>>),
+ check(fun() ->
+ << <<(X+Y):64>>|| a=b=X <- [1,2] && Y <- [1,2] >> end,
+ "begin << <<(X+Y):64>>|| a=b=X <- [1,2] && Y <- [1,2] >> end.",
+ <<>>),
+ check(fun() ->
+ << <<(X+Y):64>>|| a=b=X <- [1,2] && <<Y>> <= <<1,2>> >> end,
+ "begin << <<(X+Y):64>>|| a=b=X <- [1,2] && <<Y>> <= <<1,2>> >> end.",
+ <<>>),
+ check(fun() ->
+ << <<(X+V):64>>|| a=b=X <- [1,2] && _K:=V <- #{a=>1,b=>2}>> end,
+ "begin << <<(X+V):64>>|| a=b=X <- [1,2] && _K:=V <- #{a=>1,b=>2}>> end.",
+ <<>>),
+ error_check("begin << <<(X+Y):8>> || <<X:b>> <= <<1,2>> && <<Y>> <= <<1,2>> >> end.",
+ {bad_generators,{<<>>,<<1,2>>}}),
+ error_check("begin << <<X>> || <<X>> <= a && Y <- [1]>> end.",{bad_generators,{a,[1]}}),
+ error_check("begin
+ X = 32, Y = 32,
+ << <<(X+Y)/integer>> || X <- [1,2] && Y <- [1,2,3,4]>>
+ end.",
+ {bad_generators,{[],[3,4]}}),
+ error_check("begin << <<X:8>> || X <- [1] && Y <- a && <<Z>> <= <<2>> >> end.",
+ {bad_generators,{[1], a, <<2>>}}),
+ ok.
+
+zmc(Config) when is_list(Config) ->
+ check(fun() ->
+ [{a,b,1,3}]
+ end,
+ "begin
+ M1 = #{a=>1}, M2 = #{b=>3},
+ [{K1, K2, V1, V2} || K1 := V1 <- M1 && K2 := V2 <- M2]
+ end.",
+ [{a,b,1,3}]),
+ check(fun() ->
+ [A * 4 || A <- lists:seq(1, 50)]
+ end,
+ "begin
+ Seq = lists:seq(1, 50),
+ M1 = maps:iterator(#{X=>X || X <- Seq}, ordered),
+ M2 = maps:iterator(#{X=>X || X <- lists:seq(1,50)}, ordered),
+ [X+Y+Z+W || X := Y <- M1 && Z := W <- M2]
+ end.",
+ [A * 4 || A <- lists:seq(1, 50)]),
+ check(fun() ->
+ [{A, A*3, A*2, A*4} || A <- lists:seq(1, 50)]
+ end,
+ "begin
+ Seq = lists:seq(1, 50),
+ M3 = maps:iterator(#{X=>X*3 || X <- Seq}, ordered),
+ M4 = maps:iterator(#{X*2=>X*4 || X <- Seq}, ordered),
+ [{X, Y, Z, W} || X := Y <- M3 && Z := W <- M4]
+ end.",
+ [{A, A*3, A*2, A*4} || A <- lists:seq(1, 50)]),
+ check(fun() ->
+ #{K1 => V1+V2 || K1:=V1 <- #{a=>1} && _K2:=V2 <- #{b=>3}}
+ end,
+ "begin
+ #{K1 => V1+V2 || K1:=V1 <- #{a=>1} && _K2:=V2 <- #{b=>3}}
+ end.",
+ #{a=>4}),
+ check(fun() ->
+ #{K=>V || a := b <- #{x => y} && K := V <- #{x => y}}
+ end,
+ "begin
+ #{K=>V || a := b <- #{x => y} && K := V <- #{x => y}}
+ end.",
+ #{}),
+ error_check("begin
+ #{K1 => V1+V2 || K1:=V1 <- #{a=>1} &&
+ _K2:=V2 <- maps:iterator(#{b=>3,c=>4}, ordered)}
+ end.",
+ {bad_generators,{#{},#{c=>4}}}),
+ error_check("begin #{X=>Y || X <- [1] && Y <- a && K1:=V1 <- #{b=>3}} end.",
+ {bad_generators,{[1], a, #{b=>3}}}),
+ ok.
+
%% Simple cases, just to cover some code.
simple_cases(Config) when is_list(Config) ->
check(fun() -> A = $C end, "A = $C.", $C),
diff --git a/lib/debugger/test/zlc_SUITE.erl b/lib/debugger/test/zlc_SUITE.erl
new file mode 100644
index 0000000000..1fd30d520c
--- /dev/null
+++ b/lib/debugger/test/zlc_SUITE.erl
@@ -0,0 +1,410 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2001-2024. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(zlc_SUITE).
+
+-export([all/0, suite/0, groups/0, init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2,
+ init_per_testcase/2,end_per_testcase/2,
+ basic/1,mixed_zlc/1,zmc/1,filter_guard/1,
+ filter_pattern/1,cartesian/1,nomatch/1,bad_generators/1]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+
+suite() ->
+ [{ct_hooks,[ts_install_cth]},
+ {timetrap,{minutes,1}}].
+
+all() ->
+ [basic,
+ mixed_zlc,
+ zmc,
+ filter_guard,
+ filter_pattern,
+ cartesian,
+ nomatch,
+ bad_generators].
+
+groups() ->
+ [].
+
+init_per_suite(Config) when is_list(Config) ->
+ test_lib:interpret(?MODULE),
+ true = lists:member(?MODULE, int:interpreted()),
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_group(_GroupName, Config) ->
+ Config.
+
+end_per_group(_GroupName, Config) ->
+ Config.
+
+
+init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ Config.
+
+end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
+ ok.
+
+basic(Config) when is_list(Config) ->
+ [6, 7, 8] = [X + Y + Z || X <- [1, 2, 3] && Y <- [2, 2, 2] && Z <- [3,3,3]],
+ [{1, 2, 3}, {2, 2, 3}, {3, 2, 3}] =
+ [{X, Y, Z} || X <- [1, 2, 3] && Y <- [2, 2, 2] && Z <- [3,3,3]],
+ [6, 24] = zipwith4(fun(A, B, C, D) -> (A + B + C) * D end,
+ [1, 5], [2, 2], [0, 1], [2, 3]),
+ 96 = dot([1, 2, 3, 4], [24, 12, 8, 6]),
+
+ [1, 4, 5] = ifelse([true, false, true], [1, 3, 5], [2, 4, 6]),
+
+ [8, 14, 20] = [X + Y + Z || <<X>> <= <<5, 10, 15>> && <<Y>> <= <<1, 2, 3>>
+ && <<Z>> <= <<2, 2, 2>>].
+
+
+zipwith4(F, As, Bs, Cs, Ds) ->
+ [F(A,B,C,D) || A <- As && B <- Bs && C <- Cs && D <- Ds].
+
+dot(Xs, Ys) ->
+ lists:sum([X*Y || X <- Xs && Y <- Ys]).
+
+ifelse(Tests, Xs, Ys) ->
+ %% Simulate R's ifelse(,,)
+ [case T of
+ true -> X;
+ false -> Y
+ end || T <- Tests && X <- Xs && Y <- Ys
+ ].
+
+mixed_zlc(Config) when is_list(Config) ->
+ [{a, 2}, {b, 4}, {c, 6}] = [{X,Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>],
+ [{a, 2}, {b, 4}, {c, 6}] = [{X,Y} || <<Y>> <= <<2,4,6>> && X <- [a,b,c]],
+ [{a,c,1,3}, {b,d,2,4}] = [{K1,K2,V1,V2}||
+ K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ K2 := V2 <- maps:iterator(#{c=>3, d=>4}, ordered)],
+ [{a,1,2}, {b,2,4}] =
+ [{K1,V1,Y} || K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ <<Y>> <= <<2,4>>],
+ [{a,1,2}, {b,2,4}] = [{K1,V1,Y} ||
+ K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ <<Y>> <= <<2,4>>],
+ <<3,4,5>> = << <<(X+Y)/integer>> || X <- [1,2,3] && Y <- [2,2,2]>>,
+ <<3,4,5>> = << <<(X+V1)/integer>> ||
+ X <- [1,2,3] &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ <<3,4,5>> = << <<(X+V1)/integer>> ||
+ <<X>> <= <<1,2,3>> &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ <<3,4,5>> = << <<(V1+V2)/integer>> ||
+ _K1 := V1 <- maps:iterator(#{a=>1, b=>2, c=>3}, ordered) &&
+ _K2 := V2 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered)>>,
+ #{c := 3,b := 2,a := 1} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3]},
+ #{c := 3,b := 2,a := 1} = #{X => Y || X <- [a,b,c] && <<Y>> <= <<1,2,3>>},
+ ok.
+
+zmc(Config) when is_list(Config) ->
+ [{a,b,1,3}] = [{K1, K2, V1, V2} || K1 := V1 <- #{a=>1} && K2 := V2 <- #{b=>3}],
+ Seq = lists:seq(1, 50),
+ M1 = maps:iterator(#{X=>X || X <- Seq}, ordered),
+ M2 = maps:iterator(#{X=>X || X <- lists:seq(1,50)}, ordered),
+ true = [A * 4 || A <- Seq] =:=
+ [X+Y+Z+W || X := Y <- M1 && Z := W <- M2],
+ true = << <<(A * 4):64>> || A <- Seq>> =:=
+ << <<(X+Y+Z+W):64>> || X := Y <- M1 && Z := W <- M2>>,
+
+ M3 = maps:iterator(#{X=>X*3 || X <- Seq}, ordered),
+ M4 = maps:iterator(#{X*2=>X*4 || X <- Seq}, ordered),
+ true = [{A, A*3, A*2, A*4} || A <- Seq] =:=
+ [{X, Y, Z, W} || X := Y <- M3 && Z := W <- M4],
+ true = [A * 3 || A <- Seq] =:= [X+Y+Z || X := Y <- M1 && Z <- Seq],
+ true = << <<A:64, (A*3):64, (A*2):64, (A*4):64>> || A <- Seq>> =:=
+ << <<X:64, Y:64, Z:64, W:64>> || X := Y <- M3 && Z := W <- M4>>,
+ true = << <<(A*3):64>> || A <- Seq>> =:=
+ << <<(X+Y+Z):64>> || X := Y <- M1 && Z <- Seq>>,
+
+ M5 = maps:iterator(#{X =>
+ case X rem 2 of
+ 0 -> {ok,X};
+ 1 -> {error,X}
+ end || X <- Seq}, ordered),
+ M6 = maps:iterator(#{X*2 => X*4 || X <- Seq}, ordered),
+ [] = [X || {{X,{ok,X}}, {_,X}} <- lists:zip(maps:to_list(M5), maps:to_list(M6))],
+ [] = [X || X := {ok,X} <- M5 && _ := X <- M6],
+ [] = [X || X := {e,X} <- M5 && X := {ok,X} <- M5],
+ ok.
+
+filter_guard(Config) when is_list(Config) ->
+ [[1,2,1]] = [X++Y || X <- [[1,2], [2,-3]] && Y <- [[1], [2]], lists:sum(X)>0],
+ [{a,2}, {b,4}, {c,6}] = [{X,Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>,
+ Y rem 2 == 0],
+ [{b,4}, {c,6}] = [{X, Y} || X <- [a,b,c] && <<Y>> <= <<2,4,6>>, Y =/= 2],
+ [] = [{X,Y} || X <- [a, b, c] && <<Y>> <= <<2,4,6>>, Y rem 2 == 1],
+ [{b,4}] = [{X,Y} || <<Y>> <= <<2,4,6>> && X <- [a,b,c], X>a, X<c],
+ [{b,d,2}] = [{K1,K2,V1} || K1 := V1 <- maps:iterator(#{a=>1, b=>2}, ordered) &&
+ K2 := V2 <- maps:iterator(#{c=>3, d=>4}, ordered),
+ V2 rem 2 == 0],
+ <<5>> = << <<(X+Y)/integer>> || X <- [1,2,3] && Y <- [2,2,2], X rem 2 == 1, X+Y>4>>,
+ #{c := 3,a := 1} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3], Y rem 2 == 1},
+ #{c := 3} = #{X => Y || X <- [a,b,c] && Y <- [1,2,3], Y rem 2 == 1, Y > 1},
+ #{c := 3,a := 1} = #{X => Y || X <- [a,b,c] && <<Y>> <= <<1,2,3>>, Y rem 2 == 1}.
+
+filter_pattern(Config) when is_list(Config) ->
+ [] = do_filter_pat_1([], []),
+ [] = do_filter_pat_1([a], [a]),
+ [] = do_filter_pat_1([{ok,a}], [{error,e}]),
+
+ [] = do_filter_pat_2([], []),
+ [] = do_filter_pat_2([a], [b]),
+ [] = do_filter_pat_2([{a,1}], [{b,1}]),
+ [{1,7}] = do_filter_pat_2([{a,1}], [{a,7}]),
+ [{1,7},{10,20}] = do_filter_pat_2([{a,1},{b,9},{x,10}],
+ [{a,7},{wrong,8},{x,20}]),
+
+ ok.
+
+do_filter_pat_1(L1, L2) ->
+ Res = [{A,B} || {ok,A} <- L1 && {ok,B} <- L2],
+ Res = [{A,B} || {{ok,A},{ok,B}} <- lists:zip(L1,L2)],
+ Res.
+
+do_filter_pat_2(L1, L2) ->
+ Res = [{A,B} || {Same,A} <- L1 && {Same,B} <- L2],
+ Res = [{A,B} || {{Same,A},{Same,B}} <- lists:zip(L1,L2)],
+ Res.
+
+cartesian(Config) when is_list(Config) ->
+ [{a,3}, {b,5}, {c,7}, {a,4}, {b,6}, {c,8}] =
+ [{X, W+Y} || W <- [1,2],
+ X <- [a,b,c] && <<Y>> <= <<2,4,6>>],
+ [{a,3}, {a,4}, {b,5}, {b,6}, {c,7}, {c,8}] =
+ [{X, W+Y} || X <- [a,b,c] &&
+ <<Y>> <= <<2,4,6>>, W <- [1,2]],
+ [{a,4}, {b,6}, {c,8}] =
+ [{X, W+Y} || X <- [a,b,c] &&
+ <<Y>> <= <<2,4,6>>, W <- [1,2], (W + Y) rem 2 == 0],
+ <<4,2,5,3,6,4>> = << <<(X+V1+Y)/integer>> ||
+ X <- [1,2,3] &&
+ _K1 := V1 <- maps:iterator(#{a=>2, b=>2, c=>2}, ordered),
+ <<Y>> <= <<1,-1>> >>,
+ ok.
+
+strict_list(Config) when is_list(Config) ->
+ Seq100 = lists:seq(1, 100),
+
+ [2,3,4] = [X+Y || X <:- [1,2,3] && Y <- [1,1,1]],
+ [3,4] = [X+Y || X <:- [1,2,3] && Y <:- [1,1,1], X > 1],
+
+ [] = strict_list_mixed_1([], []),
+ [11,22] = strict_list_mixed_1([{i,1},{i,2}], [{i,10},{i,20}]),
+ [13,25] = strict_list_mixed_1([{i,3},{i,4},{i,5}], [{i,10},bad,{i,20}]),
+ {'EXIT',{{bad_generators,{[bad,{i,5}],[{i,15},{i,20}]}},_}} =
+ catch strict_list_mixed_1([{i,3},bad,{i,5}], [{i,10},{i,15},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,5}],[]}},_}} =
+ catch strict_list_mixed_1([{i,3},{i,5}], [bad]),
+
+ [] = strict_list_mixed_2([], #{}),
+ [15] = strict_list_mixed_2([{i,3}], #{{k,4} => {v,3}}),
+ [15] = strict_list_mixed_2([{i,0},{i,3}], #{{a,0} => {a,0},
+ {k,4} => {v,3}}),
+
+ ?assertEqual([I * 3*I + 7*I || I <- Seq100],
+ strict_list_mixed_2([{i,I} || I <- Seq100],
+ #{{k,3*I} => {v,7*I} || I <- Seq100})),
+ SimpleMap = #{{k,1} => {v,2}},
+ {'EXIT',{{bad_generators,{[{a,3}],{{k,1},{v,2},none}}},_}} =
+ catch strict_list_mixed_2([{a,3}], SimpleMap),
+ {'EXIT',{{bad_generators,{[],{{k,1},{v,2},none}}},_}} =
+ catch strict_list_mixed_2([], SimpleMap),
+
+ [] = strict_list_strict_1([], []),
+ [11,22] = strict_list_strict_1([{i,1},{i,2}], [{i,10},{i,20}]),
+ {'EXIT',{{bad_generators,{[bad,{i,5}],[{i,15},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},bad,{i,5}], [{i,10},{i,15},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,4},{i,5}],[{wrong_tag,7},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},{i,4},{i,5}], [{i,10},{wrong_tag,7},{i,20}]),
+ {'EXIT',{{bad_generators,{[{a,b,c},{i,5}],[{wrong_tag,7},{i,20}]}},_}} =
+ catch strict_list_strict_1([{i,3},{a,b,c},{i,5}], [{i,10},{wrong_tag,7},{i,20}]),
+ {'EXIT',{{bad_generators,{[{i,5}],[]}},_}} =
+ catch strict_list_strict_1([{i,3},{i,5}], [{i,7}]),
+
+ [] = strict_list_strict_2([], [], <<>>),
+ [5,23] = strict_list_strict_2([{i,1},{i,2}], [{i,2},{i,7}], <<3,9>>),
+ ?assertEqual([2*I * 3*I + I || I <- Seq100],
+ strict_list_strict_2([{i,2*I} || I <- Seq100],
+ [{i,3*I} || I <- Seq100],
+ list_to_binary(Seq100))),
+ {'EXIT',{{bad_generators,{[{i,2}],[{i,7}],<<9:7>>}},_}} =
+ catch strict_list_strict_2([{i,1},{i,2}], [{i,2},{i,7}], <<3,9:7>>),
+ {'EXIT',{{bad_generators,{[],[],[]}},_}} =
+ catch strict_list_strict_2([], [], []),
+ {'EXIT',{{bad_generators,{[{i,0}],[],<<>>}},_}} =
+ catch strict_list_strict_2([{i,0}], [], <<>>),
+ {'EXIT',{{bad_generators,{[{i,0}],[{bad,5}],<<99>>}},_}} =
+ catch strict_list_strict_2([{i,0}], [{bad,5}], <<99>>),
+ {'EXIT',{{bad_generators,{[{i,20}],[{i,21}],<<42:7>>}},_}} =
+ catch strict_list_strict_2([{i,20}], [{i,21}], <<42:7>>),
+
+ [] = strict_list_strict_3([], <<>>),
+ [45] = strict_list_strict_3([{i,42}], <<3>>),
+ {'EXIT',{{bad_generators,{[],<<2>>}},_}} =
+ catch strict_list_strict_3([{i,1}], <<1,2>>),
+ {'EXIT',{{bad_generators,{[],<<0:7>>}},_}} =
+ catch strict_list_strict_3([], <<0:7>>),
+ {'EXIT',{{bad_generators,{[{i,1}],<<0:7>>}},_}} =
+ catch strict_list_strict_3([{i,1}], <<0:7>>),
+
+ [] = strict_list_strict_4([], <<>>),
+ [100] = strict_list_strict_4([{i,100}], <<42>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<0>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<0>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<>>),
+ {'EXIT',{{bad_generators,{[{i,100}],<<0:8,1:1>>}},_}} =
+ catch strict_list_strict_4([{i,100}], <<0:8,1:1>>),
+
+ NaN = <<-1:64>>,
+ [] = strict_list_5(<<>>, <<>>),
+ [3.14] = strict_list_5(<<0:1,1:1>>, <<32,0.0:32/float, 64,3.14:64/float>>),
+ [0.0,3.14] = strict_list_5(<<1:1,1:1>>, <<32,0.0:32/float, 64,3.14:64/float>>),
+ {'EXIT',{{bad_generators,{<<>>,<<64,42.0/float>>}},_}} =
+ catch strict_list_5(<<>>, <<64,42.0/float>>),
+ {'EXIT',{{bad_generators,{<<0:1,1:1>>,
+ <<117,-1:117/signed,32,17.0:32/float>>}},_}} =
+ catch strict_list_5(<<0:1,1:1>>, <<117,-1:117, 32,17.0:32/float>>),
+ {'EXIT',{{bad_generators,{<<0:1>>,<<64,NaN/binary>>}},_}} =
+ catch strict_list_5(<<1:1,0:1>>, <<32,42.0:32/float, 64,NaN/binary>>),
+ {'EXIT',{{bad_generators,{<<1:1>>,<<64,NaN/binary>>}},_}} =
+ catch strict_list_5(<<1:1,1:1>>, <<32,42.0:32/float, 64,NaN/binary>>),
+
+ ok.
+
+strict_list_mixed_1(X, Y) ->
+ [A + B || {i,A} <:- X && {i,B} <- Y].
+
+strict_list_mixed_2(L, Map0) ->
+ Map = maps:iterator(Map0, ordered),
+ [A * B + C || {i,A} <:- L && {k,B} := {v,C} <- Map].
+
+strict_list_strict_1(X, Y) ->
+ [A + B || {i,A} <:- X && {i,B} <:- Y].
+
+strict_list_strict_2(X, Y, Z) ->
+ [A * B + C || {i,A} <:- X && {i,B} <:- Y && <<C:8>> <:= Z].
+
+strict_list_strict_3(List, Bin) ->
+ [A + B || {i,A} <:- List && <<B:8>> <:= Bin].
+
+strict_list_strict_4(List, Bin) ->
+ [A || {i,A} <:- List && <<42:8>> <:= Bin].
+
+strict_list_5(Wanted, Floats) ->
+ Res = [F || <<W:1>> <:= Wanted && <<Size:8,F:Size/float>> <:= Floats, W =:= 1],
+ Res = [F || <<1:1>> <= Wanted && <<Size:8,F:Size/float>> <:= Floats],
+ Res.
+
+strict_binary(Config) when is_list(Config) ->
+ Seq100 = lists:seq(1, 100),
+
+ <<2,4,6>> = << <<(X+Y)>> || X <:- [1,2,3] && <<Y>> <= <<1,2,3>>>>,
+ <<2,4>> = << <<(X+Y)>> || <<X>> <:= <<1,2,3>> && {X, Y} <- [{1,1},{2,2},{2,3}]>>,
+ <<2,24>> = << <<(X*Y*Z)>> || X := Y <:- #{1 => 2, 3 => 4} && <<Z>> <:= <<1,2>> >>,
+
+ <<>> = strict_binary_1(#{}, <<>>),
+ <<24:64>> = strict_binary_1(#{2 => {val,3}}, <<4:8>>),
+ ?assertEqual(<< <<(5*I * 3*I * I):64>> || I <- Seq100 >>,
+ strict_binary_1(maps:iterator(#{5*I => {val,3*I} || I <- Seq100}, ordered),
+ list_to_binary(Seq100))),
+ {'EXIT',{{bad_generators,{none,<<42:8>>}},_}} = catch strict_binary_1(#{}, <<42:8>>),
+ {'EXIT',{{bad_generators,{none,<<42:7>>}},_}} = catch strict_binary_1(#{}, <<42:7>>),
+ {'EXIT',{{bad_generators,{none,<<0:4>>}},_}} = catch strict_binary_1(#{2 => {val,3}}, <<0,0:4>>),
+
+ <<>> = strict_binary_mixed_1(<<>>, #{}, #{}),
+ <<>> = strict_binary_mixed_1(<<1:2>>, #{}, #{}),
+ <<999:64>> = strict_binary_mixed_1(<<1:1>>, #{0 => {v,0}}, #{1 => {v,999}}),
+ ?assertEqual(<< <<I:64>> || I <- Seq100>>,
+ strict_binary_mixed_1(<<0:100>>,
+ #{I => {v,I} || I <- Seq100},
+ #{I => {v,-I} || I <- Seq100})),
+ ?assertEqual(<< <<-I:64>> || I <- Seq100>>,
+ strict_binary_mixed_1(<<-1:100>>,
+ #{I => {v,I} || I <- Seq100},
+ #{I => {v,-I} || I <- Seq100})),
+ {'EXIT',{{bad_generators,{<<0:1>>,{0,0,none},{0,{v,7},none}}},_}} =
+ catch strict_binary_mixed_1(<<0:1>>, #{0 => 0}, #{0 => {v,7}}),
+
+ Island = ~"skärgårdsö",
+ IslandSeq = lists:seq(1, length([C || <<C/utf8>> <= Island])),
+ ?assertEqual(<< <<I:8,C:32>> ||
+ {I,C} <:- lists:zip(IslandSeq, [C || <<C/utf8>> <= Island]) >>,
+ strict_binary_utf8(IslandSeq, Island)),
+ {'EXIT',{{bad_generators,{[4,5,6,7,8],<<16#ff,16#ff,"def">>}},_}} =
+ catch strict_binary_utf8(lists:seq(1, 8), <<"abc",16#ff,16#ff,"def">>),
+
+ ok.
+
+strict_binary_1(Map, Bin) ->
+ << <<(X*Y*Z):64>> || X := {val,Y} <:- Map && <<Z:8>> <:= Bin >>.
+
+strict_binary_utf8(List, Bin) ->
+ << <<I:8,C:32>> || I <:- List && <<C/utf8>> <:= Bin >>.
+
+strict_binary_mixed_1(Bin, MapA0, MapB0) ->
+ MapA = maps:iterator(MapA0, ordered),
+ MapB = maps:iterator(MapB0, ordered),
+ <<case N of
+ 0 -> <<V1:64>>;
+ 1 -> <<V2:64>>
+ end || <<N:1>> <= Bin && _ := {v,V1} <:- MapA && _ := {v,V2} <- MapB>>.
+
+nomatch(Config) when is_list(Config) ->
+ [] = do_nomatch_1([], []),
+ [] = do_nomatch_1([1], [a]),
+ [] = do_nomatch_1([1,2], [a,b]),
+ {'EXIT',{{bad_generators,{[1,2,3],[]}},_}} = do_nomatch_1([1,2,3], []),
+ {'EXIT',{{bad_generators,{[3],[]}},_}} = do_nomatch_1([1,2,3], [a,b]),
+
+ <<>> = do_nomatch_2([], <<>>),
+ <<>> = do_nomatch_2([a], <<1>>),
+ {'EXIT',{{bad_generators,{[2],<<>>}},_}} = do_nomatch_2([1,2], <<3>>),
+ ok.
+
+do_nomatch_1(L1, L2) ->
+ catch [{X, Y} || a=b=X <- L1 && Y <- L2].
+
+do_nomatch_2(L, Bin) ->
+ catch << <<(X+Y)/integer>> || a=b=X <- L && <<Y>> <= Bin >>.
+
+bad_generators(Config) when is_list(Config) ->
+ {'EXIT',{{bad_generators,{x,[1,2]}},_}} =
+ catch [{X,Y} || X <- x && Y <- [1,2]],
+ {'EXIT',{{bad_generators,{[],[4]}},_}} =
+ catch [{X,Y} || X <- [1,2,3] && Y <- [1,2,3,4]],
+ {'EXIT',{{bad_generators,{[3,4],[]}},_}} =
+ catch [{X,Y} || X <- [1,2,3,4] && Y <- [1,2], X < 3],
+ {'EXIT',{{bad_generators,{[3,4],[]}},_}} =
+ catch << <<(X+Y)/integer>> || X <- [1,2,3,4] && Y <- [1,2], X < 3>>,
+ {'EXIT',{{bad_generators,{<<1,2>>,a}},_}} =
+ catch << <<X:16>> || <<X:16>> <= <<1:8,2:8>> && <<X:8>> <= a>>,
+ {'EXIT',{{bad_generator,a},_}} = catch [X || X := X <- a && _Y <- [1]],
+ {'EXIT',{{bad_generators,{[d],[]}},_}} =
+ catch #{X => Y || X <- [a,b,c,d] && Y <- [1,2,3], Y > 1},
+
+ ok.
diff --git a/lib/stdlib/examples/erl_id_trans.erl b/lib/stdlib/examples/erl_id_trans.erl
index 0d54643659..c336af17ca 100644
--- a/lib/stdlib/examples/erl_id_trans.erl
+++ b/lib/stdlib/examples/erl_id_trans.erl
@@ -643,6 +643,9 @@ comprehension_quals([{m_generate_strict,Anno,P0,E0}|Qs]) ->
E1 = expr(E0),
P1 = pattern(P0),
[{m_generate_strict,Anno,P1,E1}|comprehension_quals(Qs)];
+comprehension_quals([{zip,Anno,Gens0}|Qs]) ->
+ Gens1 = comprehension_quals(Gens0),
+ [{zip,Anno,Gens1}|comprehension_quals(Qs)];
comprehension_quals([E0|Qs]) ->
E1 = expr(E0),
[E1|comprehension_quals(Qs)];
diff --git a/lib/stdlib/src/erl_error.erl b/lib/stdlib/src/erl_error.erl
index d5f1e550a3..c5ae309ce1 100644
--- a/lib/stdlib/src/erl_error.erl
+++ b/lib/stdlib/src/erl_error.erl
@@ -397,6 +397,8 @@ explain_reason({bad_filter,V}, error=Cl, [], PF, S, _Enc, CL) ->
format_value(V, <<"bad filter ">>, Cl, PF, S, CL);
explain_reason({bad_generator,V}, error=Cl, [], PF, S, _Enc, CL) ->
format_value(V, <<"bad generator ">>, Cl, PF, S, CL);
+explain_reason({bad_generators,V}, error=Cl, [], PF, S, _Enc, CL) ->
+ format_value(V, <<"bad generators: ">>, Cl, PF, S, CL);
explain_reason({unbound,V}, error, [], _PF, _S, _Enc, _CL) ->
io_lib:fwrite(<<"variable ~w is unbound">>, [V]);
%% Exit codes local to the shell module (restricted shell):
diff --git a/lib/stdlib/src/erl_eval.erl b/lib/stdlib/src/erl_eval.erl
index e825efe49d..9e362190ef 100644
--- a/lib/stdlib/src/erl_eval.erl
+++ b/lib/stdlib/src/erl_eval.erl
@@ -129,7 +129,8 @@ to be called.
-export([exprs/2,exprs/3,exprs/4,expr/2,expr/3,expr/4,expr/5,
expr_list/2,expr_list/3,expr_list/4]).
--export([new_bindings/0,bindings/1,binding/2,add_binding/3,del_binding/2]).
+-export([new_bindings/0,new_bindings/1,bindings/1,binding/2,
+ add_binding/3,del_binding/2]).
-export([extended_parse_exprs/1, extended_parse_term/1]).
-export([is_constant_expr/1, partial_eval/1, eval_str/1]).
@@ -139,7 +140,7 @@ to be called.
-export([check_command/2, fun_data/1]).
--import(lists, [reverse/1,foldl/3,member/2]).
+-import(lists, [all/2,any/2,foldl/3,member/2,reverse/1]).
-export_type([binding_struct/0]).
@@ -931,6 +932,9 @@ do_apply(F, _Anno, FunOrModFun, Args) when is_function(F, 2) ->
eval_lc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
ret_expr(lists:reverse(eval_lc1(E, Qs, Bs, Lf, Ef, FUVs, [])), Bs, RBs).
+eval_lc1(E, [{zip, Anno, Gens}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Lf, Ef, FUVs),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, Lf, Ef, FUVs, Acc0, fun eval_lc1/7);
eval_lc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
case is_generator(Q) of
true ->
@@ -944,6 +948,284 @@ eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
{value,V,_} = expr(E, Bs, Lf, Ef, none, FUVs),
[V|Acc].
+%% convert values for generator vars from abstract form to flattened lists
+convert_gen_values([{Generate, Anno, P, L0}|Qs], Acc, Bs0, Lf, Ef,FUVs)
+ when Generate =:= generate;
+ Generate =:= generate_strict;
+ Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ {value,L1,_Bs1} = expr(L0, Bs0, Lf, Ef, none, FUVs),
+ convert_gen_values(Qs, [{Generate, Anno, P, L1}|Acc], Bs0, Lf, Ef, FUVs);
+convert_gen_values([{Generate, Anno, P, Map0}|Qs], Acc, Bs0, Lf, Ef,FUVs)
+ when Generate =:= m_generate;
+ Generate =:= m_generate_strict ->
+ {map_field_exact,_,K,V} = P,
+ {value,Map,_Bs1} = expr(Map0, Bs0, Lf, Ef, none, FUVs),
+ Iter = case is_map(Map) of
+ true ->
+ maps:iterator(Map);
+ false ->
+ %% Validate iterator.
+ try maps:foreach(fun(_, _) -> ok end, Map) of
+ _ ->
+ Map
+ catch
+ _:_ ->
+ apply_error({bad_generator,Map}, ?STACKTRACE,
+ Anno, Bs0, Ef, none)
+ end
+ end,
+ convert_gen_values(Qs, [{Generate, Anno, {tuple, Anno, [K, V]}, Iter}|Acc],
+ Bs0, Lf, Ef, FUVs);
+convert_gen_values([], Acc, Bs0, _Lf, _Ef, _FUVs) ->
+ {reverse(Acc), Bs0}.
+
+bind_all_generators(Gens, Bs0, Lf, Ef, FUVs) ->
+ bind_all_generators1(Gens, [], new_bindings(Bs0), Lf, Ef, FUVs, continue).
+
+bind_all_generators1([{b_generate, Anno, P, <<_/bitstring>>=Bin}|Qs],
+ Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ Mfun = match_fun(Bs0, Ef),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ ErrorFun = fun(A, R, S) -> apply_error(R, S, A, Bs0, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(Bs0), Bs0, Mfun, Efun, ErrorFun) of
+ {match, Rest, Bs1} ->
+ Bs2 = zip_add_bindings(Bs1, Bs0),
+ case Bs2 of
+ nomatch ->
+ bind_all_generators1(Qs,[{b_generate, Anno, P, Rest}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ _ ->
+ bind_all_generators1(Qs,[{b_generate, Anno, P, Rest}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ {nomatch, Rest} ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ done ->
+ {[], done}
+ end;
+bind_all_generators1([{b_generate_strict, Anno, P, <<_/bitstring>>=Bin}|Qs],
+ Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ Mfun = match_fun(Bs0, Ef),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ ErrorFun = fun(A, R, S) -> apply_error(R, S, A, Bs0, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(Bs0), Bs0, Mfun, Efun, ErrorFun) of
+ {match, Rest, Bs1} ->
+ Bs2 = zip_add_bindings(Bs1, Bs0),
+ case Bs2 of
+ nomatch -> {Acc, error};
+ _ ->
+ bind_all_generators1(Qs,[{b_generate_strict, Anno, P, Rest}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ {nomatch, _Rest} ->
+ {Acc, error};
+ done when Bin =/= <<>> ->
+ {Acc, error};
+ done ->
+ {[], done}
+ end;
+bind_all_generators1([{b_generate, Anno, P, <<_/bitstring>>=Bin}|Qs],
+ Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ Mfun = match_fun(Bs0, Ef),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ ErrorFun = fun(A, R, S) -> apply_error(R, S, A, Bs0, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(Bs0), Bs0, Mfun, Efun, ErrorFun) of
+ {match, Rest, _} ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ {nomatch, Rest} ->
+ bind_all_generators1(Qs, [{b_generate, Anno, P, Rest}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ done ->
+ {[], skip}
+ end;
+bind_all_generators1([{b_generate_strict, Anno, P, <<_/bitstring>>=Bin}|Qs],
+ Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ Mfun = match_fun(Bs0, Ef),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ ErrorFun = fun(A, R, S) -> apply_error(R, S, A, Bs0, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(Bs0), Bs0, Mfun, Efun, ErrorFun) of
+ {match, Rest, _} ->
+ bind_all_generators1(Qs, [{b_generate_strict, Anno, P, Rest}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ {nomatch, _Rest} ->
+ {Acc, error};
+ done when Bin =/= <<>> ->
+ {Acc, error};
+ done ->
+ {[], skip}
+ end;
+bind_all_generators1([{generate, Anno, P, [H|T]}|Qs], Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ case match(P, H, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch ->
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ _ -> bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ nomatch ->
+ %% match/6 returns nomatch. Skip this value
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc], Bs0, Lf, Ef, FUVs, skip)
+ end;
+bind_all_generators1([{generate_strict, Anno, P, [H|T]}|Qs], Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ case match(P, H, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch -> {Acc, error};
+ _ -> bind_all_generators1(Qs,[{generate_strict, Anno, P, T}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ nomatch ->
+ {Acc, error}
+ end;
+bind_all_generators1([{generate, Anno, P, [_H|T]}|Qs], Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ bind_all_generators1(Qs,[{generate, Anno, P, T}|Acc], Bs0, Lf, Ef, FUVs, skip);
+bind_all_generators1([{generate_strict, Anno, P, [H|T]}|Qs], Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ case match(P, H, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch ->
+ {Acc, error};
+ _ -> bind_all_generators1(Qs,[{generate_strict, Anno, P, T}|Acc],
+ Bs2, Lf, Ef, FUVs, skip)
+ end;
+ nomatch ->
+ {Acc, error}
+ end;
+bind_all_generators1([{m_generate, Anno, P, Iter0}|Qs], Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case match(P, {K,V}, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch->
+ bind_all_generators1(Qs,[{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ _ -> bind_all_generators1(Qs,[{m_generate, Anno, P, Iter}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ nomatch ->
+ bind_all_generators1(Qs, [{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Lf, Ef, FUVs, skip)
+ end;
+ none ->
+ {[], done}
+ end;
+bind_all_generators1([{m_generate_strict, Anno, P, Iter0}|Qs], Acc, Bs0, Lf, Ef, FUVs, continue) ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case match(P, {K,V}, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch -> {Acc, error};
+ _ -> bind_all_generators1(Qs,[{m_generate_strict, Anno, P, Iter}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ nomatch ->
+ {Acc, error}
+ end;
+ none ->
+ {[], done}
+ end;
+bind_all_generators1([{m_generate, Anno, P, Iter0}|Qs], Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ case maps:next(Iter0) of
+ {_K,_V,Iter} ->
+ bind_all_generators1(Qs, [{m_generate, Anno, P, Iter}|Acc],
+ Bs0, Lf, Ef, FUVs, skip);
+ none ->
+ {[], skip}
+ end;
+bind_all_generators1([{m_generate_strict, Anno, P, Iter0}|Qs], Acc, Bs0, Lf, Ef, FUVs, skip) ->
+ case maps:next(Iter0) of
+ {K,V,Iter} ->
+ case match(P, {K,V}, Anno, new_bindings(Bs0), Bs0, Ef) of
+ {match,Bsn} ->
+ Bs2 = zip_add_bindings(Bsn, Bs0),
+ case Bs2 of
+ nomatch -> {Acc, error};
+ _ -> bind_all_generators1(Qs,[{m_generate_strict, Anno, P, Iter}|Acc],
+ Bs2, Lf, Ef, FUVs, continue)
+ end;
+ nomatch -> {Acc, error}
+ end;
+ none ->
+ {[], done}
+ end;
+bind_all_generators1([{generate,_,_,[]}|_], _, _, _, _, _,_) ->
+ %% no more values left for a var, time to return
+ {[],done};
+bind_all_generators1([{generate_strict,_,_,[]}|_], _, _, _, _, _,_) ->
+ %% no more values left for a var, time to return
+ {[],done};
+bind_all_generators1([{generate, _Anno, _P, _Term}|_Qs], Acc, _Bs0,_Lf, _Ef, _FUVs,_) ->
+ {Acc, error};
+bind_all_generators1([{generate_strict, _Anno, _P, _Term}|_Qs], Acc, _Bs0,_Lf, _Ef, _FUVs,_) ->
+ {Acc, error};
+bind_all_generators1([{b_generate, _Anno, _P, _Term}|_Qs], Acc, _Bs0,_Lf, _Ef, _FUVs,_) ->
+ {Acc, error};
+bind_all_generators1([{b_generate_strict, _Anno, _P, _Term}|_Qs], Acc, _Bs0,_Lf, _Ef, _FUVs,_) ->
+ {Acc, error};
+bind_all_generators1([], [_H|_T] = Acc, Bs0, _Lf, _Ef, _FUVs, continue) ->
+ %% all vars are bind for this round
+ {Acc, Bs0};
+bind_all_generators1([], [_H|_T] = Acc, _Bs0,_Lf, _Ef, _FUVs, skip) ->
+ {Acc, skip}.
+
+check_bad_generators([{Generate,_,_,V}|T], Env, Acc)
+ when Generate =:= generate;
+ Generate =:= generate_strict ->
+ check_bad_generators(T, Env, [V|Acc]);
+check_bad_generators([{Generate,_,_,Iter0}|T], Env, Acc)
+ when Generate =:= m_generate;
+ Generate =:= m_generate_strict ->
+ case maps:next(Iter0) of
+ none -> check_bad_generators(T, Env, [#{}|Acc]);
+ _ -> check_bad_generators(T, Env, [#{K => V || K := V <- Iter0}|Acc])
+ end;
+check_bad_generators([{Generate,_,P,<<_/bitstring>>=Bin}|T], {Bs0, Lf, Ef}, Acc)
+ when Generate =:= b_generate;
+ Generate =:= b_generate_strict ->
+ Mfun = match_fun(Bs0, Ef),
+ Efun = fun(Exp, Bs) -> expr(Exp, Bs, Lf, Ef, none) end,
+ ErrorFun = fun(A, R, S) -> apply_error(R, S, A, Bs0, Ef, none) end,
+ case eval_bits:bin_gen(P, Bin, new_bindings(Bs0), Bs0, Mfun, Efun, ErrorFun) of
+ done ->
+ check_bad_generators(T, {Bs0, Lf, Ef}, [<<>>|Acc]);
+ _ ->
+ check_bad_generators(T, {Bs0, Lf, Ef}, [Bin|Acc])
+ end;
+check_bad_generators([{b_generate,_,_,Term}|T], Env, Acc) ->
+ check_bad_generators(T, Env, [Term|Acc]);
+check_bad_generators([{b_generate_strict,_,_,Term}|T], Env, Acc) ->
+ check_bad_generators(T, Env, [Term|Acc]);
+check_bad_generators([], _, Acc)->
+ case any(fun is_generator_end/1, Acc) of
+ false ->
+ %% None of the generators has reached its end.
+ {ok, list_to_tuple(reverse(Acc))};
+ true ->
+ case all(fun(V) -> is_generator_end(V) end, Acc) of
+ true ->
+ %% All generators have reached their end.
+ {ok, list_to_tuple(reverse(Acc))};
+ false ->
+ {error, {bad_generators,list_to_tuple(reverse(Acc))}}
+ end
+ end.
+
+is_generator_end([]) -> true;
+is_generator_end(<<>>) -> true;
+is_generator_end(Other) -> Other =:= #{}.
+
%% eval_bc(Expr, [Qualifier], Bindings, LocalFunctionHandler,
%% ExternalFuncHandler, RetBindings) ->
%% {value,Value,Bindings} | Value
@@ -951,6 +1233,9 @@ eval_lc1(E, [], Bs, Lf, Ef, FUVs, Acc) ->
eval_bc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
ret_expr(eval_bc1(E, Qs, Bs, Lf, Ef, FUVs, <<>>), Bs, RBs).
+eval_bc1(E, [{zip, Anno, Gens}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Lf, Ef, FUVs),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, Lf, Ef, FUVs, Acc0, fun eval_bc1/7);
eval_bc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
case is_generator(Q) of
true ->
@@ -973,6 +1258,9 @@ eval_mc(E, Qs, Bs, Lf, Ef, RBs, FUVs) ->
Map = maps:from_list(L),
ret_expr(Map, Bs, RBs).
+eval_mc1(E, [{zip, Anno, Gens}|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
+ {VarList, Bs1} = convert_gen_values(Gens, [], Bs0, Lf, Ef, FUVs),
+ eval_zip(E, [{zip, Anno, VarList}|Qs], Bs1, Lf, Ef, FUVs, Acc0, fun eval_mc1/7);
eval_mc1(E, [Q|Qs], Bs0, Lf, Ef, FUVs, Acc0) ->
case is_generator(Q) of
true ->
@@ -986,6 +1274,25 @@ eval_mc1({map_field_assoc,Lfa,K0,V0}, [], Bs, Lf, Ef, FUVs, Acc) ->
{value,KV,_} = expr({tuple,Lfa,[K0,V0]}, Bs, Lf, Ef, none, FUVs),
[KV|Acc].
+eval_zip(E, [{zip, Anno, VarList}|Qs], Bs0, Lf, Ef, FUVs, Acc0, Fun) ->
+ Gens = case check_bad_generators(VarList, {Bs0, Lf, Ef}, []) of
+ {ok, Acc} -> Acc;
+ {error, Reason} ->
+ apply_error(Reason, ?STACKTRACE, Anno, Bs0, Ef, none)
+ end,
+ {Rest, Bs1} = bind_all_generators(VarList, Bs0, Lf, Ef, FUVs),
+ case {Rest, Qs, Bs1} of
+ {_, _, error} -> apply_error({bad_generators,Gens}, ?STACKTRACE, Anno, Bs0, Ef, none);
+ {[], [], _} -> Acc0;
+ {[], _, _} -> Acc0;
+ {_,_,done} -> Acc0;
+ {_, _, skip} ->
+ eval_zip(E, [{zip, Anno, reverse(Rest)}|Qs], Bs0, Lf, Ef, FUVs, Acc0, Fun);
+ {_, _, _} ->
+ Acc1 = Fun(E, Qs, add_bindings(Bs1, Bs0), Lf, Ef, FUVs, Acc0),
+ eval_zip(E, [{zip, Anno, reverse(Rest)}|Qs], Bs0, Lf, Ef, FUVs, Acc1, Fun)
+ end.
+
eval_generator({Generate,Anno,P,L0}, Bs0, Lf, Ef, FUVs, Acc0, CompFun)
when Generate =:= generate;
Generate =:= generate_strict ->
@@ -1630,6 +1937,31 @@ structure.
del_binding(Name, Bs) when is_map(Bs) -> maps:remove(Name, Bs);
del_binding(Name, Bs) when is_list(Bs) -> orddict:erase(Name, Bs).
+zip_add_bindings(Bs1, Bs2) when is_map(Bs1), is_map(Bs2) ->
+ zip_add_bindings_map(maps:keys(Bs1), Bs1, Bs2);
+zip_add_bindings(Bs1, Bs2) when is_list(Bs1), is_list(Bs2) ->
+ zip_add_bindings1(orddict:to_list(Bs1), Bs2).
+
+zip_add_bindings_map([Key | Keys], Bs1, Bs2) ->
+ case {Bs1, Bs2} of
+ {#{Key := Same}, #{Key := Same}} -> zip_add_bindings_map(Keys, Bs1, Bs2);
+ {_, #{Key := _}} -> nomatch;
+ {#{Key := Value},_} -> zip_add_bindings_map(Keys, Bs1, Bs2#{Key => Value})
+ end;
+zip_add_bindings_map([], _, Bs2) ->
+ Bs2.
+
+zip_add_bindings1([{Name,Val}|Bs1], Bs2) ->
+ case orddict:find(Name, Bs2) of
+ {ok, Val} ->
+ zip_add_bindings1(Bs1, Bs2);
+ {ok, _Value} -> nomatch;
+ error ->
+ zip_add_bindings1(Bs1, orddict:store(Name, Val, Bs2))
+ end;
+zip_add_bindings1([], Bs2) ->
+ Bs2.
+
add_bindings(Bs1, Bs2) when is_map(Bs1), is_map(Bs2) ->
maps:merge(Bs2, Bs1);
add_bindings(Bs1, Bs2) ->
@@ -1651,6 +1983,7 @@ merge_bindings(Bs1, Bs2, Anno, Ef) ->
end end,
Bs2, orddict:to_list(Bs1)).
+-spec(new_bindings(binding_struct()) -> binding_struct()).
new_bindings(Bs) when is_map(Bs) -> maps:new();
new_bindings(Bs) when is_list(Bs) -> orddict:new().
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index 7d314632de..6b093e4c4b 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -576,6 +576,10 @@ lc_tq(Anno, [{m_generate_strict,AnnoG,P0,G0} | Qs0], St0) ->
{Qs1,St4} = lc_tq(Anno, Qs0, St3),
P1 = {map_field_exact,AnnoMFE,KeyP1,ValP1},
{[{m_generate_strict,AnnoG,P1,G1} | Qs1],St4};
+lc_tq(Anno, [{zip,AnnoG,G0} | Qs0], St0) ->
+ {G1,St1} = lc_tq(Anno, G0, St0),
+ {Qs1,St2} = lc_tq(Anno, Qs0, St1),
+ {[{zip,AnnoG,G1}|Qs1],St2};
lc_tq(Anno, [F0 | Qs0], #exprec{calltype=Calltype,raw_records=Records}=St0) ->
%% Allow record/2 and expand out as guard test.
IsOverriden = fun(FA) ->
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 850191ca22..5ba47662c9 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -403,6 +403,8 @@ format_error_1({too_many_arguments,Arity}) ->
{~"too many arguments (~w) -- maximum allowed is ~w", [Arity,?MAX_ARGUMENTS]};
format_error_1(update_literal) ->
~"expression updates a literal";
+format_error_1(illegal_zip_generator) ->
+ ~"only generators are allowed in a zip generator.";
%% --- patterns and guards ---
format_error_1(illegal_map_assoc_in_pattern) -> ~"illegal pattern, did you mean to use `:=`?";
format_error_1(illegal_pattern) -> ~"illegal pattern";
@@ -3979,6 +3981,10 @@ lc_quals(Qs, Vt0, St0) ->
{Vt,Uvt,St} = lc_quals(Qs, Vt0, [], St0#lint{recdef_top = false}),
{Vt,Uvt,St#lint{recdef_top = OldRecDef}}.
+lc_quals([{zip,_Anno,Gens} | Qs], Vt0, Uvt0, St0) ->
+ St1 = are_all_generators(Gens,St0),
+ {Vt,Uvt,St} = handle_generators(Gens,Vt0,Uvt0,St1),
+ lc_quals(Qs, Vt, Uvt, St);
lc_quals([{generate,_Anno,P,E} | Qs], Vt0, Uvt0, St0) ->
{Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St0),
lc_quals(Qs, Vt, Uvt, St);
@@ -4015,6 +4021,36 @@ is_guard_test2_info(#lint{records=RDs,locals=Locals,imports=Imports}) ->
is_imported_function(Imports, FA)
end}.
+are_all_generators([{generate,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([{generate_strict,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([{b_generate,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([{b_generate_strict,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([{m_generate,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([{m_generate_strict,_,_,_}|Qs],St) -> are_all_generators(Qs,St);
+are_all_generators([Q|_Qs],St) ->
+ Anno1 = element(2,Q),
+ add_error(Anno1, illegal_zip_generator, St);
+are_all_generators([],St) -> St.
+
+handle_generators(Gens,Vt,Uvt,St0) ->
+ Ps = [P || {_,_,P,_} <- Gens],
+ Es = [E || {_,_,_,E} <- Gens],
+ {Evt,St1} = exprs(Es, Vt, St0),
+ %% Forget variables local to E immediately.
+ Vt1 = vtupdate(vtold(Evt, Vt), Vt),
+ {_, St2} = check_unused_vars(Evt, Vt, St1),
+ {Pvt,Pnew,St3} = comprehension_pattern(Ps, Vt1, St2),
+ %% Have to keep fresh variables separated from used variables somehow
+ %% in order to handle for example X = foo(), [X || <<X:X>> <- bar()].
+ %% 1 2 2 1
+ Vt2 = vtupdate(Pvt, Vt1),
+ St4 = shadow_vars(Pnew, Vt1, generate, St3),
+ Svt = vtold(Vt2, Pnew),
+ {_, St5} = check_old_unused_vars(Svt, Uvt, St4),
+ NUvt = vtupdate(vtnew(Svt, Uvt), Uvt),
+ Vt3 = vtupdate(vtsubtract(Vt2, Pnew), Pnew),
+ {Vt3,NUvt,St5}.
+
handle_generator(P,E,Vt,Uvt,St0) ->
{Evt,St1} = expr(E, Vt, St0),
%% Forget variables local to E immediately.
@@ -4032,6 +4068,10 @@ handle_generator(P,E,Vt,Uvt,St0) ->
Vt3 = vtupdate(vtsubtract(Vt2, Pnew), Pnew),
{Vt3,NUvt,St5}.
+comprehension_pattern([_|_]=Ps, Vt, St) ->
+ Mps = [K || {map_field_exact,_,K,_} <- Ps] ++ [V || {map_field_exact,_,_,V} <- Ps],
+ Ps1 = [P || P <- Ps, element(1,P)=/=map_field_exact],
+ pattern_list(Ps1++Mps, Vt, [], St);
comprehension_pattern({map_field_exact,_,K,V}, Vt, St) ->
pattern_list([K,V], Vt, [], St);
comprehension_pattern(P, Vt, St) ->
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 18b43dd98c..7babc9c7bc 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -31,6 +31,7 @@ pat_expr pat_expr_max map_pat_expr record_pat_expr
pat_argument_list pat_exprs
list tail
list_comprehension lc_expr lc_exprs
+zc_exprs
map_comprehension
binary_comprehension
tuple
@@ -79,7 +80,7 @@ ssa_check_when_clauses.
Terminals
char integer float atom sigil_prefix string sigil_suffix var
-'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' '<:-' ';' ':' '#' '.'
+'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' '<:-' ';' ':' '#' '.' '&&'
'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
'maybe' 'else'
'andalso' 'orelse'
@@ -362,6 +363,11 @@ binary_comprehension -> '<<' expr_max '||' lc_exprs '>>' :
{bc,?anno('$1'),'$2','$4'}.
lc_exprs -> lc_expr : ['$1'].
lc_exprs -> lc_expr ',' lc_exprs : ['$1'|'$3'].
+lc_exprs -> zc_exprs : [{zip, ?anno(hd('$1')), '$1'}].
+lc_exprs -> zc_exprs ',' lc_exprs : [{zip, ?anno('$2'), '$1'}|'$3'].
+
+zc_exprs -> lc_expr '&&' lc_expr : ['$1','$3'].
+zc_exprs -> lc_expr '&&' zc_exprs : ['$1'|'$3'].
lc_expr -> expr : '$1'.
lc_expr -> map_field_exact '<-' expr : {m_generate,?anno('$2'),'$1','$3'}.
@@ -785,7 +791,7 @@ processed (see section [Error Information](#module-error-information)).
-export_type([abstract_clause/0, abstract_expr/0, abstract_form/0,
abstract_type/0, form_info/0, error_info/0]).
%% The following types are exported because they are used by syntax_tools
--export_type([af_binelement/1, af_generator/0, af_remote_function/0]).
+-export_type([af_binelement/1, af_generator/0, af_zip_generator/0, af_remote_function/0]).
%% The following type is used by PropEr
-export_type([af_field_decl/0]).
@@ -942,7 +948,10 @@ processed (see section [Error Information](#module-error-information)).
| {'m_generate', anno(), af_assoc_exact(af_pattern()), abstract_expr()}
| {'m_generate_strict', anno(), af_assoc_exact(af_pattern()), abstract_expr()}
| {'b_generate', anno(), af_pattern(), abstract_expr()}
- | {'b_generate_strict', anno(), af_pattern(), abstract_expr()}.
+ | {'b_generate_strict', anno(), af_pattern(), abstract_expr()}
+ | af_zip_generator().
+
+-type af_zip_generator() :: [af_generator(), ...].
-type af_filter() :: abstract_expr().
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 401cd11007..bff5a92318 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -1047,6 +1047,8 @@ clauses(Type, Opts, Cs) ->
lc_quals(Qs, Opts) ->
{prefer_nl,[$,],lexprs(Qs, fun lc_qual/2, Opts)}.
+lc_qual({zip,_,Qs}, Opts) ->
+ {prefer_nl,["&&"],lexprs(Qs, fun lc_qual/2, Opts)};
lc_qual({m_generate,_,Pat,E}, Opts) ->
Pl = map_field(Pat, Opts),
{list,[{step,[Pl,leaf(" <-")],lexpr(E, 0, Opts)}]};
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index 5b4893b431..117acf3b01 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -604,6 +604,12 @@ scan1(".."++Cs, St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "..", '..', 2);
scan1("."=Cs, St, Line, Col, Toks) ->
{more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
+scan1("&&"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "&&", '&&', 2);
+scan1("&"=Cs, St, Line, Col, Toks) ->
+ {more,{Cs,St,Col,Toks,Line,[],fun scan/6}};
+scan1("&"++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "&", '&', 1);
scan1([$.=C|Cs], St, Line, Col, Toks) ->
scan_dot(Cs, St, Line, Col, Toks, [C]);
scan1([$'|Cs], St, Line, Col, Toks) -> %' Emacs
@@ -741,8 +747,6 @@ scan1([$`|Cs], St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "`", '`', 1);
scan1([$~|Cs], St, Line, Col, Toks) ->
tok2(Cs, St, Line, Col, Toks, "~", '~', 1);
-scan1([$&|Cs], St, Line, Col, Toks) ->
- tok2(Cs, St, Line, Col, Toks, "&", '&', 1);
%% End of optimization.
scan1([C|Cs], St, Line, Col, Toks) when ?UNI255(C) ->
Str = [C],
diff --git a/lib/stdlib/test/erl_eval_SUITE.erl b/lib/stdlib/test/erl_eval_SUITE.erl
index 6b8ad51f9c..674204ff7c 100644
--- a/lib/stdlib/test/erl_eval_SUITE.erl
+++ b/lib/stdlib/test/erl_eval_SUITE.erl
@@ -29,6 +29,9 @@
pattern_expr/1,
guard_3/1, guard_4/1, guard_5/1,
lc/1,
+ zlc/1,
+ zbc/1,
+ zmc/1,
simple_cases/1,
unary_plus/1,
apply_atom/1,
@@ -100,7 +103,8 @@ all() ->
otp_8133, otp_10622, otp_13228, otp_14826,
funs, custom_stacktrace, try_catch, eval_expr_5, zero_width,
eep37, eep43, otp_15035, otp_16439, otp_14708, otp_16545, otp_16865,
- eep49, binary_and_map_aliases, eep58, strict_generators, binary_skip].
+ eep49, binary_and_map_aliases, eep58, strict_generators, binary_skip,
+ zlc, zbc, zmc].
groups() ->
[].
@@ -281,6 +285,161 @@ lc(Config) when is_list(Config) ->
"[X || X <- [true,false], X].", [true]),
ok.
+%% EEP-73 zip generator.
+zlc(Config) when is_list(Config) ->
+ check(fun() ->
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4,5,6]]
+ end,
+ "begin X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4,5,6]] end.",
+ [{1,4},{2,5},{3,6}]),
+ check(fun() ->
+ S1 = [x, y, z], S2 = [5, 10, 15], X = 32, Y = 32,
+ [{X, Y} || X <- S1 && Y <- S2]
+ end,
+ "begin
+ S1 = [x, y, z], S2 = [5, 10, 15], X = 32, Y = 32,
+ [{X, Y} || X <- S1 && Y <- S2]
+ end.",
+ [{x,5}, {y,10}, {z,15}]),
+ check(fun() ->
+ [{X, Y, K} || X <:- [1,2,3] && Y:=K <- #{1=>a, 2=>b, 3=>c}]
+ end,
+ "begin [{X, Y, K} || X <:- [1,2,3] && Y:=K <- #{1=>a, 2=>b, 3=>c}] end.",
+ [{1,1,a},{2,2,b},{3,3,c}]),
+ check(fun() ->
+ [{X, W+Y} || X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, W <- [1,2]]
+ end,
+ "begin [{X, W+Y} || X <- [a, b, c] && <<Y>> <= <<2, 4, 6>>, W <- [1,2]] end.",
+ [{a,3}, {a,4}, {b,5}, {b,6}, {c,7}, {c,8}]),
+ check(fun() ->
+ [{X, W+Y} || W <- [0], X <- [a, b, c] && <<Y>> <:= <<2, 4, 6>>, Y<4]
+ end,
+ "begin [{X, W+Y} || W <- [0], X <- [a, b, c] && <<Y>> <:= <<2, 4, 6>>, Y<4] end.",
+ [{a,2}]),
+ check(fun() ->
+ [{X,Y}|| a=b=X <- [1,2] && Y <-[1,2]] end,
+ "begin [{X,Y}|| a=b=X <- [1,2] && Y <-[1,2]] end.",
+ []),
+ check(fun() ->
+ [{A,B,W} || {Same,W} <- [{a,1}],
+ {Same,A} <- [{a,1},{b,9},{x,10}] && {Same,B} <- [{a,7},{wrong,8},{x,20}]]
+ end,
+ "begin [{A,B,W} || {Same,W} <- [{a,1}],
+ {Same,A} <- [{a,1},{b,9},{x,10}] && {Same,B} <- [{a,7},{wrong,8},{x,20}]]
+ end.",
+ [{1,7,1},{10,20,1}]),
+ error_check("[X || X <- a && Y <- [1]].",{bad_generators,{a,[1]}}),
+ error_check("[{X,Y} || X <- a && <<Y>> <= <<1,2>>].",{bad_generators,{a,<<1,2>>}}),
+ error_check("[{X,V} || X <- a && _K := V <- #{b=>3}].",{bad_generators,{a,#{b=>3}}}),
+ error_check("begin
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y <- [4]] end.",
+ {bad_generators,{[2,3],[]}}),
+ error_check("begin
+ X = 32, Y = 32, [{X, Y} || X <- [1,2,3] && Y:=_V <- #{1=>1}] end.",
+ {bad_generators,{[2,3],#{}}}),
+ ok.
+
+zbc(Config) when is_list(Config) ->
+ check(fun() ->
+ <<3, 4, 5>>
+ end,
+ "begin
+ X = 32, Y = 32,
+ << <<(X+Y)/integer>> || <<X>> <= <<1,2,3>> && <<Y>> <= <<2,2,2>> >>
+ end.",
+ <<3, 4, 5>>),
+ check(fun() ->
+ <<4,5,6,5,6,7,6,7,8>>
+ end,
+ "begin
+ X = 32, Y = 32, Z = 32,
+ << <<(X+Y+Z)/integer>> || <<X>> <= <<1,2,3>> && <<Y>> <= <<2,2,2>>, Z<-[1,2,3] >>
+ end.",
+ <<4,5,6,5,6,7,6,7,8>>),
+ check(fun() ->
+ <<4, 5, 6>>
+ end,
+ "begin
+ L1 = <<1, 2, 3>>, L2 = <<1, 1, 1>>, L3 = <<2, 2, 2>>,
+ << <<(X+Y+Z)/integer>> || <<X>> <= L1 && <<Y>> <= L2 && <<Z>> <= L3 >>
+ end.",
+ <<4, 5, 6>>),
+ check(fun() ->
+ << <<(X+Y):64>>|| a=b=X <- [1,2] && Y <- [1,2] >> end,
+ "begin << <<(X+Y):64>>|| a=b=X <- [1,2] && Y <- [1,2] >> end.",
+ <<>>),
+ check(fun() ->
+ << <<(X+Y):64>>|| a=b=X <- [1,2] && <<Y>> <= <<1,2>> >> end,
+ "begin << <<(X+Y):64>>|| a=b=X <- [1,2] && <<Y>> <= <<1,2>> >> end.",
+ <<>>),
+ check(fun() ->
+ << <<(X+V):64>>|| a=b=X <- [1,2] && _K:=V <- #{a=>1,b=>2}>> end,
+ "begin << <<(X+V):64>>|| a=b=X <- [1,2] && _K:=V <- #{a=>1,b=>2}>> end.",
+ <<>>),
+ error_check("begin << <<(X+Y):8>> || <<X:b>> <= <<1,2>> && <<Y>> <= <<1,2>> >> end.",
+ {bad_generators,{<<>>,<<1,2>>}}),
+ error_check("begin << <<X>> || <<X>> <= a && Y <- [1]>> end.",{bad_generators,{a,[1]}}),
+ error_check("begin
+ X = 32, Y = 32,
+ << <<(X+Y)/integer>> || X <- [1,2] && Y <- [1,2,3,4]>>
+ end.",
+ {bad_generators,{[],[3,4]}}),
+ error_check("begin << <<X:8>> || X <- [1] && Y <- a && <<Z>> <= <<2>> >> end.",
+ {bad_generators,{[1], a, <<2>>}}),
+ ok.
+
+zmc(Config) when is_list(Config) ->
+ check(fun() ->
+ [{a,b,1,3}]
+ end,
+ "begin
+ M1 = #{a=>1}, M2 = #{b=>3},
+ [{K1, K2, V1, V2} || K1 := V1 <- M1 && K2 := V2 <- M2]
+ end.",
+ [{a,b,1,3}]),
+ check(fun() ->
+ [A * 4 || A <- lists:seq(1, 50)]
+ end,
+ "begin
+ Seq = lists:seq(1, 50),
+ M1 = maps:iterator(#{X=>X || X <- Seq}, ordered),
+ M2 = maps:iterator(#{X=>X || X <- lists:seq(1,50)}, ordered),
+ [X+Y+Z+W || X := Y <- M1 && Z := W <- M2]
+ end.",
+ [A * 4 || A <- lists:seq(1, 50)]),
+ check(fun() ->
+ [{A, A*3, A*2, A*4} || A <- lists:seq(1, 50)]
+ end,
+ "begin
+ Seq = lists:seq(1, 50),
+ M3 = maps:iterator(#{X=>X*3 || X <- Seq}, ordered),
+ M4 = maps:iterator(#{X*2=>X*4 || X <- Seq}, ordered),
+ [{X, Y, Z, W} || X := Y <- M3 && Z := W <- M4]
+ end.",
+ [{A, A*3, A*2, A*4} || A <- lists:seq(1, 50)]),
+ check(fun() ->
+ #{K1 => V1+V2 || K1:=V1 <:- #{a=>1} && _K2:=V2 <- #{b=>3}}
+ end,
+ "begin
+ #{K1 => V1+V2 || K1:=V1 <- #{a=>1} && _K2:=V2 <- #{b=>3}}
+ end.",
+ #{a=>4}),
+ check(fun() ->
+ #{K=>V || a := b <- #{x => y} && K := V <- #{x => y}}
+ end,
+ "begin
+ #{K=>V || a := b <- #{x => y} && K := V <- #{x => y}}
+ end.",
+ #{}),
+ error_check("begin
+ #{K1 => V1+V2 || K1:=V1 <- #{a=>1} &&
+ _K2:=V2 <- maps:iterator(#{b=>3,c=>4}, ordered)}
+ end.",
+ {bad_generators,{#{},#{c=>4}}}),
+ error_check("begin #{X=>Y || X <- [1] && Y <- a && K1:=V1 <- #{b=>3}} end.",
+ {bad_generators,{[1], a, #{b=>3}}}),
+ ok.
+
%% Simple cases, just to cover some code.
simple_cases(Config) when is_list(Config) ->
check(fun() -> A = $C end, "A = $C.", $C),
diff --git a/lib/stdlib/test/erl_expand_records_SUITE.erl b/lib/stdlib/test/erl_expand_records_SUITE.erl
index fe055e03f7..e51044febf 100644
--- a/lib/stdlib/test/erl_expand_records_SUITE.erl
+++ b/lib/stdlib/test/erl_expand_records_SUITE.erl
@@ -39,7 +39,7 @@
-export([attributes/1, expr/1, guard/1,
init/1, pattern/1, strict/1, update/1,
otp_5915/1, otp_7931/1, otp_5990/1,
- otp_7078/1, maps/1,
+ otp_7078/1, maps/1, zlc/1,
side_effects/1]).
init_per_testcase(_Case, Config) ->
@@ -55,7 +55,7 @@ suite() ->
all() ->
[attributes, expr, guard, init,
pattern, strict, update, maps,
- side_effects, {group, tickets}].
+ side_effects, zlc, {group, tickets}].
groups() ->
[{tickets, [],
@@ -459,6 +459,15 @@ maps(Config) when is_list(Config) ->
run(Config, Ts, [strict_record_tests]),
ok.
+zlc(Config) when is_list(Config) ->
+ Ts = [<<"-record(rr, {a,b,c}).
+ t() -> R0 = id(#rr{a=[{X,Y}||X <- [1,2] && Y <- [a,b]]}),
+ ok.
+ id(X) -> X.
+ ">>],
+ run(Config, Ts, [strict_record_tests]),
+ ok.
+
%% Strict record tests in guards.
otp_5915(Config) when is_list(Config) ->
%% These tests are also run by the compiler's record_SUITE.
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index b515191639..f6b143a838 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -87,7 +87,8 @@
match_float_zero/1,
undefined_module/1,
update_literal/1,
- messages_with_jaro_suggestions/1]).
+ messages_with_jaro_suggestions/1,
+ illegal_zip_generator/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -123,7 +124,8 @@ all() ->
match_float_zero,
undefined_module,
update_literal,
- messages_with_jaro_suggestions].
+ messages_with_jaro_suggestions,
+ illegal_zip_generator].
groups() ->
[{unused_vars_warn, [],
@@ -5451,6 +5453,24 @@ messages_with_jaro_suggestions(Config) ->
ok.
+illegal_zip_generator(Config) ->
+ Ts = [{not_generator,
+ <<"-compile({nowarn_unused_function,[{foo,0}]}).
+ foo() -> [X + Y || X <- [1,2,3,4] && Y <- [5,6,7] && X > 1].
+ ">>,
+ {[]},
+ {errors,[{{2,68},erl_lint,illegal_zip_generator}],[]}},
+ {not_generator,
+ <<"-compile({nowarn_unused_function,[{bar,0}]}).
+ bar() -> [X + Y || X <- [[1,2],[3,4]] && lists:sum(X) > 0 && Y <- [5,6,7]].
+ ">>,
+ {[]},
+ {errors,[{{2,67},erl_lint,illegal_zip_generator}],[]}}
+ ],
+ [] = run(Config,Ts),
+
+ ok.
+
%%%
%%% Common utilities.
%%%
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index bcb9982f31..1d7bd31883 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -856,6 +856,12 @@ lay_2(Node, Ctxt) ->
D2 = lay(erl_syntax:strict_map_generator_body(Node), Ctxt1),
par([D1, beside(text("<:- "), D2)], Ctxt1#ctxt.break_indent);
+ zip_generator ->
+ Ctxt1 = reset_prec(Ctxt),
+ par(seq(erl_syntax:zip_generator_body(Node),
+ floating(text("&&")), Ctxt1,
+ fun lay/2));
+
implicit_fun ->
D = lay(erl_syntax:implicit_fun_name(Node),
reset_prec(Ctxt)),
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 82ca6a8e9e..e1ec49ab82 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -360,6 +360,8 @@ trees.
variable_literal/1,
warning_marker/1,
warning_marker_info/1,
+ zip_generator/1,
+ zip_generator_body/1,
tree/1,
tree/2,
@@ -467,6 +469,7 @@ trees.
| erl_parse:form_info()
| erl_parse:af_binelement(term())
| erl_parse:af_generator()
+ | erl_parse:af_zip_generator()
| erl_parse:af_remote_function().
%% The representation built by the Erlang standard library parser
@@ -558,6 +561,7 @@ reason `badarg`. Node types currently defined by this module are:
* `user_type_application`
* `variable`
* `warning_marker`
+* `zip_generator`
The user may (for special purposes) create additional nodes with other type
tags, using the `tree/2` function.
@@ -582,7 +586,7 @@ _See also: _`annotated_type/2`, `application/3`, `arity_qualifier/2`, `atom/1`,
`size_qualifier/2`, `string/1`, `text/1`, `tree/2`, `try_expr/3`, `tuple/1`,
`tuple_type/0`, `tuple_type/1`, `type_application/2`, `type_union/1`,
`typed_record_field/2`, `underscore/0`, `user_type_application/2`, `variable/1`,
-`warning_marker/1`.
+`warning_marker/1`,`zip_generator/1`.
""".
-spec type(syntaxTree()) -> atom().
@@ -635,6 +639,7 @@ type(Node) ->
{generate_strict, _, _, _} -> strict_generator;
{m_generate, _, _, _} -> map_generator;
{m_generate_strict, _, _, _} -> strict_map_generator;
+ {zip,_,_} -> zip_generator;
{lc, _, _, _} -> list_comp;
{bc, _, _, _} -> binary_comp;
{mc, _, _, _} -> map_comp;
@@ -6158,6 +6163,48 @@ strict_map_generator_body(Node) ->
end.
+-record(zip_generator, {body :: [syntaxTree()]}).
+
+-doc """
+Creates an abstract zip_generator.
+
+The result represents `G1 && ... Gn`, where each `G` is a generator.
+
+_See also: _`binary_comp/2`, `list_comp/2`, `map_comp/2`, `map_generator_body/1`,
+`map_generator_pattern/1`.
+""".
+-spec zip_generator([syntaxTree()]) -> syntaxTree().
+
+%% `erl_parse' representation:
+%%
+%% {zip, Pos, Body}
+%%
+%% Body = erl_parse()
+
+zip_generator(Body) ->
+ tree(zip_generator, #zip_generator{body = Body}).
+
+revert_zip_generator(Node) ->
+ Pos = get_pos(Node),
+ Body = zip_generator_body(Node),
+ {zip, Pos, Body}.
+
+
+-doc """
+Returns the body subtree of a `zip_generator` node.
+
+_See also: _`zip_generator/1`.
+""".
+-spec zip_generator_body(syntaxTree()) -> syntaxTree().
+
+zip_generator_body(Node) ->
+ case unwrap(Node) of
+ {zip, _, Body} ->
+ Body;
+ Node1 ->
+ (data(Node1))#zip_generator.body
+ end.
+
%% =====================================================================
-doc """
@@ -7577,6 +7624,8 @@ revert_root(Node) ->
revert_variable(Node);
warning_marker ->
revert_warning_marker(Node);
+ zip_generator ->
+ revert_zip_generator(Node);
_ ->
%% Non-revertible new-form node
Node
@@ -7934,7 +7983,9 @@ subtrees(T) ->
[typed_record_field_type(T)]];
user_type_application ->
[[user_type_application_name(T)],
- user_type_application_arguments(T)]
+ user_type_application_arguments(T)];
+ zip_generator ->
+ [zip_generator_body(T)]
end
end.
@@ -8053,7 +8104,8 @@ make_tree(tuple_type, [Es]) -> tuple_type(Es);
make_tree(type_application, [[N], Ts]) -> type_application(N, Ts);
make_tree(type_union, [Es]) -> type_union(Es);
make_tree(typed_record_field, [[F],[T]]) -> typed_record_field(F, T);
-make_tree(user_type_application, [[N], Ts]) -> user_type_application(N, Ts).
+make_tree(user_type_application, [[N], Ts]) -> user_type_application(N, Ts);
+make_tree(zip_generator, [Ts]) -> zip_generator(Ts).
-doc """
diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl
index 670b40961a..ea0e67ec7a 100644
--- a/lib/syntax_tools/src/erl_syntax_lib.erl
+++ b/lib/syntax_tools/src/erl_syntax_lib.erl
@@ -502,6 +502,8 @@ vann(Tree, Env) ->
vann_map_generator(Tree, Env);
strict_map_generator ->
vann_strict_map_generator(Tree, Env);
+ zip_generator ->
+ vann_zip_generator(Tree, Env);
block_expr ->
vann_block_expr(Tree, Env);
macro ->
@@ -646,6 +648,8 @@ vann_list_comp_body_join() ->
vann_map_generator(T,Env);
strict_map_generator ->
vann_strict_map_generator(T,Env);
+ zip_generator ->
+ vann_zip_generator(T,Env);
_ ->
%% Bindings in filters are not
%% exported to the rest of the
@@ -690,6 +694,8 @@ vann_binary_comp_body_join() ->
vann_map_generator(T,Env);
strict_map_generator ->
vann_strict_map_generator(T,Env);
+ zip_generator ->
+ vann_zip_generator(T,Env);
_ ->
%% Bindings in filters are not
%% exported to the rest of the
@@ -708,6 +714,25 @@ vann_binary_comp_body(Ts, Env) ->
{Ts1, {_, Bound, Free}} = lists:mapfoldl(F, {Env, [], []}, Ts),
{Ts1, {Bound, Free}}.
+vann_zip_generator_body_join() ->
+ fun (T, {Env, Bound, Free}) ->
+ {T1, Bound1, Free1} = case erl_syntax:type(T) of
+ binary_generator ->
+ vann_binary_generator(T, Env);
+ generator ->
+ vann_generator(T, Env)
+ end,
+ Env1 = ordsets:union(Env, Bound1),
+ {T1, {Env1, ordsets:union(Bound, Bound1),
+ ordsets:union(Free,
+ ordsets:subtract(Free1, Bound))}}
+ end.
+
+vann_zip_generator_body(Ts, Env) ->
+ F = vann_zip_generator_body_join(),
+ {Ts1, {_, Bound, Free}} = lists:mapfoldl(F, {Env, [], []}, Ts),
+ {Ts1, {Bound, Free}}.
+
%% In list comprehension generators, the pattern variables are always
%% viewed as new occurrences, shadowing whatever is in the input
%% environment (thus, the pattern contains no variable uses, only
@@ -761,6 +786,13 @@ vann_strict_map_generator(Tree, Env) ->
Tree1 = rewrite(Tree, erl_syntax:strict_map_generator(P1, E1)),
{ann_bindings(Tree1, Env, Bound, Free), Bound, Free}.
+vann_zip_generator(Tree, Env) ->
+ Es = erl_syntax:zip_generator_body(Tree),
+ {Es1, {Bound, Free}} = vann_zip_generator_body(Es, Env),
+ Env1 = ordsets:union(Env, Bound),
+ Tree1 = rewrite(Tree, erl_syntax:zip_generator(Es1)),
+ {ann_bindings(Tree1, Env1, Bound, Free), Bound, Free}.
+
vann_block_expr(Tree, Env) ->
Es = erl_syntax:block_expr_body(Tree),
{Es1, {Bound, Free}} = vann_body(Es, Env),
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl
index 87aa5cc810..0ddbbb2ebd 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl
@@ -340,6 +340,12 @@ t_erl_parse_type(Config) when is_list(Config) ->
{"#{K => V || {K,V} <- KVs}", map_comp,false},
{"#{K => V || {K,V} <:- KVs}", map_comp,false},
{"#{K => (catch V) || {K,V} <- KVs}", map_comp,false},
+ {"[V+W||V <- Vs && W <- Ws]", list_comp,false},
+ {"[catch V+W||V <- Vs && W <- Ws]", list_comp,false},
+ {"<< <<B>> || <<B>> <= Bs>>", binary_comp,false},
+ {"<< (catch <<B>>) || <<B>> <= Bs>>", binary_comp,false},
+ {"<< <<B:8,C:8>> || <<B>> <= Bs && <<C>> <= Cs>>", binary_comp,false},
+ {"<< (catch <<B:8,C:8>>) || <<B>> <= Bs && <<C>> <= Cs>>", binary_comp,false},
{"#state{ a = A, b = B}", record_expr,false},
{"#state{}", record_expr,false},
{"#s{ a = #def{ a=A }, b = B}", record_expr,false},
diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
index 9551e2641e..ddf06a7597 100644
--- a/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
+++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/syntax_tools_SUITE_test_module.erl
@@ -597,4 +597,11 @@ strict_generators() ->
[X+1 || <<X>> <:= <<1,2,3>>],
[X*Y || X := Y <:- #{1 => 2, 3 => 4}],
- ok.
\ No newline at end of file
+ ok.
+
+%% EEP-73: Zip generators.
+eep73() ->
+ [{X,Y}||X <- [1,2,3] && Y <- [2,2,2]],
+ [{X,Y}||X <- [1,2,3] && <<Y>> <= <<2,2,2>>],
+ [{K1,K2,V1,V2}|| K1 := V1 <- #{a=>1} && K2 := V2 <- #{b=>3}],
+ ok.
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index c1f92b9bd2..f582c198ad 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -1186,7 +1186,7 @@ behaviour.")
(defvar erlang-font-lock-keywords-lc
(list
- (list "\\(<-\\|<:-\\|<=\\|<:=\\|||\\)\\(\\s \\|$\\)" 1 'font-lock-keyword-face))
+ (list "\\(<-\\|<:-\\|<=\\|<:=\\|||\\|&&\\)\\(\\s \\|$\\)" 1 'font-lock-keyword-face))
"Font lock keyword highlighting list comprehension operators.")
(defvar erlang-font-lock-keywords-keywords
@@ -1513,7 +1513,7 @@ Other commands:
))
(add-to-list 'align-rules-list
`(erlang-generator-arrows
- (regexp . ,(concat space-group "\\(<-\\|<:-\\|<=\\|<:=\\)" space-group))
+ (regexp . ,(concat space-group "\\(<-\\|<:-\\|<=\\|<:=\\|&&\\)" space-group))
(group . (1 3))
(separate . ,(concat "\\(||\\|" erl-sep-forms "\\|" erl-sep-symbols "\\)"))
(repeat . t)
--
2.43.0