From 9aabcf4151bd2552ac3a6115b5f1224ae14fadd8 Mon Sep 17 00:00:00 2001
From: Hans Bolinder <hasse@erlang.org>
Date: Thu, 16 Feb 2017 16:50:23 +0100
Subject: stdlib: Improve pretty-printing of terms with maps

As of committing this branch maps:fold/3 calls maps:to_list/1, which
means that the time and memory needed for printing maps is not always
proportional to the size of the generated deep list of characters.
---
 lib/stdlib/src/io_lib_pretty.erl | 131 ++++++++++++++++++---
 lib/stdlib/test/io_SUITE.erl     | 241 +++++++++++++++++++++++++++++++++++----
 2 files changed, 337 insertions(+), 35 deletions(-)

(limited to 'lib')

diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index 16ca2f41dc..ba2cffdcb3 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %% 
-%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2017. 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.
@@ -128,6 +128,10 @@ max_cs(M, _Len) ->
     M.
 
 -define(ATM(T), is_list(element(1, T))).
+-define(ATM_PAIR(Pair),
+        ?ATM(element(2, element(1, Pair))) % Key
+        andalso
+        ?ATM(element(3, element(1, Pair)))). % Value
 -define(ATM_FLD(Field), ?ATM(element(4, element(1, Field)))).
 
 pp({_S, Len} = If, Col, Ll, M, _TInd, _Ind, LD, W) 
