Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
5616-Implement-map-comprehensions-in-the-debugg...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 5616-Implement-map-comprehensions-in-the-debugger.patch of Package erlang
From 26266f722706164730a9f903aa05325a2c2f1063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= <bjorn@erlang.org> Date: Thu, 30 Nov 2023 16:36:26 +0100 Subject: [PATCH] Implement map comprehensions in the debugger Fixes #7914 --- lib/debugger/src/dbg_ieval.erl | 88 ++++++++-- lib/debugger/src/dbg_iload.erl | 42 +++-- lib/debugger/test/Makefile | 1 + lib/debugger/test/mc_SUITE.erl | 294 +++++++++++++++++++++++++++++++++ 4 files changed, 393 insertions(+), 32 deletions(-) create mode 100644 lib/debugger/test/mc_SUITE.erl diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl index c43d6d7331..bbd3587c00 100644 --- a/lib/debugger/src/dbg_ieval.erl +++ b/lib/debugger/src/dbg_ieval.erl @@ -1087,6 +1087,8 @@ expr({lc,_Line,E,Qs}, Bs, Ieval) -> eval_lc(E, Qs, Bs, Ieval); expr({bc,_Line,E,Qs}, Bs, Ieval) -> eval_bc(E, Qs, Bs, Ieval); +expr({mc,_Line,E,Qs}, Bs, Ieval) -> + eval_mc(E, Qs, Bs, Ieval); %% Brutal exit on unknown expressions/clauses/values/etc. expr(E, _Bs, _Ieval) -> @@ -1107,16 +1109,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, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) -> - Ieval = Ieval0#ieval{line=Line}, - {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}), +eval_lc1(E, [{generator,G}|Qs], Bs, Ieval) -> CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end, - eval_generate(L1, P, Bs1, CompFun, Ieval); -eval_lc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) -> - Ieval = Ieval0#ieval{line=Line}, - {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{top=false}), - CompFun = fun(NewBs) -> eval_lc1(E, Qs, NewBs, Ieval) end, - eval_b_generate(Bin, P, Bs0, CompFun, Ieval); + eval_generator(G, Bs, CompFun, Ieval); eval_lc1(E, [{guard,Q}|Qs], Bs0, Ieval) -> case guard(Q, Bs0) of true -> eval_lc1(E, Qs, Bs0, Ieval); @@ -1140,16 +1135,9 @@ eval_bc(E, Qs, Bs, Ieval) -> Val = erlang:list_to_bitstring(eval_bc1(E, Qs, Bs, Ieval)), {value,Val,Bs}. -eval_bc1(E, [{generate,Line,P,L0}|Qs], Bs0, Ieval0) -> - Ieval = Ieval0#ieval{line=Line}, - {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}), +eval_bc1(E, [{generator,G}|Qs], Bs, Ieval) -> CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end, - eval_generate(L1, P, Bs1, CompFun, Ieval); -eval_bc1(E, [{b_generate,Line,P,L0}|Qs], Bs0, Ieval0) -> - Ieval = Ieval0#ieval{line=Line}, - {value,Bin,_} = expr(L0, Bs0, Ieval#ieval{top=false}), - CompFun = fun(NewBs) -> eval_bc1(E, Qs, NewBs, Ieval) end, - eval_b_generate(Bin, P, Bs0, CompFun, Ieval); + eval_generator(G, Bs, CompFun, Ieval); eval_bc1(E, [{guard,Q}|Qs], Bs0, Ieval) -> case guard(Q, Bs0) of true -> eval_bc1(E, Qs, Bs0, Ieval); @@ -1165,6 +1153,56 @@ eval_bc1(E, [], Bs, Ieval) -> {value,V,_} = expr(E, Bs, Ieval#ieval{top=false}), [V]. +eval_mc(E, Qs, Bs, Ieval) -> + Map = eval_mc1(E, Qs, Bs, Ieval), + {value,maps:from_list(Map),Bs}. + +eval_mc1(E, [{generator,G}|Qs], Bs, Ieval) -> + CompFun = fun(NewBs) -> eval_mc1(E, Qs, NewBs, Ieval) end, + eval_generator(G, Bs, CompFun, Ieval); +eval_mc1(E, [{guard,Q}|Qs], Bs0, Ieval) -> + case guard(Q, Bs0) of + true -> eval_mc1(E, Qs, Bs0, Ieval); + false -> [] + end; +eval_mc1(E, [Q|Qs], Bs0, Ieval) -> + case expr(Q, Bs0, Ieval#ieval{top=false}) of + {value,true,Bs} -> eval_mc1(E, Qs, Bs, Ieval); + {value,false,_Bs} -> []; + {value,V,Bs} -> exception(error, {bad_filter,V}, Bs, Ieval) + end; +eval_mc1({map_field_assoc,_,K0,V0}, [], Bs, Ieval) -> + {value,K,_} = expr(K0, Bs, Ieval#ieval{top=false}), + {value,V,_} = expr(V0, Bs, Ieval#ieval{top=false}), + [{K,V}]. + +eval_generator({generate,Line,P,L0}, Bs0, CompFun, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,L1,Bs1} = expr(L0, Bs0, Ieval#ieval{top=false}), + eval_generate(L1, P, Bs1, CompFun, Ieval); +eval_generator({b_generate,Line,P,Bin0}, Bs0, CompFun, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {value,Bin,Bs1} = expr(Bin0, Bs0, Ieval#ieval{top=false}), + eval_b_generate(Bin, P, Bs1, CompFun, Ieval); +eval_generator({m_generate,Line,P,Map0}, Bs0, CompFun, Ieval0) -> + Ieval = Ieval0#ieval{line=Line}, + {map_field_exact,_,K,V} = P, + {value,Map,_Bs1} = expr(Map0, Bs0, Ieval), + 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, + eval_m_generate(Iter, {tuple,Line,[K,V]}, Bs0, CompFun, Ieval). + eval_generate([V|Rest], P, Bs0, CompFun, Ieval) -> case catch match1(P, V, erl_eval:new_bindings(), Bs0) of {match,Bsn} -> @@ -1193,6 +1231,20 @@ eval_b_generate(<<_/bitstring>>=Bin, P, Bs0, CompFun, Ieval) -> eval_b_generate(Term, _P, Bs, _CompFun, Ieval) -> exception(error, {bad_generator,Term}, Bs, Ieval). +eval_m_generate(Iter0, P, Bs0, CompFun, Ieval) -> + case maps:next(Iter0) of + {K,V,Iter} -> + case catch match1(P, {K,V}, erl_eval:new_bindings(), Bs0) of + {match,Bsn} -> + Bs2 = add_bindings(Bsn, Bs0), + CompFun(Bs2) ++ eval_m_generate(Iter, P, Bs0, CompFun, Ieval); + nomatch -> + eval_m_generate(Iter, P, Bs0, CompFun, Ieval) + end; + none -> + [] + end. + safe_bif(M, F, As, Bs, Ieval0) -> try apply(M, F, As) of Value -> diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl index 468d52ee61..d185d6be7c 100644 --- a/lib/debugger/src/dbg_iload.erl +++ b/lib/debugger/src/dbg_iload.erl @@ -619,9 +619,11 @@ expr({'try',Anno,Es0,CaseCs0,CatchCs0,As0}, Lc, St) -> As = expr_list(As0, St), {'try',ln(Anno),Es,CaseCs,CatchCs,As}; expr({lc,_,_,_}=Compr, _Lc, St) -> - expr_lc_bc(Compr, St); + expr_comprehension(Compr, St); expr({bc,_,_,_}=Compr, _Lc, St) -> - expr_lc_bc(Compr, St); + expr_comprehension(Compr, St); +expr({mc,_,_,_}=Compr, _Lc, St) -> + expr_comprehension(Compr, St); expr({match,Anno,P0,E0}, _Lc, St) -> E1 = expr(E0, false, St), P1 = pattern(P0, St), @@ -660,7 +662,11 @@ expr({bin_element,Anno,Expr0,Size0,Type0}, _Lc, St) -> {Size1,Type} = make_bit_type(Anno, Size0, Type0), Expr = expr(Expr0, false, St), Size = expr(Size1, false, St), - {bin_element,ln(Anno),Expr,Size,Type}. + {bin_element,ln(Anno),Expr,Size,Type}; +expr({map_field_assoc,L,K0,V0}, _Lc, St) -> + K = expr(K0, false, St), + V = expr(V0, false, St), + {map_field_assoc,L,K,V}. consify([A|As]) -> {cons,0,A,consify(As)}; @@ -676,19 +682,27 @@ make_bit_type(_Line, Size, Type0) -> %Integer or 'all' {ok,Size,Bt} = erl_bits:set_bit_type(Size, Type0), {Size,erl_bits:as_list(Bt)}. -expr_lc_bc({Tag,Anno,E0,Gs0}, St) -> - Gs = lists:map(fun ({generate,L,P0,Qs}) -> - {generate,L,pattern(P0, St),expr(Qs, false, St)}; - ({b_generate,L,P0,Qs}) -> %R12. - {b_generate,L,pattern(P0, St),expr(Qs, false, St)}; - (Expr) -> - case is_guard_test(Expr, St) of - true -> {guard,guard([[Expr]], St)}; - false -> expr(Expr, false, St) - end - end, Gs0), +expr_comprehension({Tag,Anno,E0,Gs0}, St) -> + Gs = [case G of + ({generate,L,P0,Qs}) -> + {generator,{generate,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)}}; + ({m_generate,L,P0,Qs}) -> %OTP 26 + {generator,{m_generate,L,mc_pattern(P0, St),expr(Qs, false, 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}. +mc_pattern({map_field_exact,L,KeyP0,ValP0}, St) -> + KeyP1 = pattern(KeyP0, St), + ValP1 = pattern(ValP0, St), + {map_field_exact,L,KeyP1,ValP1}. + is_guard_test(Expr, #{ctype:=Ctypes}) -> IsOverridden = fun(NA) -> case maps:get(NA, Ctypes, undefined) of diff --git a/lib/debugger/test/Makefile b/lib/debugger/test/Makefile index 518335fe73..d2d32c5345 100644 --- a/lib/debugger/test/Makefile +++ b/lib/debugger/test/Makefile @@ -48,6 +48,7 @@ MODULES= \ line_number_SUITE \ map_SUITE \ maybe_SUITE \ + mc_SUITE \ overridden_bif_SUITE \ record_SUITE \ trycatch_SUITE \ diff --git a/lib/debugger/test/mc_SUITE.erl b/lib/debugger/test/mc_SUITE.erl new file mode 100644 index 0000000000..0a374cb51a --- /dev/null +++ b/lib/debugger/test/mc_SUITE.erl @@ -0,0 +1,294 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2023. 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% +%% +%% Originally based on Per Gustafsson's test suite. +%% + +-module(mc_SUITE). + +-export([all/0,suite/0,init_per_suite/1,end_per_suite/1, + init_per_testcase/2,end_per_testcase/2, + init_per_group/2,end_per_group/2, + basic/1,duplicate_keys/1,mixed/1, + shadow/1,bad_generators/1]). + +-include_lib("common_test/include/ct.hrl"). + +suite() -> [{ct_hooks,[ts_install_cth]}]. + +all() -> + [basic, + duplicate_keys, + mixed, + shadow, + bad_generators]. + +init_per_suite(Config) -> + test_lib:interpret(?MODULE), + true = lists:member(?MODULE, int:interpreted()), + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_Case, Config) -> + test_lib:interpret(?MODULE), + Config. + +end_per_testcase(_Case, _Config) -> + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +-record(foo, {a,b}). + +basic(_Config) -> + mc_double(0), + mc_double(1), + mc_double(2), + mc_double(3), + mc_double(4), + mc_double(5), + + mc_double(17), + mc_double(18), + mc_double(19), + + mc_double(30), + mc_double(31), + mc_double(32), + mc_double(33), + mc_double(34), + + mc_double(63), + mc_double(64), + mc_double(65), + + mc_double(77), + mc_double(127), + mc_double(128), + mc_double(255), + mc_double(333), + mc_double(444), + + %% Patterns that cannot possibly match. + #{} = #{K => V || K := [V]={V} <- id(#{a => [b]})}, + #{} = #{K => V || [K] = 42 := V <- id(#{42 => whatever})}, + + %% Filtering. + Map = #{{a,1} => {a,b,c}, {b,42} => [1,2,3], c => [4,5,6], d => {x,y}}, + [c, d] = lists:sort([K || K := _ <- Map, is_atom(K)]), + [{a,b,c}, [1,2,3]] = lists:sort([V || {_,_} := V <- Map]), + [1] = [H || {_,_} := [H|_] <- Map], + [c, {b,42}] = lists:sort([K || K := [_|_] <- Map]), + + %% Filtering using literal patterns. + [] = [0 || a := b <- #{}], + [] = [0 || a := b <- #{x => y}], + [0] = [0 || a := b <- #{a => b}], + + <<>> = << <<0>> || a := b <- #{} >>, + <<>> = << <<0>> || a := b <- #{x => y} >>, + <<0>> = << <<0>> || a := b <- #{a => b} >>, + + %% Matching partial records. + RecordMap = id(#{#foo{a=I,b=I*I} => I*I*I || I <- [1,2,3,4]}), + + EvenMap = maps:from_list([{K,V} || + {#foo{a=N}=K,V} <- maps:to_list(RecordMap), + N rem 2 =:= 0]), + EvenMap = #{K => V || + #foo{a=N}=K := V <- RecordMap, + N rem 2 =:= 0}, + + Odd = lists:sort([V || {#foo{a=N}, V} <- maps:to_list(RecordMap), + N rem 2 =:= 1]), + Odd = lists:sort([V || #foo{a=N} := V <- RecordMap, N rem 2 =:= 1]), + + ok. + +mc_double(Size) -> + Seq = lists:seq(1, Size), + Map = #{{key,I} => I || I <- Seq}, + + MapDouble = #{K => 2 * V || K := V <- id(Map)}, + MapDouble = maps:from_list([{{key,I}, 2 * I} || I <- Seq]), + + OddKeys = lists:seq(1, Size, 2), + OddKeys = lists:sort([I || {key,I} := I <- Map, + I rem 2 =/= 0]), + + OddMap = #{I => [] || {key,I} := I <- Map, + I rem 2 =/= 0}, + OddMap = #{I => [] || {key,I} := I <- Map, + id(I) rem 2 =/= 0}, + OddKeys = lists:sort(maps:keys(OddMap)), + + %% Test that map comprehensions works on iterators. + test_iterator(Map, 0), + test_iterator(Map, map_size(Map) div 3), + test_iterator(Map, map_size(Map) div 2), + test_iterator(Map, map_size(Map)), + + ok. + +test_iterator(Map, N) -> + Iter0 = maps:iterator(Map), + {First,Iter} = grab(N, Iter0, []), + All = [{K,V} || K := V <- Iter] ++ First, + Map = maps:from_list(All), + ok. + +grab(0, Iter, Acc) -> + {Acc,Iter}; +grab(N, Iter0, Acc) -> + case maps:next(Iter0) of + none -> + {Acc,Iter0}; + {K,V,Iter} -> + grab(N - 1, Iter, [{K,V}|Acc]) + end. + +duplicate_keys(_Config) -> + #{x := b} = #{V => K || {K,V} <- [{a, x}, {b, x}]}, + + #{a := 4, b := 4} = + #{K => V || K <- [a,b], + <<V>> <= <<1,2,3,4>>}, + ok. + +mixed(_Config) -> + Map = id(#{1 => 10, 2 => 5, 3 => 88, 4 => 99, 5 => 36}), + Bin = << <<K:8,V:24>> || K := V <- Map >>, + Map = maps:from_list([{K,V} || <<K:8,V:24>> <= Bin]), + + Atoms = [list_to_atom([C]) || C <- lists:seq($a, $z)], + Integers = lists:seq(1, 64), + + mixed_1(Atoms, Integers), + mixed_2(Atoms, Integers), + mixed_3(Atoms, Integers), + + sum_of_triangular_numbers(7), + sum_of_triangular_numbers(10), + + ok. + +mixed_1(Atoms, Integers) -> + IntegerMap = #{N => [] || N <- Integers}, + IntegerKeys = [N || N := [] <- IntegerMap], + Integers = lists:sort(IntegerKeys), + Combined = [{C,N} || C <- Atoms, N := [] <- IntegerMap], + Combined = [{C,N} || C <- Atoms, N := [] <- maps:iterator(IntegerMap)], + Combined = [{C,N} || C <- Atoms, N <- IntegerKeys], + + ok. + +mixed_2(Atoms, Integers) -> + IntegerMap = #{N => [] || N <- Integers}, + IntegerKeys = [N || N := [] <- IntegerMap], + Bin = << <<N:16>> || N := [] <- IntegerMap >>, + Integers = lists:sort(IntegerKeys), + + Combined = [{C,N} || N := [] <- IntegerMap, C <- Atoms], + Combined = [{C,N} || N := [] <- maps:iterator(IntegerMap), C <- Atoms], + Combined = [{C,N} || <<N:16>> <= Bin, C <- Atoms], + Combined = [{C,N} || N <- IntegerKeys, C <- Atoms], + + ok. + +mixed_3(Atoms, Integers) -> + Map = #{K => V || {K,V} <- lists:zip(Atoms, Integers, trim)}, + Bin = << <<N:16>> || _ := N <- Map >>, + {TrimmedAtoms,TrimmedIntegers} = lists:unzip([{K,V} || K := V <- Map]), + + Combined = lists:sort([{K,V} || K := _ <- Map, _ := V <- Map]), + Combined = lists:sort([{K,V} || K <- TrimmedAtoms, <<V:16>> <= Bin]), + Combined = lists:sort([{K,V} || K <- TrimmedAtoms, V <- TrimmedIntegers]), + + ok. + +sum_of_triangular_numbers(N) -> + Sum = N * (N + 1) * (N + 2) div 6, + Maps = [#{I => I || I <- lists:seq(0, I)} || I <- lists:seq(0, N)], + Numbers = [I || M <- Maps, I := I <- M], + Numbers = lists:flatten([[I || I := I <- M] || M <- Maps]), + Sum = lists:sum([lists:sum([I || I := I <- M]) || M <- Maps]), + Sum = lists:sum(Numbers), + ok. + +shadow(_Config)-> + Shadowed = nomatch, + _ = id(Shadowed), %Eliminate warning. + Map = #{Shadowed => Shadowed+1 || Shadowed <- lists:seq(7, 9)}, + #{7 := 8, 8 := 9, 9 := 10} = id(Map), + [8,9] = lists:sort([Shadowed || _ := Shadowed <- id(Map), + Shadowed < 10]), + ok. + +bad_generators(_Config) -> + %% Make sure that line numbers point out the generator. + case ?MODULE of + mc_inline_SUITE -> + ok; + _ -> + {'EXIT',{{bad_generator,a}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator(a)), + + {'EXIT',{{bad_generator,a}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator_bc(a)), + + {'EXIT',{{bad_generator,a}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator_mc(a)), + + BadIterator = [16#ffff|#{}], + + {'EXIT',{{bad_generator,BadIterator}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator(BadIterator)), + + {'EXIT',{{bad_generator,BadIterator}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator_bc(BadIterator)), + + {'EXIT',{{bad_generator,BadIterator}, + [{?MODULE,_,_,_}|_]}} = + catch id(bad_generator_mc(BadIterator)) + end, + ok. + +id(I) -> I. + +-file("bad_mc.erl", 1). +bad_generator(Map) -> %Line 2 + [{K,V} || %Line 3 + K := V <- Map]. %Line 4 +bad_generator_bc(Map) -> %Line 5 + << <<K:8,V:24>> || %Line 6 + K := V <- Map>>. %Line 7 +bad_generator_mc(Map) -> %Line 8 + #{V => K || %Line 9 + K := V <- Map}. %Line 10 -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor