From 675b8108486919739dc2e213587489c0daab80cb Mon Sep 17 00:00:00 2001 From: Hans Bolinder Date: Sun, 13 Jan 2013 18:19:29 +0100 Subject: [stdlib] Add control sequence modifier 'l' The modifier 'l' can be used for turning off the string recognition of ~p and ~P. --- lib/stdlib/doc/src/io.xml | 67 +++++++++++++++---------- lib/stdlib/src/erl_lint.erl | 6 +++ lib/stdlib/src/io_lib_format.erl | 86 ++++++++++++++++++-------------- lib/stdlib/src/io_lib_pretty.erl | 104 ++++++++++++++++++++------------------- lib/stdlib/test/io_SUITE.erl | 42 +++++++++++++++- 5 files changed, 189 insertions(+), 116 deletions(-) diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml index fa475804eb..9f59fda6b5 100644 --- a/lib/stdlib/doc/src/io.xml +++ b/lib/stdlib/doc/src/io.xml @@ -390,10 +390,11 @@ ok applicable, it is used for both the field width and precision. The default padding character is ' ' (space).

Mod is the control sequence modifier. It is either a - single character (currently only t, for Unicode translation, - is supported) that changes the interpretation of Data.

- -

The following control sequences are available:

+ single character (currently only t, for Unicode + translation, and l, for stopping p and + P from detecting printable characters, are supported) + that changes the interpretation of Data.

+

The following control sequences are available:

~ @@ -407,7 +408,7 @@ ok which in turn defaults to 1. The following example illustrates:

-2> io:fwrite("|~10.5c|~-10.5c|~5c|~n", [$a, $b, $c]).
+1> io:fwrite("|~10.5c|~-10.5c|~5c|~n", [$a, $b, $c]).
 |     aaaaa|bbbbb     |ccccc|
 ok

If the Unicode translation modifier (t) is in effect, @@ -415,10 +416,10 @@ ok valid Unicode codepoint, otherwise it should be an integer less than or equal to 255, otherwise it is masked with 16#FF:

-1> io:fwrite("~tc~n",[1024]).
+2> io:fwrite("~tc~n",[1024]).
 \x{400}
 ok
-2> io:fwrite("~c~n",[1024]).
+3> io:fwrite("~c~n",[1024]).
 ^@
 ok
@@ -462,20 +463,20 @@ ok

This format can be used for printing any object and truncating the output so it fits a specified field:

-3> io:fwrite("|~10w|~n", [{hey, hey, hey}]).
+1> io:fwrite("|~10w|~n", [{hey, hey, hey}]).
 |**********|
 ok