@@ -140,9 +144,8 @@ pp({{tuple,true,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
 pp({{tuple,false,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
     [${, pp_list(L, Col + 1, Ll, M, TInd, indent(1, Ind), LD, $,, W + 1), $}];
 pp({{map,Pairs},_Len}, Col, Ll, M, TInd, Ind, LD, W) ->
-    [$#,${, pp_list(Pairs, Col + 2, Ll, M, TInd, indent(2, Ind), LD, $,, W + 1), $}];
-pp({{map_pair,K,V},_Len}, Col, Ll, M, TInd, Ind, LD, W) ->
-    [pp(K, Col, Ll, M, TInd, Ind, LD, W), " => ", pp(V, Col, Ll, M, TInd, Ind, LD, W)];
+    [$#, ${, pp_map(Pairs, Col + 2, Ll, M, TInd, indent(2, Ind), LD, W + 1),
+     $}];
 pp({{record,[{Name,NLen} | L]}, _Len}, Col, Ll, M, TInd, Ind, LD, W) ->
     [Name, ${, pp_record(L, NLen, Col, Ll, M, TInd, Ind, LD, W + NLen+1), $}];
 pp({{bin,S}, _Len}, Col, Ll, M, _TInd, Ind, LD, W) ->
@@ -166,6 +169,46 @@ pp_tag_tuple([{Tag,Tlen} | L], Col, Ll, M, TInd, Ind, LD, W) ->
             [Tag, S | pp_list(L, Tcol, Ll, M, TInd, Indent, LD, S, W+Tlen+1)]
     end.
 
+pp_map([], _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+    "";
+pp_map({dots, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+    "...";
+pp_map([P | Ps], Col, Ll, M, TInd, Ind, LD, W) ->
+    {PS, PW} = pp_pair(P, Col, Ll, M, TInd, Ind, last_depth(Ps, LD), W),
+    [PS | pp_pairs_tail(Ps, Col, Col + PW, Ll, M, TInd, Ind, LD, PW)].
+
+pp_pairs_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
+    "";
+pp_pairs_tail({dots, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, _W) ->
+    ",...";
+pp_pairs_tail([{_, Len}=P | Ps], Col0, Col, Ll, M, TInd, Ind, LD, W) ->
+    LD1 = last_depth(Ps, LD),
+    ELen = 1 + Len,
+    if
+        LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_PAIR(P);
+        LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_PAIR(P) ->
+            [$,, write_pair(P) |
+             pp_pairs_tail(Ps, Col0, Col+ELen, Ll, M, TInd, Ind, LD, W+ELen)];
+        true ->
+            {PS, PW} = pp_pair(P, Col0, Ll, M, TInd, Ind, LD1, 0),
+            [$,, $\n, Ind, PS |
+             pp_pairs_tail(Ps, Col0, Col0 + PW, Ll, M, TInd, Ind, LD, PW)]
+    end.
+
+pp_pair({_, Len}=Pair, Col, Ll, M, _TInd, _Ind, LD, W)
+         when Len < Ll - Col - LD, Len + W + LD =< M ->
+    {write_pair(Pair), if
+                          ?ATM_PAIR(Pair) ->
+                              Len;
+                          true ->
+                              Ll % force nl
+                      end};
+pp_pair({{map_pair, K, V}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W) ->
+    I = map_value_indent(TInd),
+    Ind = indent(I, Ind0),
+    {[pp(K, Col0, Ll, M, TInd, Ind0, LD, W), " =>\n",
+      Ind | pp(V, Col0 + I, Ll, M, TInd, Ind, LD, 0)], Ll}. % force nl
+
 pp_record([], _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
     "";
 pp_record({dots, _}, _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->
@@ -204,7 +247,11 @@ pp_field({_, Len}=Fl, Col, Ll, M, _TInd, _Ind, LD, W)
                       end};
 pp_field({{field, Name, NameL, F}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W0) ->
     {Col, Ind, S, W} = rec_indent(NameL, TInd, Col0, Ind0, W0 + NameL),
-    {[Name, " = ", S | pp(F, Col, Ll, M, TInd, Ind, LD, W)], Ll}. % force nl
+    Sep = case S of
+              [$\n | _] -> " =";
+              _ -> " = "
+          end,
+    {[Name, Sep, S | pp(F, Col, Ll, M, TInd, Ind, LD, W)], Ll}. % force nl
 
 rec_indent(RInd, TInd, Col0, Ind0, W0) ->
     Nl = (TInd > 0) and (RInd > TInd),
@@ -291,8 +338,8 @@ write({{list, L}, _}) ->
     [$[, write_list(L, $|), $]];
 write({{map, Pairs}, _}) ->
     [$#,${, write_list(Pairs, $,), $}];
-write({{map_pair, K, V}, _}) ->
-    [write(K)," => ",write(V)];
+write({{map_pair, _K, _V}, _}=Pair) ->
+    write_pair(Pair);
 write({{record, [{Name,_} | L]}, _}) ->
     [Name, ${, write_fields(L), $}];
 write({{bin, S}, _}) ->
@@ -300,6 +347,9 @@ write({{bin, S}, _}) ->
 write({S, _}) ->
     S.
 
+write_pair({{map_pair, K, V}, _}) ->
+    [write(K), " => ", write(V)].
+
 write_fields([]) ->
     "";
 write_fields({dots, _}) ->
@@ -333,7 +383,7 @@ write_tail(E, S) ->
 
 %% The depth (D) is used for extracting and counting the characters to
 %% print. The structure is kept so that the returned intermediate
-%% format can be formatted. The separators (list, tuple, record) are
+%% format can be formatted. The separators (list, tuple, record, map) are
 %% counted but need to be added later.
 
 %% D =/= 0
@@ -406,21 +456,32 @@ print_length(Term, _D, _RF, _Enc, _Str) ->
 print_length_map(_Map, 1, _RF, _Enc, _Str) ->
     {"#{...}", 6};
 print_length_map(Map, D, RF, Enc, Str) when is_map(Map) ->
-    Pairs = print_length_map_pairs(maps:to_list(Map), D, RF, Enc, Str),
+    Pairs = print_length_map_pairs(maps_to_list(Map, D), D, RF, Enc, Str),
     {{map, Pairs}, list_length(Pairs, 3)}.
 
+maps_to_list(Map, D) when D < 0; map_size(Map) =< D ->
+    maps:to_list(Map);
+maps_to_list(Map, D) ->
+    F = fun(_K, _V, {N, L}) when N =:= D ->
+                throw(L);
+           (K, V, {N, L}) ->
+                {N+1, [{K, V} | L]}
+        end,
+    lists:reverse(catch maps:fold(F, {0, []}, Map)).
+
 print_length_map_pairs([], _D, _RF, _Enc, _Str) ->
     [];
 print_length_map_pairs(_Pairs, 1, _RF, _Enc, _Str) ->
     {dots, 3};
-print_length_map_pairs([{K,V}|Pairs], D, RF, Enc, Str) ->
-    [print_length_map_pair(K,V,D-1,RF,Enc,Str) |
-     print_length_map_pairs(Pairs,D-1,RF,Enc,Str)].
+print_length_map_pairs([{K, V} | Pairs], D, RF, Enc, Str) ->
+    [print_length_map_pair(K, V, D - 1, RF, Enc, Str) |
+     print_length_map_pairs(Pairs, D - 1, RF, Enc, Str)].
 
 print_length_map_pair(K, V, D, RF, Enc, Str) ->
     {KS, KL} = print_length(K, D, RF, Enc, Str),
     {VS, VL} = print_length(V, D, RF, Enc, Str),
-    {{map_pair, {KS,KL}, {VS,VL}}, KL + VL}.
+    KL1 = KL + 4,
+    {{map_pair, {KS, KL1}, {VS, VL}}, KL1 + VL}.
 
 print_length_tuple(_Tuple, 1, _RF, _Enc, _Str) ->
     {"{...}", 5};
@@ -612,6 +673,8 @@ cind({{tuple,true,L}, _Len}, Col, Ll, M, Ind, LD, W) ->
     cind_tag_tuple(L, Col, Ll, M, Ind, LD, W + 1);
 cind({{tuple,false,L}, _Len}, Col, Ll, M, Ind, LD, W) ->
     cind_list(L, Col + 1, Ll, M, Ind, LD, W + 1);
+cind({{map,Pairs},_Len}, Col, Ll, M, Ind, LD, W) ->
+    cind_map(Pairs, Col + 2, Ll, M, Ind, LD, W + 2);
 cind({{record,[{_Name,NLen} | L]}, _Len}, Col, Ll, M, Ind, LD, W) ->
     cind_record(L, NLen, Col, Ll, M, Ind, LD, W + NLen + 1);
 cind({{bin,_S}, _Len}, _Col, _Ll, _M, Ind, _LD, _W) ->
@@ -637,6 +700,48 @@ cind_tag_tuple([{_Tag,Tlen} | L], Col, Ll, M, Ind, LD, W) ->
             throw(no_good)
     end.
 
+cind_map([P | Ps], Col, Ll, M, Ind, LD, W) ->
+    PW = cind_pair(P, Col, Ll, M, Ind, last_depth(Ps, LD), W),
+    cind_pairs_tail(Ps, Col, Col + PW, Ll, M, Ind, LD, W + PW);
+cind_map(_, _Col, _Ll, _M, Ind, _LD, _W) ->
+    Ind.
+
+cind_pairs_tail([{_, Len}=P | Ps], Col0, Col, Ll, M, Ind, LD, W) ->
+    LD1 = last_depth(Ps, LD),
+    ELen = 1 + Len,
+    if
+        LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_PAIR(P);
+        LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_PAIR(P) ->
+            cind_pairs_tail(Ps, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen);
+        true ->
+            PW = cind_pair(P, Col0, Ll, M, Ind, LD1, 0),
+            cind_pairs_tail(Ps, Col0, Col0 + PW, Ll, M, Ind, LD, PW)
+    end;
+cind_pairs_tail(_, _Col0, _Col, _Ll, _M, Ind, _LD, _W) ->
+    Ind.
+
+cind_pair({{map_pair, _Key, _Value}, Len}=Pair, Col, Ll, M, _Ind, LD, W)
+         when Len < Ll - Col - LD, Len + W + LD =< M ->
+    if
+        ?ATM_PAIR(Pair) ->
+            Len;
+        true ->
+            Ll
+    end;
+cind_pair({{map_pair, K, V}, _Len}, Col0, Ll, M, Ind, LD, W0) ->
+    cind(K, Col0, Ll, M, Ind, LD, W0),
+    I = map_value_indent(Ind),
+    cind(V, Col0 + I, Ll, M, Ind, LD, 0),
+    Ll.
+
+map_value_indent(TInd) ->
+    case TInd > 0 of
+        true ->
+            TInd;
+        false ->
+            4
+    end.
+
 cind_record([F | Fs], Nlen, Col0, Ll, M, Ind, LD, W0) ->
     Nind = Nlen + 1,
     {Col, W} = cind_rec(Nind, Col0, Ll, M, Ind, W0),
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 7d48cbc97c..6e99619324 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -1,7 +1,7 @@
 %%
 %% %CopyrightBegin%
 %% 
-%% Copyright Ericsson AB 1999-2016. All Rights Reserved.
+%% Copyright Ericsson AB 1999-2017. 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.
@@ -30,7 +30,7 @@
 	 io_lib_print_binary_depth_one/1, otp_10302/1, otp_10755/1,
          otp_10836/1, io_lib_width_too_small/1,
          io_with_huge_message_queue/1, format_string/1,
-	 maps/1, coverage/1]).
+	 maps/1, coverage/1, otp_14175/1]).
 
 -export([pretty/2]).
 
@@ -61,7 +61,7 @@ all() ->
      printable_range, bad_printable_range,
      io_lib_print_binary_depth_one, otp_10302, otp_10755, otp_10836,
      io_lib_width_too_small, io_with_huge_message_queue,
-     format_string, maps, coverage].
+     format_string, maps, coverage, otp_14175].
 
 %% Error cases for output.
 error_1(Config) when is_list(Config) ->
@@ -415,13 +415,13 @@ otp_6354(Config) when is_list(Config) ->
     bt(<<"#rrrrr{\n"
 	 "    f1 = 1,\n"
 	 "    f2 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
-	 "    f3 = \n"
+	 "    f3 =\n"
 	 "        #rrrrr{\n"
 	 "            f1 = h,f2 = i,\n"
-	 "            f3 = \n"
+	 "            f3 =\n"
 	 "                #rrrrr{\n"
 	 "                    f1 = aa,\n"
-	 "                    f2 = \n"
+	 "                    f2 =\n"
 	 "                        #rrrrr{\n"
 	 "                            f1 = #rrrrr{f1 = a,f2 = b,f3 = c},\n"
 	 "                            f2 = 2,f3 = 3},\n"
@@ -431,17 +431,17 @@ otp_6354(Config) when is_list(Config) ->
 					    2,3},bb}}},
 	 -1)),
     bt(<<"#d{aaaaaaaaaaaaaaaaaaaa = 1,\n"
-	 "   bbbbbbbbbbbbbbbbbbbb = \n"
+	 "   bbbbbbbbbbbbbbbbbbbb =\n"
 	 "       #d{aaaaaaaaaaaaaaaaaaaa = a,bbbbbbbbbbbbbbbbbbbb = b,\n"
 	 "          cccccccccccccccccccc = c,dddddddddddddddddddd = d,\n"
 	 "          eeeeeeeeeeeeeeeeeeee = e},\n"
 	 "   cccccccccccccccccccc = 3,\n"
-	 "   dddddddddddddddddddd = \n"
+	 "   dddddddddddddddddddd =\n"
 	 "       #d{aaaaaaaaaaaaaaaaaaaa = h,bbbbbbbbbbbbbbbbbbbb = i,\n"
-	 "          cccccccccccccccccccc = \n"
+	 "          cccccccccccccccccccc =\n"
 	 "              #d{aaaaaaaaaaaaaaaaaaaa = aa,"
 	 "bbbbbbbbbbbbbbbbbbbb = bb,\n"
-	 "                 cccccccccccccccccccc = \n"
+	 "                 cccccccccccccccccccc =\n"
 	 "                     #d{aaaaaaaaaaaaaaaaaaaa = 1,"
 	 "bbbbbbbbbbbbbbbbbbbb = 2,\n"
 	 "                        cccccccccccccccccccc = 3,"
@@ -534,21 +534,21 @@ otp_6354(Config) when is_list(Config) ->
        p({A,{A,{A,{A,{A,{A,{A,
 			    {g,{h,{i,{j,{k,{l,{m,{n,{o,{a}}}}}}}}}}}}}}}}}, 100)),
     bt(<<"#c{\n"
-	 " f1 = \n"
+	 " f1 =\n"
 	 "  #c{\n"
-	 "   f1 = \n"
+	 "   f1 =\n"
 	 "    #c{\n"
-	 "     f1 = \n"
+	 "     f1 =\n"
 	 "      #c{\n"
-	 "       f1 = \n"
+	 "       f1 =\n"
 	 "        #c{\n"
-	 "         f1 = \n"
+	 "         f1 =\n"
 	 "          #c{\n"
-	 "           f1 = \n"
+	 "           f1 =\n"
 	 "            #c{\n"
-	 "             f1 = \n"
+	 "             f1 =\n"
 	 "              #c{\n"
-	 "               f1 = \n"
+	 "               f1 =\n"
 	 "                #c{\n"
 	 "                 f1 = #c{f1 = #c{f1 = #c{f1 = a,"
 	 "f2 = b},f2 = b},f2 = b},\n"
@@ -564,13 +564,13 @@ otp_6354(Config) when is_list(Config) ->
        p({c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,a,b},b},b},b},b},b},
 			 b},b},b},b},b},b}, -1)),
     bt(<<"#rrrrr{\n"
-	 " f1 = \n"
+	 " f1 =\n"
 	 "  #rrrrr{\n"
-	 "   f1 = \n"
+	 "   f1 =\n"
 	 "    #rrrrr{\n"
-	 "     f1 = \n"
+	 "     f1 =\n"
 	 "      #rrrrr{\n"
-	 "       f1 = \n"
+	 "       f1 =\n"
 	 "        {rrrrr,{rrrrr,a,#rrrrr{f1 = {rrrrr,1,2},f2 = a,"
 	 "f3 = b}},b},\n"
 	 "       f2 = {rrrrr,c,d},\n"
@@ -2106,3 +2106,200 @@ coverage(_Config) ->
     io:format("~s\n", [S2]),
 
     ok.
+
+otp_14175(_Config) ->
+    "..." = p(#{}, 0),
+    "#{}" = p(#{}, 1),
+    "#{...}" = p(#{a => 1}, 1),
+    "#{#{} => a}" = p(#{#{} => a}, 2),
+    "#{a => 1,...}" = p(#{a => 1, b => 2}, 2),
+    "#{a => 1,b => 2}" = p(#{a => 1, b => 2}, -1),
+
+    M = #{kaaaaaaaaaaaaaaaaaaa => v1,kbbbbbbbbbbbbbbbbbbb => v2,
+          kccccccccccccccccccc => v3,kddddddddddddddddddd => v4,
+          keeeeeeeeeeeeeeeeeee => v5},
+    "#{...}" = p(M, 1),
+    mt("#{kaaaaaaaaaaaaaaaaaaaa => v1,...}", p(M, 2)),
+    mt("#{kaaaaaaaaaaaaaaaaaaaa => 1,kbbbbbbbbbbbbbbbbbbbb => 2,...}",
+       p(M, 3)),
+
+    mt("#{kaaaaaaaaaaaaaaaaaaa => v1,kbbbbbbbbbbbbbbbbbbb => v2,\n"
+       "  kccccccccccccccccccc => v3,...}", p(M, 4)),
+
+    mt("#{kaaaaaaaaaaaaaaaaaaa => v1,kbbbbbbbbbbbbbbbbbbb => v2,\n"
+       "  kccccccccccccccccccc => v3,kddddddddddddddddddd => v4,...}",
+       p(M, 5)),
+
+    mt("#{kaaaaaaaaaaaaaaaaaaa => v1,kbbbbbbbbbbbbbbbbbbb => v2,\n"
+       "  kccccccccccccccccccc => v3,kddddddddddddddddddd => v4,\n"
+       "  keeeeeeeeeeeeeeeeeee => v5}", p(M, 6)),
+
+    weak("#{aaaaaaaaaaaaaaaaaaa => 1,bbbbbbbbbbbbbbbbbbbb => 2,\n"
+         "  cccccccccccccccccccc => {3},\n"
+         "  dddddddddddddddddddd => 4,eeeeeeeeeeeeeeeeeeee => 5}",
+       p(#{aaaaaaaaaaaaaaaaaaa => 1,bbbbbbbbbbbbbbbbbbbb => 2,
+           cccccccccccccccccccc => {3},
+           dddddddddddddddddddd => 4,eeeeeeeeeeeeeeeeeeee => 5}, -1)),
+
+    M2 = #{dddddddddddddddddddd => {1}, {aaaaaaaaaaaaaaaaaaaa} => 2,
+           {bbbbbbbbbbbbbbbbbbbb} => 3,{cccccccccccccccccccc} => 4,
+           {eeeeeeeeeeeeeeeeeeee} => 5},
+    "#{...}" = p(M2, 1),
+    weak("#{dddddddddddddddddddd => {...},...}", p(M2, 2)),
+    weak("#{dddddddddddddddddddd => {1},{...} => 2,...}", p(M2, 3)),
+
+    weak("#{dddddddddddddddddddd => {1},\n"
+         "  {aaaaaaaaaaaaaaaaaaaa} => 2,\n"
+         "  {...} => 3,...}", p(M2, 4)),
+
+    weak("#{dddddddddddddddddddd => {1},\n"
+         "  {aaaaaaaaaaaaaaaaaaaa} => 2,\n"
+         "  {bbbbbbbbbbbbbbbbbbbb} => 3,\n"
+         "  {...} => 4,...}", p(M2, 5)),
+
+    weak("#{dddddddddddddddddddd => {1},\n"
+         "  {aaaaaaaaaaaaaaaaaaaa} => 2,\n"
+         "  {bbbbbbbbbbbbbbbbbbbb} => 3,\n"
+         "  {cccccccccccccccccccc} => 4,\n"
+         "  {...} => 5}", p(M2, 6)),
+
+    weak("#{dddddddddddddddddddd => {1},\n"
+         "  {aaaaaaaaaaaaaaaaaaaa} => 2,\n"
+         "  {bbbbbbbbbbbbbbbbbbbb} => 3,\n"
+         "  {cccccccccccccccccccc} => 4,\n"
+         "  {eeeeeeeeeeeeeeeeeeee} => 5}", p(M2, 7)),
+
+    M3 = #{kaaaaaaaaaaaaaaaaaaa => vuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,
+           kbbbbbbbbbbbbbbbbbbb => vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv,
+           kccccccccccccccccccc => vxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+           kddddddddddddddddddd => vyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy,
+           keeeeeeeeeeeeeeeeeee => vzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz},
+
+    mt("#{aaaaaaaaaaaaaaaaaaaa =>\n"
+       "      uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,\n"
+       "  bbbbbbbbbbbbbbbbbbbb =>\n"
+       "      vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv,\n"
+       "  cccccccccccccccccccc =>\n"
+       "      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,\n"
+       "  dddddddddddddddddddd =>\n"
+       "      yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy,\n"
+       "  eeeeeeeeeeeeeeeeeeee =>\n"
+       "      zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz}", p(M3, -1)),
+
+    R4 = {c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,{c,a,b},b},b},b},b},b},
+			 b},b},b},b},b},b},
+    M4 = #{aaaaaaaaaaaaaaaaaaaa => R4,
+           bbbbbbbbbbbbbbbbbbbb => R4,
+           cccccccccccccccccccc => R4,
+           dddddddddddddddddddd => R4,
+           eeeeeeeeeeeeeeeeeeee => R4},
+
+    weak("#{aaaaaaaaaaaaaaaaaaaa =>\n"
+         "      #c{f1 = #c{f1 = #c{...},f2 = b},f2 = b},\n"
+         "  bbbbbbbbbbbbbbbbbbbb => #c{f1 = #c{f1 = {...},...},f2 = b},\n"
+         "  cccccccccccccccccccc => #c{f1 = #c{...},f2 = b},\n"
+         "  dddddddddddddddddddd => #c{f1 = {...},...},\n"
+         "  eeeeeeeeeeeeeeeeeeee => #c{...}}", p(M4, 7)),
+
+    M5 = #{aaaaaaaaaaaaaaaaaaaa => R4},
+    mt("#{aaaaaaaaaaaaaaaaaaaa =>\n"
+       "   #c{\n"
+       "    f1 =\n"
+       "     #c{\n"
+       "      f1 =\n"
+       "       #c{\n"
+       "        f1 =\n"
+       "         #c{\n"
+       "          f1 =\n"
+       "           #c{\n"
+       "            f1 =\n"
+       "             #c{\n"
+       "              f1 =\n"
+       "               #c{\n"
+       "                f1 =\n"
+       "                 #c{\n"
+       "                  f1 =\n"
+       "                   #c{\n"
+       "                    f1 = #c{f1 = #c{f1 = #c{f1 = a,f2 = b},f2 = b},"
+                                        "f2 = b},\n"
+       "                    f2 = b},\n"
+       "                  f2 = b},\n"
+       "                f2 = b},\n"
+       "              f2 = b},\n"
+       "            f2 = b},\n"
+       "          f2 = b},\n"
+       "        f2 = b},\n"
+       "      f2 = b},\n"
+       "    f2 = b}}", p(M5, -1)),
+    ok.
+
+%% Just check number of newlines and dots ('...').
+-define(WEAK, true).
+
+-ifdef(WEAK).
+
+weak(S, R) ->
+    (nl(S) =:= nl(R) andalso
+     dots(S) =:= dots(S)).
+
+nl(S) ->
+    [C || C <- S, C =:= $\n].
+
+dots(S) ->
+    [C || C <- S, C =:= $\.].
+
+-else. % WEAK
+
+weak(S, R) ->
+    mt(S, R).
+
+-endif. % WEAK
+
+%% If EXACT is defined: mt() matches strings exactly.
+%%
+%% if EXACT is not defined: do not match the strings exactly, but
+%% compare them assuming that all map keys and all map values are
+%% equal (by assuming all map keys and all map values have the same
+%% length and begin with $k and $v respectively).
+
+%-define(EXACT, true).
+
+-ifdef(EXACT).
+
+mt(S, R) ->
+    S =:= R.
+
+-else. % EXACT
+
+mt(S, R) ->
+    anon(S) =:= anon(R).
+
+anon(S) ->
+    {ok, Ts0, _} = erl_scan:string(S, 1, [text]),
+    Ts = anon1(Ts0),
+    text(Ts).
+
+anon1([]) -> [];
+anon1([{atom,Anno,Atom}=T|Ts]) ->
+    case erl_anno:text(Anno) of
+        "k" ++ _ ->
+            NewAnno = erl_anno:set_text("key", Anno),
+            [{atom,NewAnno,Atom}|anon1(Ts)];
+        "v" ++ _ ->
+            NewAnno = erl_anno:set_text("val", Anno),
+            [{atom,NewAnno,Atom}|anon1(Ts)];
+        _ ->
+            [T|anon1(Ts)]
+    end;
+anon1([T|Ts]) ->
+    [T|anon1(Ts)].
+
+text(Ts) ->
+    lists:append(text1(Ts)).
+
+text1([]) -> [];
+text1([T|Ts]) ->
+    Anno = element(2, T),
+    [erl_anno:text(Anno) | text1(Ts)].
+
+-endif. % EXACT
-- 
cgit v1.2.3