-4> io:fwrite("|~10s|~n", [io_lib:write({hey, hey, hey})]).
+2> io:fwrite("|~10s|~n", [io_lib:write({hey, hey, hey})]).
 |{hey,hey,h|
-5> io:fwrite("|~-10.8s|~n", [io_lib:write({hey, hey, hey})]).
+3> io:fwrite("|~-10.8s|~n", [io_lib:write({hey, hey, hey})]).
 |{hey,hey  |
 ok

A list with integers larger than 255 is considered an error if the Unicode translation modifier is not given:

-1> io:fwrite("~ts~n",[[1024]]).
+4> io:fwrite("~ts~n",[[1024]]).
 \x{400}
 ok
-2> io:fwrite("~s~n",[[1024]]).
+5> io:fwrite("~s~n",[[1024]]).
 ** exception exit: {badarg,[{io,format,[<0.26.0>,"~s~n",[[1024]]]},
    ...
@@ -496,17 +497,17 @@ ok printable characters and to output these as strings. For example:

-5> T = [{attributes,[[{id,age,1.50000},{mode,explicit},
+1> T = [{attributes,[[{id,age,1.50000},{mode,explicit},
 {typename,"INTEGER"}], [{id,cho},{mode,explicit},{typename,'Cho'}]]},
 {typename,'Person'},{tag,{'PRIVATE',3}},{mode,implicit}].
 ...
-6> io:fwrite("~w~n", [T]).
+2> io:fwrite("~w~n", [T]).
 [{attributes,[[{id,age,1.5},{mode,explicit},{typename,
 [73,78,84,69,71,69,82]}],[{id,cho},{mode,explicit},{typena
 me,'Cho'}]]},{typename,'Person'},{tag,{'PRIVATE',3}},{mode
 ,implicit}]
 ok
-7> io:fwrite("~62p~n", [T]).
+3> io:fwrite("~62p~n", [T]).
 [{attributes,[[{id,age,1.5},
                {mode,explicit},
                {typename,"INTEGER"}],
@@ -522,7 +523,7 @@ ok
io:fwrite or io:format. For example, using T above:

-8> io:fwrite("Here T = ~62p~n", [T]).
+4> io:fwrite("Here T = ~62p~n", [T]).
 Here T = [{attributes,[[{id,age,1.5},
                         {mode,explicit},
                         {typename,"INTEGER"}],
@@ -532,6 +533,18 @@ Here T = [{attributes,[[{id,age,1.5},
           {typename,'Person'},
           {tag,{'PRIVATE',3}},
           {mode,implicit}]
+ok
+

When the modifier l is given no detection of + printable character lists will take place. For example:

+
+5> S = [{a,"a"}, {b, "b"}].
+6> io:fwrite("~15p~n", [S]).
+[{a,"a"},
+ {b,"b"}]
+ok
+7> io:fwrite("~15lp~n", [S]).
+[{a,[97]},
+ {b,[98]}]
 ok
W @@ -541,7 +554,7 @@ ok are printed. Anything below this depth is replaced with .... For example, using T above:

-9> io:fwrite("~W~n", [T,9]).
+8> io:fwrite("~W~n", [T,9]).
 [{attributes,[[{id,age,1.5},{mode,explicit},{typename,...}],
 [{id,cho},{mode,...},{...}]]},{typename,'Person'},
 {tag,{'PRIVATE',3}},{mode,implicit}]
@@ -558,7 +571,7 @@ ok
are printed. Anything below this depth is replaced with .... For example:

-10> io:fwrite("~62P~n", [T,9]).
+9> io:fwrite("~62P~n", [T,9]).
 [{attributes,[[{id,age,1.5},{mode,explicit},{typename,...}],
               [{id,cho},{mode,...},{...}]]},
  {typename,'Person'},
@@ -572,13 +585,13 @@ ok
10. A leading dash is printed for negative integers.

The precision field selects base. For example:

-11> io:fwrite("~.16B~n", [31]).
+1> io:fwrite("~.16B~n", [31]).
 1F
 ok
-12> io:fwrite("~.2B~n", [-19]).
+2> io:fwrite("~.2B~n", [-19]).
 -10011
 ok
-13> io:fwrite("~.36B~n", [5*36+35]).
+3> io:fwrite("~.36B~n", [5*36+35]).
 5Z
 ok
@@ -590,10 +603,10 @@ ok

The prefix can be a possibly deep list of characters or an atom.

-14> io:fwrite("~X~n", [31,"10#"]).
+1> io:fwrite("~X~n", [31,"10#"]).
 10#31
 ok
-15> io:fwrite("~.16X~n", [-31,"0x"]).
+2> io:fwrite("~.16X~n", [-31,"0x"]).
 -0x1F
 ok
@@ -602,10 +615,10 @@ ok

Like B, but prints the number with an Erlang style #-separated base prefix.

-16> io:fwrite("~.10#~n", [31]).
+1> io:fwrite("~.10#~n", [31]).
 10#31
 ok
-17> io:fwrite("~.16#~n", [-31]).
+2> io:fwrite("~.16#~n", [-31]).
 -16#1F
 ok
@@ -639,10 +652,10 @@ ok

If an error occurs, there is no output. For example:

-18> io:fwrite("~s ~w ~i ~w ~c ~n",['abc def', 'abc def', {foo, 1},{foo, 1}, 65]).
+1> io:fwrite("~s ~w ~i ~w ~c ~n",['abc def', 'abc def', {foo, 1},{foo, 1}, 65]).
 abc def 'abc def'  {foo,1} A
 ok
-19> io:fwrite("~s", [65]).
+2> io:fwrite("~s", [65]).
 ** exception exit: {badarg,[{io,format,[<0.22.0>,"~s","A"]},
                             {erl_eval,do_apply,5},
                             {shell,exprs,6},
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 12505b33d1..68a8534f15 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -3483,6 +3483,12 @@ extract_sequence(4, [$t, $P | Fmt], Need) ->
     extract_sequence(5, [$P|Fmt], Need);
 extract_sequence(4, [$t, C | _Fmt], _Need) ->
     {error,"invalid control ~t" ++ [C]};
+extract_sequence(4, [$l, $p | Fmt], Need) ->
+    extract_sequence(5, [$p|Fmt], Need);
+extract_sequence(4, [$l, $P | Fmt], Need) ->
+    extract_sequence(5, [$P|Fmt], Need);
+extract_sequence(4, [$l, C | _Fmt], _Need) ->
+    {error,"invalid control ~l" ++ [C]};
 extract_sequence(4, Fmt, Need) ->
     extract_sequence(5, Fmt, Need);
 extract_sequence(5, [C|Fmt], Need0) ->
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index 64d19ccf48..19eccf9355 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -58,14 +58,22 @@ collect_cseq(Fmt0, Args0) ->
     {P,Fmt2,Args2} = precision(Fmt1, Args1),
     {Pad,Fmt3,Args3} = pad_char(Fmt2, Args2),
     {Encoding,Fmt4,Args4} = encoding(Fmt3, Args3),
-    {C,As,Fmt5,Args5} = collect_cc(Fmt4, Args4),
-    {{C,As,F,Ad,P,Pad,Encoding},Fmt5,Args5}.
+    {Strings,Fmt5,Args5} = pretty_lists(Fmt4, Args4),
+    {C,As,Fmt6,Args6} = collect_cc(Fmt5, Args5),
+    {{C,As,F,Ad,P,Pad,Encoding,Strings},Fmt6,Args6}.
 
 encoding([$t|Fmt],Args) ->
+    true = hd(Fmt) =/= $l,
     {unicode,Fmt,Args};
 encoding(Fmt,Args) ->
     {latin1,Fmt,Args}.
 
+pretty_lists([$l|Fmt],Args) ->
+    true = hd(Fmt) =/= $t,
+    {no,Fmt,Args};
+pretty_lists(Fmt,Args) ->
+    {unicode,Fmt,Args}.
+
 field_width([$-|Fmt0], Args0) ->
     {F,Fmt,Args} = field_value(Fmt0, Args0),
     field_width(-F, Fmt, Args);
@@ -128,8 +136,8 @@ collect_cc([$i|Fmt], [A|Args]) -> {$i,[A],Fmt,Args}.
 
 pcount(Cs) -> pcount(Cs, 0).
 
-pcount([{$p,_As,_F,_Ad,_P,_Pad,_Enc}|Cs], Acc) -> pcount(Cs, Acc+1);
-pcount([{$P,_As,_F,_Ad,_P,_Pad,_Enc}|Cs], Acc) -> pcount(Cs, Acc+1);
+pcount([{$p,_As,_F,_Ad,_P,_Pad,_Enc,_Str}|Cs], Acc) -> pcount(Cs, Acc+1);
+pcount([{$P,_As,_F,_Ad,_P,_Pad,_Enc,_Str}|Cs], Acc) -> pcount(Cs, Acc+1);
 pcount([_|Cs], Acc) -> pcount(Cs, Acc);
 pcount([], Acc) -> Acc.
 
@@ -138,8 +146,8 @@ pcount([], Acc) -> Acc.
 %%  remaining and only calculate indentation when necessary. Must also
 %%  be smart when calculating indentation for characters in format.
 
-build([{C,As,F,Ad,P,Pad,Enc}|Cs], Pc0, I) ->
-    S = control(C, As, F, Ad, P, Pad, Enc, I),
+build([{C,As,F,Ad,P,Pad,Enc,Str}|Cs], Pc0, I) ->
+    S = control(C, As, F, Ad, P, Pad, Enc, Str, I),
     Pc1 = decr_pc(C, Pc0),
     if
 	Pc1 > 0 -> [S|build(Cs, Pc1, indentation(S, I))];
@@ -171,59 +179,59 @@ indentation([], I) -> I.
 %%  This is the main dispatch function for the various formatting commands.
 %%  Field widths and precisions have already been calculated.
 
-control($w, [A], F, Adj, P, Pad, _Enc,_I) ->
+control($w, [A], F, Adj, P, Pad, _Enc, _Str, _I) ->
     term(io_lib:write(A, -1), F, Adj, P, Pad);
-control($p, [A], F, Adj, P, Pad, Enc, I) ->
-    print(A, -1, F, Adj, P, Pad, Enc, I);
-control($W, [A,Depth], F, Adj, P, Pad, _Enc, _I) when is_integer(Depth) ->
+control($p, [A], F, Adj, P, Pad, Enc, Str, I) ->
+    print(A, -1, F, Adj, P, Pad, Enc, Str, I);
+control($W, [A,Depth], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(Depth) ->
     term(io_lib:write(A, Depth), F, Adj, P, Pad);
-control($P, [A,Depth], F, Adj, P, Pad, Enc, I) when is_integer(Depth) ->
-    print(A, Depth, F, Adj, P, Pad, Enc, I);
-control($s, [A], F, Adj, P, Pad, _Enc, _I) when is_atom(A) ->
+control($P, [A,Depth], F, Adj, P, Pad, Enc, Str, I) when is_integer(Depth) ->
+    print(A, Depth, F, Adj, P, Pad, Enc, Str, I);
+control($s, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_atom(A) ->
     string(atom_to_list(A), F, Adj, P, Pad);
-control($s, [L0], F, Adj, P, Pad, latin1, _I) ->
+control($s, [L0], F, Adj, P, Pad, latin1, _Str, _I) ->
     L = iolist_to_chars(L0),
     string(L, F, Adj, P, Pad);
-control($s, [L0], F, Adj, P, Pad, unicode, _I) ->
+control($s, [L0], F, Adj, P, Pad, unicode, _Str, _I) ->
     L = cdata_to_chars(L0),
     uniconv(string(L, F, Adj, P, Pad));
-control($e, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+control($e, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) ->
     fwrite_e(A, F, Adj, P, Pad);
-control($f, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+control($f, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) ->
     fwrite_f(A, F, Adj, P, Pad);
-control($g, [A], F, Adj, P, Pad, _Enc, _I) when is_float(A) ->
+control($g, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) ->
     fwrite_g(A, F, Adj, P, Pad);
-control($b, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($b, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     unprefixed_integer(A, F, Adj, base(P), Pad, true);
-control($B, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($B, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     unprefixed_integer(A, F, Adj, base(P), Pad, false);
-control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A), 
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A),
                                                  is_atom(Prefix) ->
     prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), true);
-control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
     prefixed_integer(A, F, Adj, base(P), Pad, Prefix, true);
-control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A), 
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A),
                                                  is_atom(Prefix) ->
     prefixed_integer(A, F, Adj, base(P), Pad, atom_to_list(Prefix), false);
-control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($X, [A,Prefix], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     true = io_lib:deep_char_list(Prefix), %Check if Prefix a character list
     prefixed_integer(A, F, Adj, base(P), Pad, Prefix, false);
-control($+, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($+, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     Base = base(P),
     Prefix = [integer_to_list(Base), $#],
     prefixed_integer(A, F, Adj, Base, Pad, Prefix, true);
-control($#, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($#, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     Base = base(P),
     Prefix = [integer_to_list(Base), $#],
     prefixed_integer(A, F, Adj, Base, Pad, Prefix, false);
-control($c, [A], F, Adj, P, Pad, unicode, _I) when is_integer(A) ->
+control($c, [A], F, Adj, P, Pad, unicode, _Str, _I) when is_integer(A) ->
     char(A, F, Adj, P, Pad);
-control($c, [A], F, Adj, P, Pad, _Enc, _I) when is_integer(A) ->
+control($c, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) ->
     char(A band 255, F, Adj, P, Pad);
-control($~, [], F, Adj, P, Pad, _Enc, _I) -> char($~, F, Adj, P, Pad);
-control($n, [], F, Adj, P, Pad, _Enc, _I) -> newline(F, Adj, P, Pad);
-control($i, [_A], _F, _Adj, _P, _Pad, _Enc, _I) -> [].
+control($~, [], F, Adj, P, Pad, _Enc, _Str, _I) -> char($~, F, Adj, P, Pad);
+control($n, [], F, Adj, P, Pad, _Enc, _Str, _I) -> newline(F, Adj, P, Pad);
+control($i, [_A], _F, _Adj, _P, _Pad, _Enc, _Str, _I) -> [].
 
 -ifdef(UNICODE_AS_BINARIES).
 uniconv(C) ->
@@ -259,12 +267,16 @@ term(T, F, Adj, P0, Pad) ->
 %%       Indentation)
 %%  Print a term.
 
-print(T, D, none, Adj, P, Pad, E, I) -> print(T, D, 80, Adj, P, Pad, E, I);
-print(T, D, F, Adj, none, Pad, E, I) -> print(T, D, F, Adj, I+1, Pad, E, I);
-print(T, D, F, right, P, _Pad, latin1, _I) ->
-    io_lib_pretty:print(T, P, F, D);
-print(T, D, F, right, P, _Pad, Enc, _I) ->
-    Options = [{column, P}, {line_length, F}, {depth, D}, {encoding, Enc}],
+print(T, D, none, Adj, P, Pad, E, Str, I) ->
+    print(T, D, 80, Adj, P, Pad, E, Str, I);
+print(T, D, F, Adj, none, Pad, E, Str, I) ->
+    print(T, D, F, Adj, I+1, Pad, E, Str, I);
+print(T, D, F, right, P, _Pad, Enc, Str, _I) ->
+    Options = [{column, P},
+               {line_length, F},
+               {depth, D},
+               {encoding, Enc},
+               {strings, Str}],
     io_lib_pretty:print(T, Options).
 
 %% fwrite_e(Float, Field, Adjust, Precision, PadChar)
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index b05db3d290..c7d2ad97c7 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -56,6 +56,7 @@ print(Term) ->
                 | {depth, depth()}
                 | {max_chars, max_chars()}
                 | {record_print_fun, rec_print_fun()}
+                | {strings, io_lib:pretty_lists()}
                 | {encoding, latin1 | utf8 | unicode}.
 -type options() :: [option()].
 
@@ -69,7 +70,8 @@ print(Term, Options) when is_list(Options) ->
     M = proplists:get_value(max_chars, Options, -1),
     RecDefFun = proplists:get_value(record_print_fun, Options, no_fun),
     Encoding = proplists:get_value(encoding, Options, epp:default_encoding()),
-    print(Term, Col, Ll, D, M, RecDefFun, Encoding);
+    Strings = proplists:get_value(strings, Options, true),
+    print(Term, Col, Ll, D, M, RecDefFun, Encoding, Strings);
 print(Term, RecDefFun) ->
     print(Term, -1, RecDefFun).
 
@@ -81,7 +83,7 @@ print(Term, Depth, RecDefFun) ->
 -spec print(term(), column(), line_length(), depth()) -> chars().
 
 print(Term, Col, Ll, D) ->
-    print(Term, Col, Ll, D, _M=-1, no_fun, latin1).
+    print(Term, Col, Ll, D, _M=-1, no_fun, latin1, true).
 
 -spec print(term(), column(), line_length(), depth(), rec_print_fun()) ->
                    chars().
@@ -92,15 +94,15 @@ print(Term, Col, Ll, D, RecDefFun) ->
             rec_print_fun()) -> chars().
 
 print(Term, Col, Ll, D, M, RecDefFun) ->
-    print(Term, Col, Ll, D, M, RecDefFun, latin1).
-
-print(_, _, _, 0, _M, _RF, _Enc) -> "...";
-print(Term, Col, Ll, D, M, RecDefFun, Enc) when Col =< 0 ->
-    print(Term, 1, Ll, D, M, RecDefFun, Enc);
-print(Term, Col, Ll, D, M0, RecDefFun, Enc) when is_tuple(Term);
-                                                 is_list(Term);
-                                                 is_bitstring(Term) ->
-    If = {_S, Len} = print_length(Term, D, RecDefFun, Enc),
+    print(Term, Col, Ll, D, M, RecDefFun, latin1, true).
+
+print(_, _, _, 0, _M, _RF, _Enc, _Str) -> "...";
+print(Term, Col, Ll, D, M, RecDefFun, Enc, Str) when Col =< 0 ->
+    print(Term, 1, Ll, D, M, RecDefFun, Enc, Str);
+print(Term, Col, Ll, D, M0, RecDefFun, Enc, Str) when is_tuple(Term);
+                                                      is_list(Term);
+                                                      is_bitstring(Term) ->
+    If = {_S, Len} = print_length(Term, D, RecDefFun, Enc, Str),
     M = max_cs(M0, Len),
     if
         Len < Ll - Col, Len =< M ->
@@ -111,7 +113,7 @@ print(Term, Col, Ll, D, M0, RecDefFun, Enc) when is_tuple(Term);
                               1),
             pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
     end;
-print(Term, _Col, _Ll, _D, _M, _RF, _Enc) ->
+print(Term, _Col, _Ll, _D, _M, _RF, _Enc, _Str) ->
     io_lib:write(Term).
 
 %%%
@@ -325,12 +327,12 @@ write_tail(E, S) ->
 %% counted but need to be added later.
 
 %% D =/= 0
-print_length([], _D, _RF, _Enc) ->
+print_length([], _D, _RF, _Enc, _Str) ->
     {"[]", 2};
-print_length({}, _D, _RF, _Enc) ->
+print_length({}, _D, _RF, _Enc, _Str) ->
     {"{}", 2};
-print_length(List, D, RF, Enc) when is_list(List) ->
-    case printable_list(List, D, Enc) of
+print_length(List, D, RF, Enc, Str) when is_list(List) ->
+    case Str =:= unicode andalso printable_list(List, D, Enc) of
         true ->
             S = write_string(List, Enc),
             {S, length(S)};
@@ -339,30 +341,30 @@ print_length(List, D, RF, Enc) when is_list(List) ->
         %    S = write_string(Prefix, Enc),
         %    {[S | "..."], 3 + length(S)};
         false ->
-            print_length_list(List, D, RF, Enc)
+            print_length_list(List, D, RF, Enc, Str)
     end;
-print_length(Fun, _D, _RF, _Enc) when is_function(Fun) ->
+print_length(Fun, _D, _RF, _Enc, _Str) when is_function(Fun) ->
     S = io_lib:write(Fun),
     {S, iolist_size(S)};
-print_length(R, D, RF, Enc) when is_atom(element(1, R)),
-                                 is_function(RF) ->
+print_length(R, D, RF, Enc, Str) when is_atom(element(1, R)),
+                                      is_function(RF) ->
     case RF(element(1, R), tuple_size(R) - 1) of
         no -> 
-            print_length_tuple(R, D, RF, Enc);
+            print_length_tuple(R, D, RF, Enc, Str);
         RDefs ->
-            print_length_record(R, D, RF, RDefs, Enc)
+            print_length_record(R, D, RF, RDefs, Enc, Str)
     end;
-print_length(Tuple, D, RF, Enc) when is_tuple(Tuple) ->
-    print_length_tuple(Tuple, D, RF, Enc);
-print_length(<<>>, _D, _RF, _Enc) ->
+print_length(Tuple, D, RF, Enc, Str) when is_tuple(Tuple) ->
+    print_length_tuple(Tuple, D, RF, Enc, Str);
+print_length(<<>>, _D, _RF, _Enc, _Str) ->
     {"<<>>", 4};
-print_length(<<_/bitstring>>, 1, _RF, _Enc) ->
+print_length(<<_/bitstring>>, 1, _RF, _Enc, _Str) ->
     {"<<...>>", 7};
-print_length(<<_/bitstring>>=Bin, D, _RF, Enc) ->
+print_length(<<_/bitstring>>=Bin, D, _RF, Enc, Str) ->
     case bit_size(Bin) rem 8 of
         0 ->
 	    D1 = D - 1, 
-	    case printable_bin(Bin, D1, Enc) of
+	    case Str =:= unicode andalso printable_bin(Bin, D1, Enc) of
                 {true, List} when is_list(List) ->
                     S = io_lib:write_string(List, $"), %"
 	            {[$<,$<,S,$>,$>], 4 + length(S)};
@@ -383,51 +385,53 @@ print_length(<<_/bitstring>>=Bin, D, _RF, Enc) ->
            S = io_lib:write(Bin, D),
 	   {{bin,S}, iolist_size(S)}
     end;    
-print_length(Term, _D, _RF, _Enc) ->
+print_length(Term, _D, _RF, _Enc, _Str) ->
     S = io_lib:write(Term),
     {S, lists:flatlength(S)}.
 
-print_length_tuple(_Tuple, 1, _RF, _Enc) ->
+print_length_tuple(_Tuple, 1, _RF, _Enc, _Str) ->
     {"{...}", 5};
-print_length_tuple(Tuple, D, RF, Enc) ->
-    L = print_length_list1(tuple_to_list(Tuple), D, RF, Enc),
+print_length_tuple(Tuple, D, RF, Enc, Str) ->
+    L = print_length_list1(tuple_to_list(Tuple), D, RF, Enc, Str),
     IsTagged = is_atom(element(1, Tuple)) and (tuple_size(Tuple) > 1),
     {{tuple,IsTagged,L}, list_length(L, 2)}.
 
-print_length_record(_Tuple, 1, _RF, _RDefs, _Enc) ->
+print_length_record(_Tuple, 1, _RF, _RDefs, _Enc, _Str) ->
     {"{...}", 5};
-print_length_record(Tuple, D, RF, RDefs, Enc) ->
+print_length_record(Tuple, D, RF, RDefs, Enc, Str) ->
     Name = [$# | io_lib:write_atom(element(1, Tuple))],
     NameL = length(Name),
-    L = print_length_fields(RDefs, D - 1, tl(tuple_to_list(Tuple)), RF, Enc),
+    Elements = tl(tuple_to_list(Tuple)),
+    L = print_length_fields(RDefs, D - 1, Elements, RF, Enc, Str),
     {{record, [{Name,NameL} | L]}, list_length(L, NameL + 2)}.
 
-print_length_fields([], _D, [], _RF, _Enc) ->
+print_length_fields([], _D, [], _RF, _Enc, _Str) ->
     [];
-print_length_fields(_, 1, _, _RF, _Enc) ->
+print_length_fields(_, 1, _, _RF, _Enc, _Str) ->
     {dots, 3};
-print_length_fields([Def | Defs], D, [E | Es], RF, Enc) ->
-    [print_length_field(Def, D - 1, E, RF, Enc) |
-     print_length_fields(Defs, D - 1, Es, RF, Enc)].
+print_length_fields([Def | Defs], D, [E | Es], RF, Enc, Str) ->
+    [print_length_field(Def, D - 1, E, RF, Enc, Str) |
+     print_length_fields(Defs, D - 1, Es, RF, Enc, Str)].
 
-print_length_field(Def, D, E, RF, Enc) ->
+print_length_field(Def, D, E, RF, Enc, Str) ->
     Name = io_lib:write_atom(Def),
-    {S, L} = print_length(E, D, RF, Enc),
+    {S, L} = print_length(E, D, RF, Enc, Str),
     NameL = length(Name) + 3,
     {{field, Name, NameL, {S, L}}, NameL + L}.
 
-print_length_list(List, D, RF, Enc) ->
-    L = print_length_list1(List, D, RF, Enc),
+print_length_list(List, D, RF, Enc, Str) ->
+    L = print_length_list1(List, D, RF, Enc, Str),
     {{list, L}, list_length(L, 2)}.
 
-print_length_list1([], _D, _RF, _Enc) ->
+print_length_list1([], _D, _RF, _Enc, _Str) ->
     [];
-print_length_list1(_, 1, _RF, _Enc) ->
+print_length_list1(_, 1, _RF, _Enc, _Str) ->
     {dots, 3};
-print_length_list1([E | Es], D, RF, Enc) ->
-    [print_length(E, D - 1, RF, Enc) | print_length_list1(Es, D - 1, RF, Enc)];
-print_length_list1(E, D, RF, Enc) ->
-    print_length(E, D - 1, RF, Enc).
+print_length_list1([E | Es], D, RF, Enc, Str) ->
+    [print_length(E, D - 1, RF, Enc, Str) |
+     print_length_list1(Es, D - 1, RF, Enc, Str)];
+print_length_list1(E, D, RF, Enc, Str) ->
+    print_length(E, D - 1, RF, Enc, Str).
 
 list_length([], Acc) ->
     Acc;
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 65a112c966..aa698ecaa2 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -29,7 +29,8 @@
          manpage/1, otp_6708/1, otp_7084/1, otp_7421/1,
 	 io_lib_collect_line_3_wb/1, cr_whitespace_in_string/1,
 	 io_fread_newlines/1, otp_8989/1, io_lib_fread_literal/1,
-	 io_lib_print_binary_depth_one/1, otp_10302/1, otp_10836/1]).
+	 io_lib_print_binary_depth_one/1, otp_10302/1, otp_10755/1,
+	 otp_10836/1]).
 
 %-define(debug, true).
 
@@ -65,7 +66,7 @@ all() ->
      manpage, otp_6708, otp_7084, otp_7421,
      io_lib_collect_line_3_wb, cr_whitespace_in_string,
      io_fread_newlines, otp_8989, io_lib_fread_literal,
-     io_lib_print_binary_depth_one, otp_10302, otp_10836].
+     io_lib_print_binary_depth_one, otp_10302, otp_10755, otp_10836].
 
 groups() -> 
     [].
@@ -2085,3 +2086,40 @@ otp_10836(Suite) when is_list(Suite) ->
     S = io_lib:format("~ts", [[<<"äpple"/utf8>>, <<"äpple">>]]),
     "äppleäpple" = lists:flatten(S),
     ok.
+
+otp_10755(doc) ->
+    "OTP-10755. The 'l' modifier";
+otp_10755(Suite) when is_list(Suite) ->
+    S = "string",
+    "\"string\"" = fmt("~p", [S]),
+    "[115,116,114,105,110,103]" = fmt("~lp", [S]),
+    "\"string\"" = fmt("~P", [S, 2]),
+    "[115|...]" = fmt("~lP", [S, 2]),
+    {'EXIT',{badarg,_}} = (catch fmt("~ltp", [S])),
+    {'EXIT',{badarg,_}} = (catch fmt("~tlp", [S])),
+    {'EXIT',{badarg,_}} = (catch fmt("~ltP", [S])),
+    {'EXIT',{badarg,_}} = (catch fmt("~tlP", [S])),
+    Text =
+        "-module(l_mod).\n"
+        "-export([t/0]).\n"
+        "t() ->\n"
+        "    S = \"string\",\n"
+        "    io:format(\"~ltp\", [S]),\n"
+        "    io:format(\"~tlp\", [S]),\n"
+        "    io:format(\"~ltP\", [S, 1]),\n"
+        "    io:format(\"~tlP\", [S, 1]).\n",
+    {ok,l_mod,[{_File,Ws}]} = compile_file("l_mod.erl", Text, Suite),
+    ["format string invalid (invalid control ~lt)",
+     "format string invalid (invalid control ~tl)",
+     "format string invalid (invalid control ~lt)",
+     "format string invalid (invalid control ~tl)"] =
+        [lists:flatten(M:format_error(E)) || {_L,M,E} <- Ws],
+    ok.
+
+compile_file(File, Text, Config) ->
+    PrivDir = ?privdir(Config),
+    Fname = filename:join(PrivDir, File),
+    ok = file:write_file(Fname, Text),
+    try compile:file(Fname, [return])
+    after ok %file:delete(Fname)
+    end.
-- 
cgit v1.2.3