File 2161-Introduce-bfwrite-and-bformat-functions.patch of Package erlang
From 0705d7ed868d54209b5c92efc6a2aeb72a1efe6d Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Wed, 30 Apr 2025 09:03:33 +0200
Subject: [PATCH 1/2] Introduce bfwrite and bformat functions
This can be used to directly create utf8 binaries instead of iolists.
Slightly slower for some usage input but the result uses a lot less
memory on 64b systems.
---
lib/stdlib/src/io_lib.erl | 366 ++++++++++++++++-
lib/stdlib/src/io_lib_format.erl | 407 ++++++++++++++++---
lib/stdlib/src/io_lib_pretty.erl | 663 +++++++++++++++++++++++++------
lib/stdlib/test/io_SUITE.erl | 201 +++++++---
4 files changed, 1393 insertions(+), 244 deletions(-)
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 550142680d..1528b49c8e 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -72,13 +72,17 @@ used for flattening deep lists.
-compile(nowarn_deprecated_catch).
-export([fwrite/2,fwrite/3,fread/2,fread/3,format/2,format/3]).
+-export([bfwrite/2, bfwrite/3, bformat/2, bformat/3]).
-export([scan_format/2,unscan_format/1,build_text/1,build_text/2]).
-export([print/1,print/4,indentation/2]).
--export([write/1,write/2,write/3,nl/0,format_prompt/1,format_prompt/2]).
+-export([write/1,write/2,write/3,write/5,bwrite/2]).
+-export([nl/0,format_prompt/1,format_prompt/2]).
-export([write_binary/3]).
-export([write_atom/1,write_string/1,write_string/2,write_latin1_string/1,
- write_latin1_string/2, write_char/1, write_latin1_char/1]).
+ write_latin1_string/2, write_char/1, write_latin1_char/1,
+ bwrite_string/3
+ ]).
-export([write_atom_as_latin1/1, write_string_as_latin1/1,
write_string_as_latin1/2, write_char_as_latin1/1]).
@@ -100,9 +104,16 @@ used for flattening deep lists.
-export([chars_length/1]).
+-export([write_bin/5, write_string_bin/3, write_binary_bin/4]).
+
-export_type([chars/0, latin1_string/0, continuation/0,
fread_error/0, fread_item/0, format_spec/0, chars_limit/0]).
+-dialyzer([{nowarn_function,
+ [string_bin_escape_unicode/6,
+ string_bin_escape_latin1/6]},
+ no_improper_lists]).
+
%%----------------------------------------------------------------------
-doc "An possibly deep list containing only `t:char/0`s.".
@@ -207,6 +218,32 @@ Valid option:
fwrite(Format, Args, Options) ->
format(Format, Args, Options).
+
+-doc(#{equiv => bfwrite(Format, Data, [])}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bfwrite(Format, Data) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()].
+
+bfwrite(F, A) ->
+ bformat(F, A, []).
+
+-doc("""
+ Binary variant of `fwrite/3`
+
+ Returns a UTF-8 encoded binary string.
+ """).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bfwrite(Format, Data, Options) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()],
+ Options :: [Option],
+ Option :: {'chars_limit', CharsLimit},
+ CharsLimit :: chars_limit().
+
+bfwrite(F, A, Opts) ->
+ bformat(F, A, Opts).
+
-doc """
Tries to read `String` in accordance with the control sequences in `Format`.
@@ -234,6 +271,7 @@ _Example:_
{ok,[15.6,1.73e-5,24.5],[]}
```
""".
+
-spec fread(Format, String) -> Result when
Format :: string(),
String :: string(),
@@ -316,6 +354,38 @@ format(Format, Args, Options) ->
erlang:error(badarg, [Format, Args])
end.
+
+-doc(#{equiv => bfwrite(Format, Data, [])}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bformat(Format, Data) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()].
+
+bformat(Format, Args) ->
+ try io_lib_format:fwrite_bin(Format, Args)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [Format, Args])
+ end.
+
+-doc(#{equiv => bfwrite(Format, Data, Options)}).
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bformat(Format, Data, Options) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()],
+ Options :: [Option],
+ Option :: {'chars_limit', CharsLimit},
+ CharsLimit :: chars_limit().
+
+bformat(Format, Args, Options) ->
+ try io_lib_format:fwrite_bin(Format, Args, Options)
+ catch
+ C:R:S ->
+ test_modules_loaded(C, R, S),
+ erlang:error(badarg, [Format, Args])
+ end.
+
-doc """
Returns a list corresponding to the specified format string, where control
sequences have been replaced with corresponding tuples. This list can be passed
@@ -513,7 +583,8 @@ _Example:_
Options :: [Option],
Option :: {'chars_limit', CharsLimit}
| {'depth', Depth}
- | {'encoding', 'latin1' | 'utf8' | 'unicode'},
+ | {'encoding', 'latin1' | 'utf8' | 'unicode'}
+ | {'maps_order', maps:iterator_order()},
CharsLimit :: chars_limit(),
Depth :: depth().
@@ -522,6 +593,12 @@ write(Term, Options) when is_list(Options) ->
Encoding = get_option(encoding, Options, epp:default_encoding()),
CharsLimit = get_option(chars_limit, Options, -1),
MapsOrder = get_option(maps_order, Options, undefined),
+ write(Term, Depth, Encoding, MapsOrder, CharsLimit);
+write(Term, Depth) ->
+ write(Term, [{depth, Depth}, {encoding, latin1}]).
+
+-doc false.
+write(Term, Depth, Encoding, MapsOrder, CharsLimit) ->
if
Depth =:= 0; CharsLimit =:= 0 ->
"...";
@@ -532,29 +609,195 @@ write(Term, Options) when is_list(Options) ->
If = io_lib_pretty:intermediate
(Term, Depth, CharsLimit, RecDefFun, Encoding, _Str=false, MapsOrder),
io_lib_pretty:write(If)
+ end.
+
+-doc """
+Behaves as `write/2` but returns a UTF-8 encoded binary string.
+""".
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bwrite(Term, Options) -> unicode:unicode_binary() when
+ Term :: term(),
+ Options :: [Option],
+ Option :: {'chars_limit', CharsLimit}
+ | {'depth', Depth}
+ | {'encoding', 'latin1' | 'utf8' | 'unicode'}
+ | {'maps_order', maps:iterator_order()},
+ CharsLimit :: chars_limit(),
+ Depth :: depth().
+
+bwrite(Term, Options) when is_list(Options) ->
+ Depth = get_option(depth, Options, -1),
+ %% InEncoding = How should we display atoms
+ InEncoding = get_option(encoding, Options, epp:default_encoding()),
+ CharsLimit = get_option(chars_limit, Options, -1),
+ MapsOrder = get_option(maps_order, Options, undefined),
+ {S, _Sz} = write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit),
+ S.
+
+-doc false.
+-spec write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit) ->
+ {unicode:unicode_binary(), Sz::integer()} when
+ Term :: term(),
+ Depth :: depth(),
+ InEncoding :: 'latin1' | 'utf8' | 'unicode',
+ MapsOrder :: maps:iterator_order() | undefined,
+ CharsLimit :: chars_limit().
+
+write_bin(Term, Depth, InEncoding, MapsOrder, CharsLimit) ->
+ if
+ Depth =:= 0; CharsLimit =:= 0 ->
+ {<<"...">>, 3};
+ is_integer(CharsLimit), CharsLimit < 0, is_integer(Depth) ->
+ write_bin1(Term, Depth, InEncoding, MapsOrder, 0, <<>>);
+ is_integer(CharsLimit), CharsLimit > 0 ->
+ RecDefFun = fun(_, _) -> no end,
+ If = io_lib_pretty:intermediate(Term, Depth, CharsLimit, RecDefFun,
+ {InEncoding, utf8}, _Str=false, MapsOrder),
+ {_, Len, _, _} = If,
+ Bin = io_lib_pretty:write(If, {unicode,utf8}),
+ {Bin, Len}
+ end.
+
+write_bin1(_Term, 0, _Enc, _O, Sz, Acc) ->
+ {<<Acc/binary, "...">>, Sz+3};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_integer(Term) ->
+ Int = integer_to_binary(Term),
+ {<<Acc/binary, Int/binary>>, byte_size(Int) + Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_float(Term) ->
+ Float = float_to_binary(Term, [short]),
+ {<<Acc/binary, Float/binary>>, byte_size(Float)+Sz};
+write_bin1(Atom, _D, latin1, _O, Sz, Acc) when is_atom(Atom) ->
+ Str = unicode:characters_to_binary(write_atom_as_latin1(Atom)),
+ {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Atom, _D, _Enc, _O, Sz, Acc) when is_atom(Atom) ->
+ Str = write_atom(Atom),
+ {<<Acc/binary, (unicode:characters_to_binary(Str))/binary>>, length(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_port(Term) ->
+ Str = (list_to_binary(erlang:port_to_list(Term))),
+ {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_pid(Term) ->
+ Str = (list_to_binary(pid_to_list(Term))),
+ {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(Term, _D, _Enc, _O, Sz, Acc) when is_reference(Term) ->
+ Str = (list_to_binary(erlang:ref_to_list(Term))),
+ {<<Acc/binary, Str/binary>>, byte_size(Str)+Sz};
+write_bin1(<<_/bitstring>>=Term, D, _Enc, _O, Sz, Acc) ->
+ write_binary_bin0(Term, D, Sz, Acc);
+write_bin1([], _D, _Enc, _O, Sz, Acc) ->
+ {<<Acc/binary, "[]">>, Sz+2};
+write_bin1({}, _D, _Enc, _O, Sz, Acc) ->
+ {<<Acc/binary, "{}">>, Sz+2};
+write_bin1(F, _D, _Enc, _O, Sz, Acc) when is_function(F) ->
+ Str = (list_to_binary(erlang:fun_to_list(F))),
+ {<<Acc/binary, Str/binary>>, byte_size(Str) + Sz};
+write_bin1([H|T], D, Enc, O, Sz, Acc) ->
+ if
+ D =:= 1 -> {<<Acc/binary, "[...]">>, Sz+5};
+ true ->
+ {Head, Sz1} = write_bin1(H, D-1, Enc, O, Sz+1, <<Acc/binary, $[>>),
+ write_tail_bin(T, D-1, Enc, O, Sz1, Head)
end;
-write(Term, Depth) ->
- write(Term, [{depth, Depth}, {encoding, latin1}]).
+write_bin1(T, D, Enc, O, Sz, Acc) when is_tuple(T) ->
+ if
+ D =:= 1 -> {<<Acc/binary, "{...}">>, Sz+5};
+ true ->
+ {First, Sz1} = write_bin1(element(1, T), D-1, Enc, O, Sz+1, <<Acc/binary, ${>>),
+ write_tuple_bin(T, 2, D-1, Enc, O, Sz1, First)
+ end;
+write_bin1(Term, 1, _Enc, _O, Sz, Acc) when is_map(Term) ->
+ {<<Acc/binary, "#{}">>, Sz+3};
+write_bin1(Map, D, Enc, O, Sz, Acc) when is_map(Map), is_integer(D) ->
+ I = maps:iterator(Map, O),
+ case maps:next(I) of
+ {K, V, NextI} ->
+ D0 = D - 1,
+ {Start,Sz1} = write_map_assoc_bin(K, V, D0, Enc, O, Sz+2, <<Acc/binary, $#, ${>>),
+ write_map_body_bin(NextI, D0, D0, Enc, O, Sz1, Start);
+ none ->
+ {~"#{}", 3}
+ end.
+
+write_tail_bin([], _D, _Enc, _O, Sz, Acc) -> {<<Acc/binary, $]>>, Sz+1};
+write_tail_bin(_, 1, _Enc, _O, Sz, Acc) -> {<<Acc/binary, "|...]">>, Sz+1};
+write_tail_bin([H|T], D, Enc, O, Sz, Acc) ->
+ {Head, Sz1} = write_bin1(H, D-1, Enc, O, Sz+1, <<Acc/binary,$,>>),
+ write_tail_bin(T, D-1, Enc, O, Sz1, Head);
+write_tail_bin(Other, D, Enc, O, Sz, Acc) ->
+ {Tail, Sz1} = write_bin1(Other, D-1, Enc, O, Sz+1, <<Acc/binary, $|>>),
+ {<<Tail/binary, $]>>, Sz1+1}.
+
+write_tuple_bin(T, I, _D, _Enc, _O, Sz, Acc) when I > tuple_size(T) ->
+ {<<Acc/binary, $}>>, Sz+1};
+write_tuple_bin(_, _I, 1, _Enc, _O, Sz, Acc) ->
+ {<<Acc/binary, ",...}">>, Sz+4};
+write_tuple_bin(T, I, D, Enc, O, Sz, Acc) ->
+ {Elem, Sz1} = write_bin1(element(I, T), D-1, Enc, O, Sz+1, <<Acc/binary, $,>>),
+ write_tuple_bin(T, I+1, D-1, Enc, O, Sz1, Elem).
+
+write_map_body_bin(_, 1, _D0, _Enc, _O, Sz, Acc) ->
+ {<<Acc/binary, ",...}">>, Sz+5};
+write_map_body_bin(I, D, D0, Enc, O, Sz, Acc) ->
+ case maps:next(I) of
+ {K, V, NextI} ->
+ {W, Sz1} = write_map_assoc_bin(K, V, D0, Enc, O, Sz+1, <<Acc/binary, $,>>),
+ write_map_body_bin(NextI, D - 1, D0, Enc, O, Sz1, W);
+ none ->
+ {<<Acc/binary, "}">>, Sz+1}
+ end.
+write_map_assoc_bin(K, V, D, Enc, O, Sz, Acc) ->
+ {KBin, Sz1} = write_bin1(K, D, Enc, O, Sz, Acc),
+ write_bin1(V, D, Enc, O, Sz1 + 4, <<KBin/binary, " => ">>).
+
+write_binary_bin0(B, D, Sz, Acc) ->
+ {S, _} = write_binary_bin(B, D, -1, Acc),
+ {S, byte_size(S)+Sz}.
+
+-doc false.
+-spec write_binary_bin(Bin, Depth, T, Acc) -> {unicode:unicode_binary(), binary()} when
+ Bin :: binary(),
+ Depth :: integer(),
+ T :: integer(),
+ Acc :: unicode:unicode_binary().
+
+write_binary_bin(B, D, T, Acc) when is_integer(T) ->
+ write_binary_body_bin(B, D, tsub(T, 4), <<Acc/binary, "<<" >>).
+
+write_binary_body_bin(<<>> = B, _D, _T, Acc) ->
+ {<<Acc/binary, ">>" >>, B};
+write_binary_body_bin(<<_/bitstring>>=B, D, T, Acc) when D =:= 1; T =:= 0->
+ {<<Acc/binary, "...>>">>, B};
+write_binary_body_bin(<<X:8>>, _D, _T, Acc) ->
+ {<<Acc/binary, (integer_to_binary(X))/binary, ">>">>, <<>>};
+write_binary_body_bin(<<X:8,Rest/bitstring>>, D, T, Acc) ->
+ IntBin = integer_to_binary(X),
+ write_binary_body_bin(Rest, D-1, tsub(T, byte_size(IntBin) + 1),
+ <<Acc/binary, IntBin/binary, $,>>);
+write_binary_body_bin(B, _D, _T, Acc) ->
+ L = bit_size(B),
+ <<X:L>> = B,
+ {<<Acc/binary, (integer_to_binary(X))/binary, $:,
+ (integer_to_binary(L))/binary,">>">>,
+ <<>>}.
+
write1(_Term, 0, _E, _O) -> "...";
write1(Term, _D, _E, _O) when is_integer(Term) -> integer_to_list(Term);
write1(Term, _D, _E, _O) when is_float(Term) -> io_lib_format:fwrite_g(Term);
write1(Atom, _D, latin1, _O) when is_atom(Atom) -> write_atom_as_latin1(Atom);
write1(Atom, _D, _E, _O) when is_atom(Atom) -> write_atom(Atom);
-write1(Term, _D, _E, _O) when is_port(Term) -> write_port(Term);
+write1(Term, _D, _E, _O) when is_port(Term) -> erlang:port_to_list(Term);
write1(Term, _D, _E, _O) when is_pid(Term) -> pid_to_list(Term);
-write1(Term, _D, _E, _O) when is_reference(Term) -> write_ref(Term);
+write1(Term, _D, _E, _O) when is_reference(Term) -> erlang:ref_to_list(Term);
write1(<<_/bitstring>>=Term, D, _E, _O) -> write_binary(Term, D);
write1([], _D, _E, _O) -> "[]";
write1({}, _D, _E, _O) -> "{}";
+write1(F, _D, _E, _O) when is_function(F) -> erlang:fun_to_list(F);
write1([H|T], D, E, O) ->
if
D =:= 1 -> "[...]";
true ->
[$[,[write1(H, D-1, E, O)|write_tail(T, D-1, E, O)],$]]
end;
-write1(F, _D, _E, _O) when is_function(F) ->
- erlang:fun_to_list(F);
write1(Term, D, E, O) when is_map(Term) ->
write_map(Term, D, E, O);
write1(T, D, E, O) when is_tuple(T) ->
@@ -581,12 +824,6 @@ write_tuple(_, _I, 1, _E, _O) -> [$, | "..."];
write_tuple(T, I, D, E, O) ->
[$,,write1(element(I, T), D-1, E, O)|write_tuple(T, I+1, D-1, E, O)].
-write_port(Port) ->
- erlang:port_to_list(Port).
-
-write_ref(Ref) ->
- erlang:ref_to_list(Ref).
-
write_map(_, 1, _E, _O) -> "#{}";
write_map(Map, D, E, O) when is_integer(D) ->
I = maps:iterator(Map, O),
@@ -738,6 +975,105 @@ write_string(S) ->
write_string(S, Q) ->
[Q|write_string1(unicode_as_unicode, S, Q)].
+-doc "Returns the UTF-8 encoded binary `String` surrounded by `Qoute`.".
+-doc(#{since => <<"OTP 28.0">>}).
+-spec bwrite_string(String, Qoute, InEnc) -> unicode:unicode_binary() when
+ String :: string() | binary(),
+ Qoute :: integer() | [],
+ InEnc :: 'unicode' | 'latin1'. %% In case of binary input
+
+bwrite_string(S, Q, InEnc) ->
+ {Bin, _Sz} = write_string_bin(S, Q, InEnc),
+ Bin.
+
+-doc false.
+-spec write_string_bin(String, Qoute, InEnc) -> {unicode:unicode_binary(), Sz::integer()} when
+ String :: string() | binary(),
+ Qoute :: integer() | [],
+ InEnc :: 'unicode' | 'latin1'. %% In case of binary input
+
+write_string_bin(S, Q, _InEnc) when is_list(S) ->
+ Escaped = write_string(S,Q),
+ Sz = chars_length(Escaped),
+ Bin = unicode:characters_to_binary(Escaped),
+ true = is_binary(Bin),
+ {Bin, Sz};
+write_string_bin(S, Q, latin1) when is_binary(S) ->
+ Escaped = string_bin_escape_latin1(S, S, Q, [], 0, 0),
+ Sz = iolist_size(Escaped) + if Q == [] -> 0; true -> 2 end,
+ Bin = unicode:characters_to_binary([Q,Escaped,Q], latin1, utf8),
+ true = is_binary(Bin),
+ {Bin, Sz};
+write_string_bin(S, Q, unicode) when is_binary(S) ->
+ Escaped = string_bin_escape_unicode(S, S, Q, [], 0, 0),
+ Bin = case Q of
+ [] when is_binary(Escaped) -> Escaped;
+ _ -> unicode:characters_to_binary([Q,Escaped,Q])
+ end,
+ {Bin, string:length(Bin)}.
+
+string_bin_escape_latin1(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) ->
+ case needs_escape(Byte, Q) of
+ false ->
+ string_bin_escape_latin1(Rest, Orig, Q, Acc, Skip0, Len+1);
+ Escape when Len =:= 0 ->
+ Skip = Skip0 + Len + 1,
+ string_bin_escape_latin1(Rest, Orig, Q, [Acc | Escape], Skip, 0);
+ Escape ->
+ Skip = Skip0 + Len + 1,
+ Part = binary_part(Orig, Skip0, Len),
+ string_bin_escape_latin1(Rest, Orig, Q, [Acc, Part | Escape], Skip, 0)
+ end;
+string_bin_escape_latin1(_, Orig, _, _Acc, 0, _) ->
+ Orig;
+string_bin_escape_latin1(_, Orig, _, Acc, Skip, Len) ->
+ case Len =:= 0 of
+ true -> Acc;
+ false -> [Acc | binary_part(Orig, Skip, Len)]
+ end.
+
+string_bin_escape_unicode(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) when Byte > 127 ->
+ string_bin_escape_unicode(Rest, Orig, Q, Acc, Skip0, Len+1);
+string_bin_escape_unicode(<<Byte, Rest/binary>>, Orig, Q, Acc, Skip0, Len) ->
+ case needs_escape(Byte, Q) of
+ false ->
+ string_bin_escape_unicode(Rest, Orig, Q, Acc, Skip0, Len+1);
+ Escape when Len =:= 0 ->
+ Skip = Skip0 + Len + 1,
+ string_bin_escape_unicode(Rest, Orig, Q, [Acc | Escape], Skip, 0);
+ Escape ->
+ Skip = Skip0 + Len + 1,
+ Part = binary_part(Orig, Skip0, Len),
+ string_bin_escape_unicode(Rest, Orig, Q, [Acc, Part | Escape], Skip, 0)
+ end;
+string_bin_escape_unicode(_, Orig, _, _Acc, 0, _) ->
+ Orig;
+string_bin_escape_unicode(_, Orig, _, Acc, Skip, Len) ->
+ case Len =:= 0 of
+ true -> Acc;
+ false -> [Acc | binary_part(Orig, Skip, Len)]
+ end.
+
+needs_escape(Q, Q) -> [$\\, Q];
+needs_escape($\\, _) -> [$\\, $\\];
+needs_escape(C, _) when C >= $\s, C =< $~ ->
+ false;
+needs_escape($\n, _) -> [$\\, $n];
+needs_escape($\r, _) -> [$\\, $r];
+needs_escape($\t, _) -> [$\\, $t];
+needs_escape($\v, _) -> [$\\, $v];
+needs_escape($\b, _) -> [$\\, $b];
+needs_escape($\f, _) -> [$\\, $f];
+needs_escape($\e, _) -> [$\\, $e];
+needs_escape($\d, _) -> [$\\, $d];
+needs_escape(C, _) when C >= $\240, C =< $\377 ->
+ false;
+needs_escape(C, _) when C < $\240 ->
+ C1 = (C bsr 6) + $0,
+ C2 = ((C bsr 3) band 7) + $0,
+ C3 = (C band 7) + $0,
+ [$\\,C1,C2,C3].
+
%% Backwards compatibility.
-doc false.
write_unicode_string(S) ->
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index 6e552a3503..8eb8b9af14 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -22,10 +22,17 @@
-compile(nowarn_deprecated_catch).
+-dialyzer([{nowarn_function, [iolist_to_bin/4]},
+ no_improper_lists]).
+
%% Formatting functions of io library.
--export([fwrite/2,fwrite/3,fwrite_g/1,indentation/2,scan/2,unscan/1,
- build/1, build/2]).
+-export([fwrite/2,fwrite/3,
+ fwrite_bin/2, fwrite_bin/3,
+ fwrite_g/1,
+ indentation/2,
+ scan/2,unscan/1,
+ build/1, build/2, build_bin/1, build_bin/2]).
%% Format the arguments in Args after string Format. Just generate
%% an error if there is an error in the arguments.
@@ -58,6 +65,24 @@ fwrite(Format, Args) ->
fwrite(Format, Args, Options) ->
build(scan(Format, Args), Options).
+%% Binary variants
+-spec fwrite_bin(Format, Data) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()].
+
+fwrite_bin(Format, Args) ->
+ build_bin(scan(Format, Args)).
+
+-spec fwrite_bin(Format, Data, Options) -> unicode:unicode_binary() when
+ Format :: io:format(),
+ Data :: [term()],
+ Options :: [Option],
+ Option :: {'chars_limit', CharsLimit},
+ CharsLimit :: io_lib:chars_limit().
+
+fwrite_bin(Format, Args, Options) ->
+ build_bin(scan(Format, Args), Options).
+
%% Build the output text for a pre-parsed format list.
-spec build(FormatList) -> io_lib:chars() when
@@ -84,6 +109,32 @@ build(Cs, Options) ->
build_limited(Res1, P, NumOfLimited, RemainingChars, 0)
end.
+%% binary
+
+-spec build_bin(FormatList) -> unicode:unicode_binary() when
+ FormatList :: [char() | io_lib:format_spec()].
+build_bin(Cs) ->
+ build_bin(Cs, []).
+
+-spec build_bin(FormatList, Options) -> unicode:unicode_binary() when
+ FormatList :: [char() | io_lib:format_spec()],
+ Options :: [Option],
+ Option :: {'chars_limit', CharsLimit},
+ CharsLimit :: io_lib:chars_limit().
+
+build_bin(Cs, Options) ->
+ CharsLimit = get_option(chars_limit, Options, -1),
+ Res1 = build_small_bin(Cs),
+ {P, S, W, Other} = count_small(Res1),
+ case P + S + W of
+ 0 ->
+ iolist_to_binary(Res1);
+ NumOfLimited ->
+ RemainingChars = sub(CharsLimit, Other),
+ Res = build_limited_bin(Res1, P, NumOfLimited, RemainingChars, 0),
+ iolist_to_binary(Res) %% Res only contains utf-8 binary or ASCII
+ end.
+
%% Parse all control sequences in the format string.
-spec scan(Format, Data) -> FormatList when
@@ -264,9 +315,12 @@ count_small([#{control_char := $W}|Cs], #{w := W} = Cnts) ->
count_small(Cs, Cnts#{w := W + 1});
count_small([#{control_char := $s}|Cs], #{w := W} = Cnts) ->
count_small(Cs, Cnts#{w := W + 1});
-count_small([S|Cs], #{other := Other} = Cnts) when is_list(S);
- is_binary(S) ->
+count_small([S|Cs], #{other := Other} = Cnts)
+ when is_list(S) ->
count_small(Cs, Cnts#{other := Other + io_lib:chars_length(S)});
+count_small([S|Cs], #{other := Other} = Cnts)
+ when is_binary(S) ->
+ count_small(Cs, Cnts#{other := Other + string:length(S)});
count_small([C|Cs], #{other := Other} = Cnts) when is_integer(C) ->
count_small(Cs, Cnts#{other := Other + 1});
count_small([], #{p := P, s := S, w := W, other := Other}) ->
@@ -310,9 +364,10 @@ build_limited([#{control_char := C, args := As, width := F, adjust := Ad,
sub(MaxLen0, Len)
end,
if
- NumOfPs > 0 -> [S|build_limited(Cs, NumOfPs, Count,
- MaxLen, indentation(S, I))];
- true -> [S|build_limited(Cs, NumOfPs, Count, MaxLen, I)]
+ NumOfPs > 0 ->
+ [S|build_limited(Cs, NumOfPs, Count, MaxLen, indentation(S, I))];
+ true ->
+ [S|build_limited(Cs, NumOfPs, Count, MaxLen, I)]
end;
build_limited([$\n|Cs], NumOfPs, Count, MaxLen, _I) ->
[$\n|build_limited(Cs, NumOfPs, Count, MaxLen, 0)];
@@ -326,20 +381,95 @@ decr_pc($p, Pc) -> Pc - 1;
decr_pc($P, Pc) -> Pc - 1;
decr_pc(_, Pc) -> Pc.
+build_small_bin([#{control_char := C, args := As, width := F, adjust := Ad,
+ precision := P, pad_char := Pad, encoding := Enc}=CC | Cs]) ->
+ case control_small(C, As, F, Ad, P, Pad, Enc) of
+ not_small ->
+ [CC | build_small_bin(Cs)];
+ [$\n|_] = NL ->
+ [NL | build_small_bin(Cs)];
+ S ->
+ SBin = unicode:characters_to_binary(S, Enc, unicode),
+ true = is_binary(SBin),
+ [SBin | build_small_bin(Cs)]
+ end;
+build_small_bin([$\t|Cs]) ->
+ [$\t | build_small_bin(Cs)];
+build_small_bin([C|Cs]) ->
+ [C | build_small_bin(Cs)];
+build_small_bin([]) ->
+ [].
+
+build_limited_bin([#{control_char := C, args := As, width := F, adjust := Ad,
+ precision := P, pad_char := Pad, encoding := Enc,
+ strings := Str} = Map | Cs],
+ NumOfPs0, Count0, MaxLen0, I0) ->
+ Ord = maps:get(maps_order, Map, undefined),
+ MaxChars = if
+ MaxLen0 < 0 -> MaxLen0;
+ true -> MaxLen0 div Count0
+ end,
+ {S, Sz, I} = control_limited_bin(C, As, F, Ad, P, Pad, Enc, Str, Ord, MaxChars, I0),
+ NumOfPs = decr_pc(C, NumOfPs0),
+ Count = Count0 - 1,
+ MaxLen = if
+ MaxLen0 < 0 -> MaxLen0; % optimization
+ Sz < 0 -> sub(MaxLen0, string:length(S));
+ true -> sub(MaxLen0, Sz)
+ end,
+ if
+ NumOfPs > 0, I < 0 ->
+ [S|build_limited_bin(Cs, NumOfPs, Count, MaxLen, indentation(S, I0))];
+ true ->
+ [S|build_limited_bin(Cs, NumOfPs, Count, MaxLen, I)]
+ end;
+build_limited_bin([[$\n|_]=NL|Cs], NumOfPs, Count, MaxLen, _I) ->
+ [NL|build_limited_bin(Cs, NumOfPs, Count, MaxLen, 0)];
+build_limited_bin([$\t|Cs], NumOfPs, Count, MaxLen, I) ->
+ [$\t|build_limited_bin(Cs, NumOfPs, Count, MaxLen, ((I + 8) div 8) * 8)];
+build_limited_bin([C|Cs], NumOfPs, Count, MaxLen, I) when is_integer(C) ->
+ [C|build_limited_bin(Cs, NumOfPs, Count, MaxLen, 1+I)];
+build_limited_bin([Bin|Cs], NumOfPs, Count, MaxLen, I) when is_binary(Bin) ->
+ [Bin|build_limited_bin(Cs, NumOfPs, Count, MaxLen, byte_size(Bin)+I)];
+build_limited_bin([], _, _, _, _) -> [].
+
+
%% Calculate the indentation of the end of a string given its start
%% indentation. We assume tabs at 8 cols.
-spec indentation(String, StartIndent) -> integer() when
- String :: io_lib:chars(),
+ String :: unicode:chardata(),
StartIndent :: integer().
-indentation([$\n|Cs], _I) -> indentation(Cs, 0);
-indentation([$\t|Cs], I) -> indentation(Cs, ((I + 8) div 8) * 8);
+indentation([$\n|Cs], _I) ->
+ indentation(Cs, 0);
+indentation([$\t|Cs], I) ->
+ indentation(Cs, ((I + 8) div 8) * 8);
indentation([C|Cs], I) when is_integer(C) ->
indentation(Cs, I+1);
indentation([C|Cs], I) ->
indentation(Cs, indentation(C, I));
-indentation([], I) -> I.
+indentation(Bin, I0) when is_binary(Bin) ->
+ indentation_bin(Bin, I0);
+indentation([], I) ->
+ I.
+
+indentation_bin(Bin, I) ->
+ indentation_bin(Bin, Bin, 0, 0, I).
+
+indentation_bin(<<$\n, Cs/binary>>, Orig, _Start, N,_I) ->
+ indentation_bin(Cs, Orig, N+1, 0, 0);
+indentation_bin(<<$\t, Cs/binary>>, Orig, Start, N, I0) ->
+ Part = binary:part(Orig, Start, N),
+ PSz = string:length(Part),
+ indentation_bin(Cs, Orig, N+1, N+1, ((I0+PSz + 8) div 8) * 8);
+indentation_bin(<<_, Cs/binary>>, Orig, Start, N, I) ->
+ indentation_bin(Cs, Orig, Start, N+1, I);
+indentation_bin(<<>>, Orig, Start, N, I) ->
+ Part = binary:part(Orig, Start, N),
+ PSz = string:length(Part),
+ I + PSz.
+
%% control_small(FormatChar, [Argument], FieldWidth, Adjust, Precision,
%% PadChar, Encoding) -> String
@@ -391,34 +521,55 @@ control_small($n, [], F, Adj, P, Pad, _Enc) -> newline(F, Adj, P, Pad);
control_small($i, [_A], _F, _Adj, _P, _Pad, _Enc) -> [];
control_small(_C, _As, _F, _Adj, _P, _Pad, _Enc) -> not_small.
-control_limited($s, [L0], F, Adj, P, Pad, latin1=Enc, _Str, _Ord, CL, _I) ->
- L = iolist_to_chars(L0, F, CL),
- string(L, limit_field(F, CL), Adj, P, Pad, Enc);
-control_limited($s, [L0], F, Adj, P, Pad, unicode=Enc, _Str, _Ord, CL, _I) ->
- L = cdata_to_chars(L0, F, CL),
- uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc));
+control_limited($s, [L0], F, Adj, P, Pad, Enc, _Str, _Ord, CL, _I) ->
+ if Enc =:= latin1 ->
+ L = iolist_to_chars(L0, F, CL),
+ string(L, limit_field(F, CL), Adj, P, Pad, Enc);
+ Enc =:= unicode ->
+ L = cdata_to_chars(L0, F, CL),
+ uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc))
+ end;
control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I) ->
- Chars = io_lib:write(A, [
- {depth, -1},
- {encoding, Enc},
- {chars_limit, CL},
- {maps_order, Ord}
- ]),
- term(Chars, F, Adj, P, Pad);
+ Chars = io_lib:write(A, -1, Enc, Ord, CL),
+ term(Chars, F, Adj, P, Pad, Enc);
control_limited($p, [A], F, Adj, P, Pad, Enc, Str, Ord, CL, I) ->
- print(A, -1, F, Adj, P, Pad, Enc, Str, Ord, CL, I);
+ print(A, -1, F, Adj, P, Pad, Enc, list, Str, Ord, CL, I);
control_limited($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, Ord, CL, _I)
- when is_integer(Depth) ->
- Chars = io_lib:write(A, [
- {depth, Depth},
- {encoding, Enc},
- {chars_limit, CL},
- {maps_order, Ord}
- ]),
- term(Chars, F, Adj, P, Pad);
+ when is_integer(Depth) ->
+ Chars = io_lib:write(A, Depth, Enc, Ord, CL),
+ term(Chars, F, Adj, P, Pad, Enc);
control_limited($P, [A,Depth], F, Adj, P, Pad, Enc, Str, Ord, CL, I)
- when is_integer(Depth) ->
- print(A, Depth, F, Adj, P, Pad, Enc, Str, Ord, CL, I).
+ when is_integer(Depth) ->
+ print(A, Depth, F, Adj, P, Pad, Enc, list, Str, Ord, CL, I).
+
+control_limited_bin($s, [L0], F, Adj, P, Pad, Enc, _Str, _Ord, CL, _I) ->
+ {B, Sz} = iolist_to_bin(L0, F, CL, Enc),
+ string_bin(B, Sz, limit_field(F, CL), Adj, P, Pad, Enc);
+control_limited_bin($w, [A], F, Adj, P, Pad, Enc, _Str, Ord, CL, I) ->
+ {Chars, Sz} = io_lib:write_bin(A, -1, Enc, Ord, CL),
+ term_bin(Chars, F, Adj, P, Pad, Enc, Sz, I);
+control_limited_bin($p, [A], F, Adj, P, Pad, Enc, Str, Ord, CL, I) ->
+ print(A, -1, F, Adj, P, Pad, Enc, binary, Str, Ord, CL, I);
+control_limited_bin($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, Ord, CL, I)
+ when is_integer(Depth) ->
+ {Chars, Sz} = io_lib:write_bin(A, Depth, Enc, Ord, CL),
+ term_bin(Chars, F, Adj, P, Pad, Enc, Sz, I);
+control_limited_bin($P, [A,Depth], F, Adj, P, Pad, Enc, Str, Ord, CL, I)
+ when is_integer(Depth) ->
+ print(A, Depth, F, Adj, P, Pad, Enc, binary, Str, Ord, CL, I).
+
+term_bin(T, none, _Adj, none, _Pad, _Enc, Sz, I) ->
+ {T, Sz, Sz+I};
+term_bin(T, none, Adj, P, Pad, Enc, Sz, I) ->
+ term_bin(T, P, Adj, P, Pad, Enc, Sz, I);
+term_bin(T, F, Adj, P0, Pad, _Enc, Sz, I) ->
+ P = erlang:min(Sz, case P0 of none -> F; _ -> min(P0, F) end),
+ if
+ Sz > P ->
+ {adjust(chars($*, P), chars(Pad, F-P), Adj), F, I+F};
+ F >= P ->
+ {adjust(T, chars(Pad, F-Sz), Adj), F, I+F}
+ end.
-ifdef(UNICODE_AS_BINARIES).
uniconv(C) ->
@@ -438,10 +589,15 @@ base(B) when is_integer(B) ->
%% Adjust the characters within the field if length less than Max padding
%% with PadChar.
-term(T, none, _Adj, none, _Pad) -> T;
-term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
-term(T, F, Adj, P0, Pad) ->
- L = io_lib:chars_length(T),
+term(T, none, _Adj, none, _Pad, _Enc) ->
+ T;
+term(T, none, Adj, P, Pad, Enc) ->
+ term(T, P, Adj, P, Pad, Enc);
+term(T, F, Adj, P0, Pad, Enc) ->
+ L = case Enc =:= latin1 of
+ true -> io_lib:chars_length(T);
+ false -> string:length(T)
+ end,
P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end),
if
L > P ->
@@ -455,19 +611,34 @@ term(T, F, Adj, P0, Pad) ->
%% Print a term. Field width sets maximum line length, Precision sets
%% initial indentation.
-print(T, D, none, Adj, P, Pad, E, Str, Ord, ChLim, I) ->
- print(T, D, 80, Adj, P, Pad, E, Str, Ord, ChLim, I);
-print(T, D, F, Adj, none, Pad, E, Str, Ord, ChLim, I) ->
- print(T, D, F, Adj, I+1, Pad, E, Str, Ord, ChLim, I);
-print(T, D, F, right, P, _Pad, Enc, Str, Ord, ChLim, _I) ->
+print(T, D, none, Adj, P, Pad, E, Type, Str, Ord, ChLim, I) ->
+ print(T, D, 80, Adj, P, Pad, E, Type, Str, Ord, ChLim, I);
+print(T, D, F, Adj, none, Pad, E, Type, Str, Ord, ChLim, I) ->
+ print(T, D, F, Adj, I+1, Pad, E, Type, Str, Ord, ChLim, I);
+print(T, D, F, right, P, _Pad, Enc, list, Str, Ord, ChLim, _I) ->
Options = [{chars_limit, ChLim},
{column, P},
{line_length, F},
{depth, D},
{encoding, Enc},
{strings, Str},
- {maps_order, Ord}],
- io_lib_pretty:print(T, Options).
+ {maps_order, Ord}
+ ],
+ io_lib_pretty:print(T, Options);
+print(T, D, F, right, P, _Pad, Enc, binary, Str, Ord, ChLim, I) ->
+ Options = #{chars_limit => ChLim,
+ column => P,
+ line_length => F,
+ depth => D,
+ encoding => Enc,
+ strings => Str,
+ maps_order => Ord
+ },
+ {Bin, Sz, Col} = Res = io_lib_pretty:print_bin(T, Options),
+ case Col > 0 of
+ true -> Res;
+ false -> {Bin, Sz, I - Col}
+ end.
%% fwrite_e(Float, Field, Adjust, Precision, PadChar)
@@ -478,7 +649,7 @@ fwrite_e(Fl, none, _Adj, P, _Pad) when P >= 2 ->
fwrite_e(Fl, F, Adj, none, Pad) ->
fwrite_e(Fl, F, Adj, 6, Pad);
fwrite_e(Fl, F, Adj, P, Pad) when P >= 2 ->
- term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad).
+ term(float_e(Fl, float_data(Fl), P), F, Adj, F, Pad, latin1).
float_e(Fl, Fd, P) ->
signbit(Fl) ++ abs_float_e(abs(Fl), Fd, P).
@@ -533,7 +704,7 @@ fwrite_f(Fl, none, _Adj, P, _Pad) when P >= 1 ->
fwrite_f(Fl, F, Adj, none, Pad) ->
fwrite_f(Fl, F, Adj, 6, Pad);
fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 ->
- term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad).
+ term(float_f(Fl, float_data(Fl), P), F, Adj, F, Pad, latin1).
float_f(Fl, Fd, P) ->
signbit(Fl) ++ abs_float_f(abs(Fl), Fd, P).
@@ -678,6 +849,89 @@ limit_cdata_to_chars(Cs, Limit, Mode) ->
[GC | limit_cdata_to_chars(Cs1, Limit - 1, Mode)]
end.
+iolist_to_bin(L, F, CharsLimit, latin1) when CharsLimit < 0; CharsLimit >= F ->
+ Bin = unicode:characters_to_binary(L, latin1, unicode),
+ {Bin, iolist_size(L)};
+iolist_to_bin(L, F, CharsLimit, unicode) when CharsLimit < 0; CharsLimit >= F ->
+ case unicode:characters_to_binary(L) of
+ Bin when is_binary(Bin) ->
+ {Bin, undefined};
+ {error, Ok, Bad} ->
+ %% Try latin1, strange allowing mixing latin1 and utf8
+ %% but we handled it before for unknown reason.
+ {Bin, _} = iolist_to_bin(Bad, F, CharsLimit, latin1),
+ {iolist_to_binary([Ok|Bin]), undefined}
+ end;
+iolist_to_bin(L, _, CharsLimit, Enc) ->
+ {Acc, Sz, _Limit, Rest} = limit_iolist_to_bin(L, sub(CharsLimit, 3), Enc, 0, <<>>),
+ case string:is_empty(Rest) of
+ true ->
+ {Acc, Sz};
+ false ->
+ {Cont, Size, _, _} = limit_iolist_to_bin(Rest, 4, Enc, 0, <<>>),
+ if Size < 4 ->
+ {<<Acc/binary, Cont/binary>>, Sz+Size};
+ true ->
+ {<<Acc/binary, "...">>, Sz+3}
+ end
+ end.
+
+limit_iolist_to_bin(Cs, 0, _, Size, Acc) ->
+ {Acc, Size, 0, Cs};
+limit_iolist_to_bin([C|Cs], Limit, latin1, Size, Acc)
+ when C >= $\000, C =< $\377 ->
+ limit_iolist_to_bin(Cs, Limit-1, latin1, Size+1, <<Acc/binary, C/utf8>>);
+limit_iolist_to_bin(Bin0, Limit, latin1, Size0, Acc)
+ when is_binary(Bin0) ->
+ case byte_size(Bin0) of
+ Sz when Sz > Limit ->
+ {B1, B2} = split_binary(Bin0, Limit),
+ Bin = unicode:characters_to_binary(B1, latin1, unicode),
+ {<<Acc/binary, Bin/binary>>, Size0+Limit, 0, B2};
+ Sz ->
+ Bin = unicode:characters_to_binary(Bin0, latin1, unicode),
+ {<<Acc/binary, Bin/binary>>, Size0+Sz, Limit-Sz, []}
+ end;
+limit_iolist_to_bin(Bin0, Limit, unicode, Size0, Acc)
+ when is_binary(Bin0) ->
+ try string:length(Bin0) of
+ Sz when Sz > Limit ->
+ B1 = string:slice(Bin0, 0, Limit),
+ Skip = byte_size(Bin0) - byte_size(B1),
+ <<_:Skip/binary, B2/binary>> = Bin0,
+ {<<Acc/binary, B1/binary>>, Size0+Limit, 0, B2};
+ Sz ->
+ {<<Acc/binary, Bin0/binary>>, Size0+Sz, Limit-Sz, []}
+ catch _:_ -> %% We allow latin1 as binary strings, so try that
+ limit_iolist_to_bin(Bin0, Limit, latin1, Size0, Acc)
+ end;
+limit_iolist_to_bin(CPs, Limit, unicode, Size, Acc) ->
+ case string:next_grapheme(CPs) of
+ {error, <<C,Cs1/binary>>} ->
+ %% This is how ~ts handles Latin1 binaries with option
+ %% chars_limit.
+ limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, C/utf8>>);
+ {error, [C|Cs1]} -> % not all versions of module string return this
+ limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, C/utf8>>);
+ [] ->
+ {Acc, Size, Limit, []};
+ [GC|Cs1] when is_integer(GC) ->
+ limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, GC/utf8>>);
+ [GC|Cs1] ->
+ Utf8 = unicode:characters_to_binary(GC),
+ limit_iolist_to_bin(Cs1, Limit-1, unicode, Size+1, <<Acc/binary, Utf8/binary>>)
+ end;
+limit_iolist_to_bin([Deep|Cs], Limit0, Enc, Size0, Acc0) ->
+ {Acc, Sz, L, Cont} = limit_iolist_to_bin(Deep, Limit0, Enc, Size0, Acc0),
+ case string:is_empty(Cont) of
+ true ->
+ limit_iolist_to_bin(Cs, L, Enc, Sz, Acc);
+ false ->
+ limit_iolist_to_bin([Cont|Cs], L, Enc, Sz, Acc)
+ end;
+limit_iolist_to_bin([], Limit, _Enc, Size, Acc) ->
+ {Acc, Size, Limit, []}.
+
limit_field(F, CharsLimit) when CharsLimit < 0; F =:= none ->
F;
limit_field(F, CharsLimit) ->
@@ -685,7 +939,8 @@ limit_field(F, CharsLimit) ->
%% string(String, Field, Adjust, Precision, PadChar)
-string(S, none, _Adj, none, _Pad, _Enc) -> S;
+string(S, none, _Adj, none, _Pad, _Enc) ->
+ S;
string(S, F, Adj, none, Pad, Enc) ->
string_field(S, F, Adj, io_lib:chars_length(S), Pad, Enc);
string(S, none, _Adj, P, Pad, Enc) ->
@@ -711,6 +966,41 @@ string_field(S, F, Adj, N, Pad, _Enc) when N < F ->
string_field(S, _, _, _, _, _) -> % N == F
S.
+string_bin(S, _, none, _Adj, none, _Pad, _Enc) ->
+ {S, -1, -1};
+string_bin(S, undefined, F, Adj, P, Pad, Enc) ->
+ unicode = Enc, %% Assert size=-1 should only happen for unicode
+ string_bin(S, string:length(S), F, Adj, P, Pad, Enc);
+string_bin(S, Sz, F, Adj, none, Pad, Enc) ->
+ string_field_bin(S, F, Adj, Sz, Pad, Enc);
+string_bin(S, Sz, none, _Adj, P, Pad, Enc) ->
+ string_field_bin(S, P, left, Sz, Pad, Enc);
+string_bin(S0, Sz, F, Adj, P, Pad, Enc) when F >= P ->
+ if F > P ->
+ if Sz > P ->
+ S = adjust(flat_trunc(S0, P, Enc), chars(Pad, F-P), Adj),
+ {S, F, -1};
+ Sz < P ->
+ S = adjust([S0|chars(Pad, P-Sz)], chars(Pad, F-P), Adj),
+ {S, F, -1};
+ true -> % N == P
+ S = adjust(S0, chars(Pad, F-P), Adj),
+ {S, Sz+(F-P), -1}
+ end;
+ true -> % F == P
+ string_field_bin(S0, F, Adj, Sz, Pad, Enc)
+ end.
+
+string_field_bin(S0, F, _Adj, N, _Pad, Enc) when N > F ->
+ S = flat_trunc(S0, F, Enc),
+ {S, F, -1};
+string_field_bin(S0, F, Adj, N, Pad, _Enc) when N < F ->
+ S = adjust(S0, chars(Pad, F-N), Adj),
+ {S, N+F-N, -1};
+string_field_bin(S, _, _, N, _, _) -> % N == F
+ {S, N, -1}.
+
+
%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
%% -> [Char].
@@ -718,10 +1008,10 @@ unprefixed_integer(Int, F, Adj, Base, Pad, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-|S], F, Adj, none, Pad);
+ term([$-|S], F, Adj, none, Pad, latin1);
true ->
S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term(S, F, Adj, none, Pad)
+ term(S, F, Adj, none, Pad, latin1)
end.
%% prefixed_integer(Int, Field, Adjust, Base, PadChar, Prefix, Lowercase)
@@ -731,10 +1021,10 @@ prefixed_integer(Int, F, Adj, Base, Pad, Prefix, Lowercase)
when Base >= 2, Base =< 1+$Z-$A+10 ->
if Int < 0 ->
S = cond_lowercase(erlang:integer_to_list(-Int, Base), Lowercase),
- term([$-,Prefix|S], F, Adj, none, Pad);
+ term([$-,Prefix|S], F, Adj, none, Pad, latin1);
true ->
S = cond_lowercase(erlang:integer_to_list(Int, Base), Lowercase),
- term([Prefix|S], F, Adj, none, Pad)
+ term([Prefix|S], F, Adj, none, Pad, latin1)
end.
%% char(Char, Field, Adjust, Precision, PadChar) -> chars().
@@ -760,11 +1050,14 @@ adjust(Data, Pad, right) -> [Pad|Data].
%% Flatten and truncate a deep list to at most N elements.
-flat_trunc(List, N, latin1) when is_integer(N), N >= 0 ->
+flat_trunc(List, N, latin1) when is_list(List), is_integer(N), N >= 0 ->
{S, _} = lists:split(N, lists:flatten(List)),
S;
-flat_trunc(List, N, unicode) when is_integer(N), N >= 0 ->
- string:slice(List, 0, N).
+flat_trunc(Str, N, unicode) when is_integer(N), N >= 0 ->
+ string:slice(Str, 0, N);
+flat_trunc(Bin, N, latin1) when is_binary(Bin), is_integer(N), N >= 0 ->
+ {B, _} = split_binary(Bin, N),
+ B.
%% A deep version of lists:duplicate/2
@@ -800,7 +1093,7 @@ lowercase([H|T]) ->
lowercase([]) ->
[].
-%% Make sure T does change sign.
+%% Make sure T does not change sign.
sub(T, _) when T < 0 -> T;
sub(T, E) when T >= E -> T - E;
sub(_, _) -> 0.
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index ced5ab1cb1..76490806e3 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -25,10 +25,11 @@
%%% In this module "print" means the formatted printing while "write"
%%% means just writing out onto one line.
--export([print/1,print/2,print/3,print/4,print/5,print/6]).
+-export([print/1,print/2,print/3,print/4,print/5,print/6,
+ print_bin/2]).
%% To be used by io_lib only.
--export([intermediate/7, write/1]).
+-export([intermediate/7, write/1, write/2]).
%%%
%%% Exported functions
@@ -68,6 +69,23 @@ print(Term) ->
| {'strings', boolean()}
| {'maps_order', maps:iterator_order()}.
-type options() :: [option()].
+-type options_map() :: map().
+
+-spec print_bin(term(), options_map()) -> {unicode:unicode_binary(), Sz::integer(), Col::integer()}.
+print_bin(Term, Options) when is_map(Options) ->
+ Col = maps:get(column, Options, 1),
+ Ll = maps:get(line_length, Options, 80),
+ D = maps:get(depth, Options, -1),
+ M = maps:get(line_max_chars, Options, -1),
+ T = maps:get(chars_limit, Options, -1),
+ RecDefFun = maps:get(record_print_fun, Options, no_fun),
+ InEncoding = maps:get(encoding, Options, epp:default_encoding()),
+ Strings = maps:get(strings, Options, true),
+ MapsOrder = maps:get(maps_order, Options, undefined),
+ print_bin(Term, Col, Ll, D, M, T, RecDefFun, {InEncoding, utf8}, Strings, MapsOrder);
+print_bin(Term, Options) when is_list(Options) ->
+ print_bin(Term, maps:from_list(Options)).
+
-spec print(term(), rec_print_fun()) -> chars();
(term(), options()) -> chars().
@@ -116,15 +134,15 @@ print(Term, Col, Ll, D, M, RecDefFun) ->
%% M = CHAR_MAX (-1 if no max, 60 when printing from shell)
print(_, _, _, 0, _M, _T, _RF, _Enc, _Str, _Ord) -> "...";
print(_, _, _, _D, _M, 0, _RF, _Enc, _Str, _Ord) -> "...";
-print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord) when Col =< 0 ->
+print(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord)
+ when Col =< 0 ->
%% ensure Col is at least 1
print(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str, Ord);
-print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str, _Ord) when is_atom(Atom) ->
+print(Atom, _Col, _Ll, _D, _M, _T, _RF, Enc, _Str, _Ord)
+ when is_atom(Atom) ->
write_atom(Atom, Enc);
-print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord) when is_tuple(Term);
- is_list(Term);
- is_map(Term);
- is_bitstring(Term) ->
+print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord)
+ when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
%% preprocess and compute total number of chars
{_, Len, _Dots, _} = If =
case T < 0 of
@@ -135,14 +153,14 @@ print(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord) when is_tuple(Term);
M = max_cs(M0, Len),
if
Ll =:= 0 ->
- write(If);
+ write(If, Enc);
Len < Ll - Col, Len =< M ->
%% write the whole thing on a single line when there is room
- write(If);
+ write(If, Enc);
true ->
%% compute the indentation TInd for tagged tuples and records
- TInd = while_fail([-1, 4],
- fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
+ TInd = while_fail([-1, 4],
+ fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
1),
pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)
end;
@@ -150,6 +168,52 @@ print(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str, _Ord) ->
%% atomic data types (bignums, atoms, ...) are never truncated
io_lib:write(Term).
+print_bin(_, _Col, _, 0, _M, _T, _RF, _Enc, _Str, _Ord) ->
+ {~"...", 3, -3};
+print_bin(_, _Col, _, _D, _M, 0, _RF, _Enc, _Str, _Ord) ->
+ {~"...", 3, -3};
+print_bin(Term, Col, Ll, D, M, T, RecDefFun, Enc, Str, Ord)
+ when Col =< 0 ->
+ %% ensure Col is at least 1
+ print_bin(Term, 1, Ll, D, M, T, RecDefFun, Enc, Str, Ord);
+print_bin(Atom, _Col, _Ll, _D, _M, _T, _RF, {InEnc, _}, _Str, _Ord)
+ when is_atom(Atom) ->
+ {Bin, Sz} = io_lib:write_bin(Atom, -1, InEnc, undefined, -1),
+ {Bin, Sz, -Sz};
+print_bin(Term, Col, Ll, D, M0, T, RecDefFun, Enc, Str, Ord)
+ when is_tuple(Term); is_list(Term); is_map(Term); is_bitstring(Term) ->
+ %% preprocess and compute total number of chars
+ {_, Len, _Dots, _} = If =
+ case T < 0 of
+ true -> print_length(Term, D, T, RecDefFun, Enc, Str, Ord);
+ false -> intermediate(Term, D, T, RecDefFun, Enc, Str, Ord)
+ end,
+ %% use Len as CHAR_MAX if M0 = -1
+ M = max_cs(M0, Len),
+ if
+ Ll =:= 0 ->
+ {write_bin(If, <<>>), Len, -Len};
+ Len < Ll - Col, Len =< M ->
+ %% write the whole thing on a single line when there is room
+ {write_bin(If,<<>>), Len, -Len};
+ true ->
+ %% compute the indentation TInd for tagged tuples and records
+ TInd = while_fail([-1, 4],
+ fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,
+ 1),
+ {Bin, NL, W} = pp_bin(If, Col, Ll, M, TInd, indent(Col), 0, 0, 0, 0, <<>>),
+ case NL > 0 of
+ true ->
+ {Bin, Len+Col*NL, W-1};
+ false ->
+ {Bin, Len, -W}
+ end
+ end;
+print_bin(Term, _Col, _Ll, _D, _M, _T, _RF, _Enc, _Str, _Ord) ->
+ %% atomic data types (bignums, atoms, ...) are never truncated
+ {Bin, Sz} = io_lib:write_bin(Term, -1, unicode, undefined, -1),
+ {Bin, Sz, -Sz}.
+
%%%
%%% Local functions
%%%
@@ -160,7 +224,7 @@ max_cs(M, Len) when M < 0 ->
max_cs(M, _Len) ->
M.
--define(ATM(T), is_list(element(1, T))).
+-define(ATM(T), (is_list(element(1, T)) orelse is_binary(element(1, T)))).
-define(ATM_PAIR(Pair),
?ATM(element(2, element(1, Pair))) % Key
andalso
@@ -332,9 +396,9 @@ pp_tail([{_, Len, _, _}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, W) ->
pp_tail({dots, _, _, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, _W) ->
[S | "..."];
pp_tail({_, Len, _, _}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, W)
- when Len + 1 < Ll - Col - (LD + 1),
- Len + 1 + W + (LD + 1) =< M,
- ?ATM(E) ->
+ when Len + 1 < Ll - Col - (LD + 1),
+ Len + 1 + W + (LD + 1) =< M,
+ ?ATM(E) ->
[S | write(E)];
pp_tail(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, _W) ->
[S, $\n, Ind | pp(E, Col0, Ll, M, TInd, Ind, LD + 1, 0)].
@@ -369,7 +433,256 @@ pp_binary(S, N, _N0, Ind) ->
S
end.
+
+-define(IND(Spaces), (list_to_binary(Spaces))/binary).
+
+pp_bin({_S,Len,_,_} = If, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+ when Len < Ll - Col - LD, Len + W + LD =< M ->
+ {write_bin(If, Acc), NL, Pos+Len};
+pp_bin({{list,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ pp_list_bin(L, Col+1, Ll, M, TInd, indent(1, Ind), LD, $|, $], NL, W+1, Pos+1,
+ <<Acc/binary, $[>>);
+pp_bin({{tuple,true,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ pp_tag_tuple_bin(L, Col, Ll, M, TInd, Ind, LD, NL, W+1, Pos+1,
+ <<Acc/binary, ${>>);
+pp_bin({{tuple,false,L}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ pp_list_bin(L, Col+1, Ll, M, TInd, indent(1, Ind), LD, $,, $}, NL, W+1, Pos+1,
+ <<Acc/binary, ${>>);
+pp_bin({{map,Pairs}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ pp_map_bin(Pairs, Col+2, Ll, M, TInd, indent(2, Ind), LD, NL, W+2, Pos,
+ <<Acc/binary, $#, ${>>);
+pp_bin({{record,[{Name0,NLen} | L]}, _Len, _, _}, Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ Name = unicode:characters_to_binary(Name0),
+ pp_record_bin(L, NLen, Col, Ll, M, TInd, Ind, LD, NL, W+NLen+1, Pos+NLen+1,
+ <<Acc/binary, Name/binary, ${>>);
+pp_bin({{bin,S}, _Len, _, _}, Col, Ll, M, _TInd, Ind, LD, NL, W, Pos, Acc) ->
+ pp_binary_bin(S, Col+2, Ll, M, indent(2, Ind), LD, NL, W, Pos, Acc);
+pp_bin({S,Len,_,_}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ Bin = unicode:characters_to_binary(S),
+ {<<Acc/binary, Bin/binary>>, NL, Pos+Len}.
+
+%% Print a tagged tuple by indenting the rest of the elements
+%% differently to the tag. Tuple has size >= 2.
+pp_tag_tuple_bin({dots, _, _, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, "...}">>, NL, Pos+4};
+pp_tag_tuple_bin([{Tag0,Tlen,_,_} | L], Col, Ll, M, TInd, Ind, LD, NL, W, Pos, Acc) ->
+ %% this uses TInd
+ TagInd = Tlen+2,
+ Tcol = Col+TagInd,
+ Tag = unicode:characters_to_binary(Tag0),
+ S = $,,
+ if
+ TInd > 0, TagInd > TInd ->
+ Col1 = Col+TInd,
+ Indent = indent(TInd, Ind),
+ pp_tail_bin(L, Col1, Tcol, Ll, M, TInd, Indent, LD, S, $}, NL, W+Tlen, Pos+Tlen,
+ <<Acc/binary, Tag/binary>>);
+ true ->
+ Indent = indent(TagInd, Ind),
+ pp_list_bin(L, Tcol, Ll, M, TInd, Indent, LD, S, $}, NL, W+Tlen+1, Pos+Tlen+1,
+ <<Acc/binary, Tag/binary, S>>)
+ end.
+
+pp_map_bin([], _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, $}>>, NL, Pos+1}; % cannot happen
+pp_map_bin({dots, _, _, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, "...}">>, NL, Pos+4}; % cannot happen
+pp_map_bin([P | Ps], Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+ {PS, NL, Pos, PW} = pp_pair_bin(P, Col, Ll, M, TInd, Ind, last_depth(Ps, LD), NL0, W, Pos0, Acc),
+ pp_pairs_tail_bin(Ps, Col, Col+PW, Ll, M, TInd, Ind, LD, NL, PW, Pos, PS).
+
+pp_pairs_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, $}>>, NL, Pos+1};
+pp_pairs_tail_bin({dots, _, _, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _L, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, ",...}">>, NL, Pos+5};
+pp_pairs_tail_bin([{_, Len, _, _}=P | Ps], Col0, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos, Acc) ->
+ 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) ->
+ pp_pairs_tail_bin(Ps, Col0, Col+ELen, Ll, M, TInd, Ind, LD, NL0, W+ELen, Pos+ELen,
+ write_bin(P, <<Acc/binary, $,>>));
+ true ->
+ {PS, NL, Pos1, PW} = pp_pair_bin(P, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+ <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+ pp_pairs_tail_bin(Ps, Col0, Col0+PW, Ll, M, TInd, Ind, LD, NL, PW, Pos1, PS)
+ end.
+
+pp_pair_bin({_, Len, _, _}=Pair, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+ when Len < Ll - Col - LD, Len+W+LD =< M ->
+ {write_bin(Pair, Acc),
+ NL,
+ Len+Pos,
+ if
+ ?ATM_PAIR(Pair) ->
+ Len;
+ true ->
+ Ll % force nl
+ end};
+pp_pair_bin({{map_pair, K, V}, _Len, _, _}, Col0, Ll, M, TInd, Ind0, LD, NL0, W, Pos0, Acc0) ->
+ I = map_value_indent(TInd),
+ Ind = indent(I, Ind0),
+ {Acc1, NL1, _} = pp_bin(K, Col0, Ll, M, TInd, Ind0, LD, NL0, W, Pos0, Acc0),
+ {Acc2, NL, Pos} = pp_bin(V, Col0+I, Ll, M, TInd, Ind, LD, NL1+1, 0, Col0+I,
+ <<Acc1/binary, " =>\n", ?IND(Ind)>>),
+ {Acc2, NL, Pos, Ll}.
+
+pp_record_bin([], _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, $}>>, NL, Pos+1};
+pp_record_bin({dots, _, _, _}, _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, "...}">>, NL, Pos+4};
+pp_record_bin([F | Fs], Nlen, Col0, Ll, M, TInd, Ind0, LD, NL0, W0, Pos0, Acc) ->
+ Nind = Nlen+1,
+ {Col, Ind, S, W} = rec_indent(Nind, TInd, Col0, Ind0, W0),
+ {Pos1, NL1} = if W == 0 -> {Col, NL0+1}; true -> {Pos0, NL0} end,
+ {FS, NL, Pos, FW} = pp_field_bin(F, Col, Ll, M, TInd, Ind, last_depth(Fs, LD), NL1, W, Pos1,
+ <<Acc/binary, ?IND(S)>>),
+ pp_fields_tail_bin(Fs, Col, Col+FW, Ll, M, TInd, Ind, LD, NL, W+FW, Pos, FS).
+
+pp_fields_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, $}>>, NL, Pos+1};
+pp_fields_tail_bin({dots, _, _ ,_}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, ",...}">>, NL, Pos+5};
+pp_fields_tail_bin([{_, Len, _, _}=F | Fs], Col0, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+ LD1 = last_depth(Fs, LD),
+ ELen = 1+Len,
+ if
+ LD1 =:= 0, ELen+1 < Ll-Col, W+ELen+1 =< M, ?ATM_FLD(F);
+ LD1 > 0, ELen < Ll-Col-LD1, W+ELen+LD1 =< M, ?ATM_FLD(F) ->
+ pp_fields_tail_bin(Fs, Col0, Col+ELen, Ll, M, TInd, Ind, LD, NL0, W+ELen, Pos0+ELen,
+ write_field_bin(F, <<Acc/binary, $,>>));
+ true ->
+ {FS, NL, Pos, FW} = pp_field_bin(F, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+ <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+ pp_fields_tail_bin(Fs, Col0, Col0+FW, Ll, M, TInd, Ind, LD, NL, FW, Pos, FS)
+ end.
+
+pp_field_bin({_, Len, _, _}=Fl, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+ when Len < Ll-Col-LD, Len+W+LD =< M ->
+ {write_field_bin(Fl, Acc),
+ NL,
+ Len+Pos,
+ if
+ ?ATM_FLD(Fl) -> Len;
+ true -> Ll % force nl
+ end};
+pp_field_bin({{field, Name0, NameL, F},_,_, _}, Col0, Ll, M, TInd, Ind0, LD, NL0, W0, Pos0, Acc0) ->
+ {Col, Ind, S, W} = rec_indent(NameL, TInd, Col0, Ind0, W0+NameL),
+ Name = unicode:characters_to_binary(Name0),
+ {Acc, NL, Pos} =
+ case W of
+ 0 ->
+ Acc1 = <<Acc0/binary, Name/binary, " =", ?IND(S)>>,
+ pp_bin(F, Col, Ll, M, TInd, Ind, LD, NL0+1, W, Col, Acc1);
+ _ ->
+ Acc1 = <<Acc0/binary, Name/binary, " = ", ?IND(S)>>,
+ pp_bin(F, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc1)
+ end,
+ {Acc, NL, Pos, Ll}. % force nl
+
+pp_list_bin({dots, _, _, _}, _Col0, _Ll, _M, _TInd, _Ind, _LD, _S, C, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, "...", C>>, NL, Pos+4};
+pp_list_bin([E | Es], Col0, Ll, M, TInd, Ind, LD, S, C, NL0, W, Pos0, Acc) ->
+ {ES, NL, Pos, WE} = pp_element_bin(E, Col0, Ll, M, TInd, Ind, last_depth(Es, LD), NL0, W, Pos0, Acc),
+ pp_tail_bin(Es, Col0, Col0+WE, Ll, M, TInd, Ind, LD, S, C, NL, W+WE, Pos, ES).
+
+pp_tail_bin([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _S, C, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, C>>, NL, Pos+1};
+pp_tail_bin([{_, Len, _, _}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, C, NL0, W, Pos0, Acc) ->
+ LD1 = last_depth(Es, LD),
+ ELen = 1+Len,
+ if
+ LD1 =:= 0, ELen+1 < Ll-Col, W+ELen+1 =< M, ?ATM(E);
+ LD1 > 0, ELen < Ll-Col-LD1, W+ELen+LD1 =< M, ?ATM(E) ->
+ pp_tail_bin(Es, Col0, Col+ELen, Ll, M, TInd, Ind, LD, S, C, NL0, W+ELen, Pos0+ELen,
+ write_bin(E, <<Acc/binary, $,>>));
+ true ->
+ {ES, NL, Pos, WE} = pp_element_bin(E, Col0, Ll, M, TInd, Ind, LD1, NL0+1, 0, Col0,
+ <<Acc/binary, $,, $\n, ?IND(Ind)>>),
+ pp_tail_bin(Es, Col0, Col0+WE, Ll, M, TInd, Ind, LD, S, C, NL, WE, Pos, ES)
+ end;
+pp_tail_bin({dots, _, _, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, C, NL, _W, Pos, Acc) ->
+ {<<Acc/binary, S, "...", C>>, NL, Pos+5};
+pp_tail_bin({_, Len, _, _}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, C, NL, W, Pos, Acc)
+ when Len+1 < Ll - Col - (LD+1),
+ Len+1+W+(LD+1) =< M,
+ ?ATM(E) ->
+ Acc1 = write_bin(E, <<Acc/binary, S>>),
+ {<<Acc1/binary, C>>, NL, Pos+1+Len};
+pp_tail_bin(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, C, NL0, _W, _Pos0, Acc) ->
+ {Acc1, NL, Pos} = pp_bin(E, Col0, Ll, M, TInd, Ind, LD+1, NL0+1, 0, Col0,
+ <<Acc/binary, S, $\n, ?IND(Ind)>>),
+ {<<Acc1/binary, C>>, NL, Pos+1}.
+
+pp_element_bin({_, Len, _, _}=E, Col, Ll, M, _TInd, _Ind, LD, NL, W, Pos, Acc)
+ when Len < Ll - Col - LD, Len+W+LD =< M, ?ATM(E) ->
+ {write_bin(E, Acc), NL, Pos+Len, Len};
+pp_element_bin(E, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc) ->
+ {Acc1, NL, Pos} = pp_bin(E, Col, Ll, M, TInd, Ind, LD, NL0, W, Pos0, Acc),
+ {Acc1, NL, Pos, Ll}. % force nl
+
+pp_binary_bin(Orig, Col, Ll, M, Ind, LD, NL, W, Pos, Acc) ->
+ N = erlang:max(8, erlang:min(Ll - Col, M - 4 - W) - LD),
+ <<$<,$<,Rest/binary>> = Orig,
+ pp_binary_bin_ind(Rest, Orig, Ind, 0, 2, N, N, NL, Pos, Acc).
+
+pp_binary_bin_ind(<<_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+ N1 = N-2,
+ case N1 < 0 of
+ false ->
+ pp_binary_bin_ind(Rest, Orig, Ind, S, I+2, N1, N0, NL, Pos, Acc);
+ true ->
+ Part = binary:part(Orig, S, I),
+ pp_binary_bin_ind(Rest, Orig, Ind, S+I, 2, N0-2, N0, NL+1, 0,
+ <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+ end;
+pp_binary_bin_ind(<<_,_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+ N1 = N-3,
+ case N1 < 0 of
+ false ->
+ pp_binary_bin_ind(Rest, Orig, Ind, S, I+3, N1, N0, NL, Pos, Acc);
+ true ->
+ Part = binary:part(Orig, S, I),
+ pp_binary_bin_ind(Rest, Orig, Ind, S+I, 3, N0-3, N0, NL+1, Pos,
+ <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+ end;
+pp_binary_bin_ind(<<_,_,_,$,, Rest/binary>>, Orig, Ind, S, I, N, N0, NL, Pos, Acc) ->
+ N1 = N-4,
+ case N1 < 0 of
+ false ->
+ pp_binary_bin_ind(Rest, Orig, Ind, S, I+4, N1, N0, NL, Pos, Acc);
+ true ->
+ Part = binary:part(Orig, S, I),
+ pp_binary_bin_ind(Rest, Orig, Ind, S+I, 4, N0-4, N0, NL+1, Pos,
+ <<Acc/binary, Part/binary, $\n, ?IND(Ind)>>)
+ end;
+pp_binary_bin_ind(Bin, Orig, Ind, S, I, N, _, NL, Pos0, Acc) ->
+ Col = iolist_size(Ind)+1,
+ Pos = if Pos0 =:= 0 -> Col; true -> Pos0 end,
+ case (byte_size(Bin)-2) > N of %% Don't count bin closing ">>"
+ false when S =:= 0 ->
+ Sz = byte_size(Orig),
+ {<<Acc/binary, Orig/binary>>, NL, Pos+Sz};
+ false ->
+ Sz = byte_size(Orig)-S,
+ Part = binary:part(Orig, S, Sz),
+ {<<Acc/binary, Part/binary>>, NL, Pos+Sz};
+ true ->
+ Part1 = binary:part(Orig, S, I),
+ Sz = byte_size(Orig) - (S+I),
+ Part2 = binary:part(Orig, S+I, Sz),
+ {<<Acc/binary, Part1/binary, $\n, ?IND(Ind), Part2/binary>>,
+ NL+1, Col+Sz}
+ end.
+
%% write the whole thing on a single line
+
+write(If, {_, utf8}) ->
+ write_bin(If, <<>>);
+write(If, _) ->
+ write(If).
+
write({{tuple, _IsTagged, L}, _, _, _}) ->
[${, write_list(L, $,), $}];
write({{list, L}, _, _, _}) ->
@@ -419,6 +732,62 @@ write_tail({dots, _, _, _}, S) ->
write_tail(E, S) ->
[S | write(E)].
+
+%% write the whole thing on a single line
+write_bin({{tuple, _IsTagged, L}, _, _, _}, Acc) ->
+ write_list_bin(L, $,, $}, <<Acc/binary, ${>>);
+write_bin({{list, L}, _, _, _}, Acc) ->
+ write_list_bin(L, $|, $], <<Acc/binary, $[>>);
+write_bin({{map, Pairs}, _, _, _}, Acc) ->
+ write_list_bin(Pairs, $,, $}, <<Acc/binary, "#{">>);
+write_bin({{map_pair, K, V}, _, _, _}, Acc0) ->
+ Acc = write_bin(K, Acc0),
+ write_bin(V, <<Acc/binary, " => ">>);
+write_bin({{record, [{Name0,_} | L]}, _, _, _}, Acc) ->
+ Name = unicode:characters_to_binary(Name0),
+ write_fields_bin(L, <<Acc/binary, Name/binary, ${>>);
+write_bin({{bin, S}, _, _, _}, Acc) ->
+ <<Acc/binary, S/binary>>;
+write_bin({S, _, _, _}, Acc) when is_binary(S) ->
+ <<Acc/binary, S/binary>>;
+write_bin({S, _, _, _}, Acc) ->
+ Bin = unicode:characters_to_binary(S),
+ <<Acc/binary, Bin/binary>>.
+
+write_fields_bin([], Acc) ->
+ <<Acc/binary, $}>>;
+write_fields_bin({dots, _, _, _}, Acc) ->
+ <<Acc/binary, "...}">>;
+write_fields_bin([F | Fs], Acc) ->
+ write_fields_tail_bin(Fs, write_field_bin(F, Acc)).
+
+write_fields_tail_bin([], Acc) ->
+ <<Acc/binary, $}>>;
+write_fields_tail_bin({dots, _, _, _}, Acc) ->
+ <<Acc/binary, ",...}">>;
+write_fields_tail_bin([F | Fs], Acc) ->
+ write_fields_tail_bin(Fs, write_field_bin(F, <<Acc/binary, $,>>)).
+
+write_field_bin({{field, Name, _NameL, F}, _, _, _}, Acc) ->
+ Bin = unicode:characters_to_binary(Name),
+ true = is_binary(Bin),
+ write_bin(F, <<Acc/binary, Bin/binary, " = ">>).
+
+write_list_bin({dots, _, _, _}, _S, End, Acc) ->
+ <<Acc/binary, "...", End>>;
+write_list_bin([E | Es], S, End, Acc) ->
+ write_tail_bin(Es, S, End, write_bin(E, Acc)).
+
+write_tail_bin([], _S, End, Acc) ->
+ <<Acc/binary, End>>;
+write_tail_bin([E | Es], S, End, Acc) ->
+ write_tail_bin(Es, S, End, write_bin(E, <<Acc/binary, $,>>));
+write_tail_bin({dots, _, _, _}, S, End, Acc) ->
+ <<Acc/binary, S, "...", End>>;
+write_tail_bin(E, S, End, Acc) ->
+ Bin = write_bin(E, <<Acc/binary, S>>),
+ <<Bin/binary, End>>.
+
-type more() :: fun((chars_limit(), DeltaDepth :: non_neg_integer()) ->
intermediate_format()).
@@ -445,7 +814,9 @@ write_tail(E, S) ->
}.
-spec intermediate(term(), depth(), pos_integer(), rec_print_fun(),
- encoding(), boolean(), boolean()) -> intermediate_format().
+ encoding() | {encoding, utf8},
+ boolean(), maps:iterator_order() | undefined) ->
+ intermediate_format().
intermediate(Term, D, T, RF, Enc, Str, Ord) when T > 0 ->
D0 = 1,
@@ -460,8 +831,8 @@ intermediate(Term, D, T, RF, Enc, Str, Ord) when T > 0 ->
find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str, Ord, LastLen) ->
Dd2 = Dd * 2,
D1 = case D < 0 of
- true -> Dl + Dd2;
- false -> min(Dl + Dd2, D)
+ true -> Dl+Dd2;
+ false -> min(Dl+Dd2, D)
end,
If = expand(Lower, T, D1 - Dl),
case If of
@@ -488,7 +859,7 @@ search_depth(Lower, Upper, _Term, T, Dl, Du, _RF, _Enc, _Str, _Ord)
Upper
end;
search_depth(Lower, Upper, Term, T, Dl, Du, RF, Enc, Str, Ord) ->
- D1 = (Dl + Du) div 2,
+ D1 = (Dl +Du) div 2,
If = expand(Lower, T, D1 - Dl),
case If of
{_, Len, _, _} when Len > T ->
@@ -521,14 +892,14 @@ print_length(List, D, T, RF, Enc, Str, Ord) when is_list(List) ->
case Str andalso printable_list(List, D, T, Enc) of
true ->
%% print as string, escaping double-quotes in the list
- S = write_string(List, Enc),
- {S, io_lib:chars_length(S), 0, no_more};
+ {S, Len} = write_string(List, Enc),
+ {S, Len, 0, no_more};
{true, Prefix} ->
%% Truncated lists when T < 0 could break some existing code.
- S = write_string(Prefix, Enc),
+ {S, Len} = write_string(Prefix, Enc),
%% NumOfDots = 0 to avoid looping--increasing the depth
%% does not make Prefix longer.
- {[S | "..."], 3 + io_lib:chars_length(S), 0, no_more};
+ {[S | "..."], 3 + Len, 0, no_more};
false ->
case print_length_list(List, D, T, RF, Enc, Str, Ord) of
{What, Len, Dots, _More} when Dots > 0 ->
@@ -561,41 +932,7 @@ print_length(<<_/bitstring>> = Bin, 1, _T, RF, Enc, Str, Ord) ->
More = fun(T1, Dd) -> ?FUNCTION_NAME(Bin, 1+Dd, T1, RF, Enc, Str, Ord) end,
{"<<...>>", 7, 3, More};
print_length(<<_/bitstring>> = Bin, D, T, RF, Enc, Str, Ord) ->
- D1 = D - 1,
- case
- Str andalso
- (bit_size(Bin) rem 8) =:= 0 andalso
- printable_bin0(Bin, D1, tsub(T, 6), Enc)
- of
- {true, List} when is_list(List) ->
- S = io_lib:write_string(List, $"), %"
- {[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
- {false, List} when is_list(List) ->
- S = io_lib:write_string(List, $"), %"
- {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
- {true, true, Prefix} ->
- S = io_lib:write_string(Prefix, $"), %"
- More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
- end,
- {[$<,$<,S|"...>>"], 7 + length(S), 3, More};
- {false, true, Prefix} ->
- S = io_lib:write_string(Prefix, $"), %"
- More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
- end,
- {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
- false ->
- case io_lib:write_binary(Bin, D, T) of
- {S, <<>>} ->
- {{bin, S}, iolist_size(S), 0, no_more};
- {S, _Rest} ->
- More = fun(T1, Dd) ->
- ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
- end,
- {{bin, S}, iolist_size(S), 3, More}
- end
- end;
+ print_length_binary(Bin, D, T, RF, Enc, Str, Ord);
print_length(Term, _D, _T, _RF, _Enc, _Str, _Ord) ->
S = io_lib:write(Term),
%% S can contain unicode, so iolist_size(S) cannot be used here
@@ -735,12 +1072,87 @@ list_length_tail([{_, Len, Dots, _} | Es], Acc, DotsAcc) ->
list_length_tail({_, Len, Dots, _}, Acc, DotsAcc) ->
{Acc + 1 + Len, DotsAcc + Dots}.
+print_length_binary(Bin, D, T, RF, {InEnc, utf8} = Enc, Str, Ord) ->
+ D1 = D - 1,
+ case
+ Str andalso
+ (bit_size(Bin) rem 8) =:= 0 andalso
+ printable_bin0(Bin, D1, tsub(T, 6), InEnc, binary)
+ of
+ {true, _Bin} ->
+ {Utf8, Len} = io_lib:write_string_bin(Bin, [], latin1),
+ {[$<,$<,$",Utf8,$",$>,$>], 6 + Len, 0, no_more};
+ {false, _Bin} ->
+ {Utf8, Len} = io_lib:write_string_bin(Bin, [], unicode),
+ {[$<,$<,$",Utf8,"\"/utf8>>"], 11 + Len, 0, no_more};
+ {true, true, Prefix} ->
+ {S, Len} = io_lib:write_string_bin(Prefix, [], latin1), %"
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {[$<,$<,$",S|"\"...>>"], 9 + Len, 3, More};
+ {false, true, Prefix} ->
+ {S, Len} = io_lib:write_string_bin(Prefix, [], unicode), %"
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {[$<,$<,$",S|"\"/utf8...>>"], 14 + Len, 3, More};
+ false ->
+ case io_lib:write_binary_bin(Bin, D, T, <<>>) of
+ {S, <<>>} ->
+ {{bin, S}, iolist_size(S), 0, no_more};
+ {S, _Rest} ->
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {{bin, S}, iolist_size(S), 3, More}
+ end
+ end;
+print_length_binary(Bin, D, T, RF, Enc, Str, Ord) -> %% list output
+ D1 = D - 1,
+ case
+ Str andalso
+ (bit_size(Bin) rem 8) =:= 0 andalso
+ printable_bin0(Bin, D1, tsub(T, 6), Enc, list)
+ of
+ {true, List} when is_list(List) ->
+ S = io_lib:write_string(List, $"), %"
+ {[$<,$<,S,$>,$>], 4 + length(S), 0, no_more};
+ {false, List} when is_list(List) ->
+ S = io_lib:write_string(List, $"), %"
+ {[$<,$<,S,"/utf8>>"], 9 + io_lib:chars_length(S), 0, no_more};
+ {true, true, Prefix} ->
+ S = io_lib:write_string(Prefix, $"), %"
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {[$<,$<,S|"...>>"], 7 + length(S), 3, More};
+ {false, true, Prefix} ->
+ S = io_lib:write_string(Prefix, $"), %"
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {[$<,$<,S|"/utf8...>>"], 12 + io_lib:chars_length(S), 3, More};
+ false ->
+ case io_lib:write_binary(Bin, D, T) of
+ {S, <<>>} ->
+ {{bin, S}, iolist_size(S), 0, no_more};
+ {S, _Rest} ->
+ More = fun(T1, Dd) ->
+ ?FUNCTION_NAME(Bin, D+Dd, T1, RF, Enc, Str, Ord)
+ end,
+ {{bin, S}, iolist_size(S), 3, More}
+ end
+ end.
+
%% ?CHARS printable characters has depth 1.
-define(CHARS, 4).
%% only flat lists are "printable"
printable_list(_L, 1, _T, _Enc) ->
false;
+printable_list(L, D, T, {latin1, _}) ->
+ printable_list(L, D, T, latin1);
printable_list(L, _D, T, latin1) when T < 0 ->
io_lib:printable_latin1_list(L);
printable_list(L, _D, T, latin1) when T >= 0 ->
@@ -785,7 +1197,7 @@ is_flat([C|Cs], N) when is_integer(C) ->
is_flat(_, _N) ->
false.
-printable_bin0(Bin, D, T, Enc) ->
+printable_bin0(Bin, D, T, InEnc, Type) ->
Len = case D >= 0 of
true ->
%% Use byte_size() also if Enc =/= latin1.
@@ -801,57 +1213,53 @@ printable_bin0(Bin, D, T, Enc) ->
false when T >= 0 -> % cannot happen
T
end,
- printable_bin(Bin, Len, D, Enc).
+ printable_bin(Bin, Len, D, InEnc, Type).
-printable_bin(_Bin, 0, _D, _Enc) ->
+printable_bin(_Bin, 0, _D, _In, _Out) ->
false;
-printable_bin(Bin, Len, D, latin1) ->
- N = erlang:min(20, Len),
- L = binary_to_list(Bin, 1, N),
- case printable_latin1_list(L, N) of
- all when N =:= byte_size(Bin) ->
- {true, L};
- all when N =:= Len -> % N < byte_size(Bin)
- {true, true, L};
+printable_bin(Bin, Len, D, latin1, Out) when is_binary(Bin) ->
+ case printable_latin1_bin(Bin, Len) of
+ all when Len =:= byte_size(Bin) ->
+ case Out of
+ binary -> {true, Bin};
+ list -> {true, binary_to_list(Bin)}
+ end;
all ->
- case printable_bin1(Bin, 1 + N, Len - N) of
- 0 when byte_size(Bin) =:= Len ->
- {true, binary_to_list(Bin)};
- NC when D > 0, Len - NC >= D ->
- {true, true, binary_to_list(Bin, 1, Len - NC)};
- NC when is_integer(NC) ->
- false
+ case Out of
+ binary -> {true, true, binary:part(Bin, 0, Len)};
+ list -> {true, true, binary_to_list(Bin, 1, Len)}
+ end;
+ NC when is_integer(NC), D > 0, Len - NC >= D ->
+ case Out of
+ binary -> {true, true, binary:part(Bin, 0, Len - NC)};
+ list -> {true, true, binary_to_list(Bin, 1, Len - NC)}
end;
- NC when is_integer(NC), D > 0, N - NC >= D ->
- {true, true, binary_to_list(Bin, 1, N - NC)};
NC when is_integer(NC) ->
false
end;
-printable_bin(Bin, Len, D, _Uni) ->
- case valid_utf8(Bin,Len) of
- true ->
- case printable_unicode(Bin, Len, [], io:printable_range()) of
- {_, <<>>, L} ->
- {byte_size(Bin) =:= length(L), L};
- {NC, Bin1, L} when D > 0, Len - NC >= D ->
- {byte_size(Bin)-byte_size(Bin1) =:= length(L), true, L};
- {_NC, _Bin, _L} ->
- false
- end;
- false ->
- printable_bin(Bin, Len, D, latin1)
- end.
-
-printable_bin1(_Bin, _Start, 0) ->
- 0;
-printable_bin1(Bin, Start, Len) ->
- N = erlang:min(10000, Len),
- L = binary_to_list(Bin, Start, Start + N - 1),
- case printable_latin1_list(L, N) of
- all ->
- printable_bin1(Bin, Start + N, Len - N);
- NC when is_integer(NC) ->
- Len - (N - NC)
+printable_bin(Bin, Len, D, _Uni, Out) ->
+ case printable_unicode_bin(Bin, Len, io:printable_range()) of
+ not_utf8 ->
+ printable_bin(Bin, Len, D, latin1, Out);
+ {N, <<>>} when (Len - N) =:= byte_size(Bin) -> %% Ascii only
+ case Out of
+ binary -> {true, Bin};
+ list -> {true, binary_to_list(Bin)}
+ end;
+ {_N, <<>>} ->
+ case Out of
+ binary -> {false, Bin};
+ list -> {false, unicode:characters_to_list(Bin)}
+ end;
+ {N, RestBin} when D > 0, Len - N >= D ->
+ Sz = byte_size(Bin)-byte_size(RestBin),
+ Part = binary:part(Bin, 0, Sz),
+ case Out of
+ binary -> {Sz =:= (Len - N), true, Part};
+ list -> {Sz =:= (Len - N), true, unicode:characters_to_list(Part)}
+ end;
+ _ ->
+ false
end.
%% -> all | integer() >=0. Adopted from io_lib.erl.
@@ -870,24 +1278,25 @@ printable_latin1_list([$\e | Cs], N) -> printable_latin1_list(Cs, N - 1);
printable_latin1_list([], _) -> all;
printable_latin1_list(_, N) -> N.
-valid_utf8(<<>>,_) ->
- true;
-valid_utf8(_,0) ->
- true;
-valid_utf8(<<_/utf8, R/binary>>,N) ->
- valid_utf8(R,N-1);
-valid_utf8(_,_) ->
- false.
+printable_latin1_bin(<<>>, _) -> all;
+printable_latin1_bin(_, 0) -> 0;
+printable_latin1_bin(<<Char:8, Rest/binary>>, N) ->
+ case printable_char(Char, latin1) of
+ true -> printable_latin1_bin(Rest, N-1);
+ false -> N
+ end.
-printable_unicode(<<C/utf8, R/binary>>=Bin, I, L, Range) when I > 0 ->
- case printable_char(C,Range) of
- true ->
- printable_unicode(R, I - 1, [C | L],Range);
- false ->
- {I, Bin, lists:reverse(L)}
+printable_unicode_bin(<<C/utf8, R/binary>>=Bin, I, Range) when I > 0 ->
+ case printable_char(C, Range) of
+ true -> printable_unicode_bin(R, I-1, Range);
+ false -> {I, Bin}
end;
-printable_unicode(Bin, I, L,_) ->
- {I, Bin, lists:reverse(L)}.
+printable_unicode_bin(<<_/utf8, _/binary>>=Bin, I, _Range) ->
+ {I, Bin};
+printable_unicode_bin(<<>>, I, _Range) ->
+ {I, <<>>};
+printable_unicode_bin(_, _, _) ->
+ not_utf8.
printable_char($\n,_) -> true;
printable_char($\r,_) -> true;
@@ -907,13 +1316,19 @@ printable_char(C,unicode) ->
write_atom(A, latin1) ->
io_lib:write_atom_as_latin1(A);
+write_atom(A, {latin1, _}) ->
+ io_lib:write_atom_as_latin1(A);
write_atom(A, _Uni) ->
io_lib:write_atom(A).
-write_string(S, latin1) ->
- io_lib:write_latin1_string(S, $"); %"
-write_string(S, _Uni) ->
- io_lib:write_string(S, $"). %"
+write_string(S0, latin1) ->
+ S = io_lib:write_latin1_string(S0, $"), %"
+ {S, io_lib:chars_length(S)};
+write_string(S0, {InEnc, _Out}) ->
+ io_lib:write_string_bin(S0, $", InEnc); %"
+write_string(S0, _Uni) ->
+ S = io_lib:write_string(S0, $"), %"
+ {S, io_lib:chars_length(S)}.
expand({_, _, _Dots=0, no_more} = If, _T, _Dd) -> If;
expand({{tuple,IsTagged,L}, _Len, _, no_more}, T, Dd) ->
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index ed2edc5cf8..757aa8640e 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -36,7 +36,7 @@
github_4801/1, chars_limit/1, error_info/1, otp_17525/1,
unscan_format_without_maps_order/1, build_text_without_maps_order/1]).
--export([pretty/2, trf/3]).
+-export([pretty/2, trf/3, rfd/2]).
%%-define(debug, true).
@@ -94,9 +94,9 @@ error_1(Config) when is_list(Config) ->
format_neg_zero(Config) when is_list(Config) ->
<<NegZero/float>> = <<16#8000000000000000:64>>,
- "-0.000000" = io_lib:format("~f", [NegZero]),
- "-0.00000e+0" = io_lib:format("~g", [NegZero]),
- "-0.00000e+0" = io_lib:format("~e", [NegZero]),
+ "-0.000000" = fmt("~f", [NegZero]),
+ "-0.00000e+0" = fmt("~g", [NegZero]),
+ "-0.00000e+0" = fmt("~e", [NegZero]),
"-0.0" = io_lib_format:fwrite_g(NegZero),
ok.
@@ -780,7 +780,45 @@ rp(Term, Col, Ll, D, M, RF) ->
%% [Col, Ll, D, Term]),
R = io_lib_pretty:print(Term, Col, Ll, D, M, RF),
%% io:format("~s~n<--~n", [R]),
- lists:flatten(io_lib:format("~s", [R])).
+ OrigRes = lists:flatten(io_lib:format("~s", [R])),
+ Args = [{column, Col}, {line_length, Ll}, {depth, D},
+ {line_max_chars, M}, {record_print_fun, RF},
+ %% Default values for print/[1,3,4,5,6]
+ {chars_limit, -1}, {encoding, latin1},
+ {strings, true}, {maps_order, undefined}],
+ check_bin_p(OrigRes, Term, Args).
+
+check_bin_p(OrigRes, Term, Args) ->
+ try
+ {UTF8, _, Width} = io_lib_pretty:print_bin(Term, maps:from_list(Args)),
+
+ StartCol = proplists:get_value(column, Args, 0),
+ OrigW = io_lib_format:indentation(OrigRes, StartCol),
+ case Width > 0 of
+ true when Width == OrigW -> ok;
+ false when (StartCol - Width) == OrigW -> ok;
+ _ ->
+ io:format("Width fail, start: ~w got ~w correct ~w~n", [StartCol, Width, OrigW]),
+ io:put_chars(OrigRes),
+ io:nl(),
+ io:put_chars(UTF8),
+ exit(bad_width)
+ end,
+ true = is_binary(UTF8),
+ case unicode:characters_to_list(UTF8) of
+ OrigRes ->
+ OrigRes;
+ Other ->
+ io:format("Exp: ~w~nGot: ~w~n", [OrigRes, Other]),
+ io:format("Binary failed:~nio_lib_pretty:print_bin(~0p,~n ~w). ", [Term, Args]),
+ UTF8
+ end
+ catch _:Reason:ST ->
+ io:format("Exp: ~w~n~n", [OrigRes]),
+ io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+ io:format("Binary crashed: io_lib_pretty:print_bin(~p, ~w). ", [Term, Args]),
+ Reason
+ end.
fmt(Fmt, Args) ->
FormatList = io_lib:scan_format(Fmt, Args),
@@ -790,7 +828,30 @@ fmt(Fmt, Args) ->
Chars3 = lists:flatten(io_lib:format(Fmt, Args)),
Chars1 = Chars2,
Chars2 = Chars3,
- Chars3.
+ check_bin_fmt(Chars1, Fmt, Args, []).
+
+fmt(Fmt, Args, Opts) ->
+ OrigRes = lists:flatten(io_lib:format(Fmt, Args, Opts)),
+ check_bin_fmt(OrigRes, Fmt, Args, Opts).
+
+check_bin_fmt(OrigRes, Fmt, Args, Opts) ->
+ try
+ Utf8 = io_lib:bformat(Fmt, Args, Opts),
+ true = is_binary(Utf8),
+ case unicode:characters_to_list(Utf8) of
+ OrigRes ->
+ OrigRes;
+ Other ->
+ io:format("Exp: ~w~n ~ts~nGot: ~w~n ~ts~n", [OrigRes, OrigRes, Other, Other]),
+ io:format("Binary failed:~nio_lib:bformat(\"~ts\", ~w, ~w). ", [Fmt, Args, Opts]),
+ Utf8
+ end
+ catch _:Reason:ST ->
+ io:format("Exp: ~w~n~n", [OrigRes]),
+ io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+ io:format("Binary crashed: io_lib:bformat(\"~ts\", ~w, ~w). ", [Fmt, Args, Opts]),
+ Reason
+ end.
rfd(a, 0) ->
[];
@@ -1270,7 +1331,7 @@ ft(V, _F, 0) when is_float(V) ->
g_t(V) when is_float(V) ->
%% io:format("Testing ~.17g~n", [V]),
- Io = io_lib:format("~p", [V]),
+ Io = fmt("~p", [V]),
Sv = binary_to_list(iolist_to_binary(Io)),
ok = g_t(V, Sv),
Sv.
@@ -2017,7 +2078,7 @@ printable_range(Suite) when is_list(Suite) ->
$> = print_max(DNode,
[<<16#10FFFF/utf8,"\t\v\b\f\e\r\n">>,
PrettyOptions]),
-
+
1025 = format_max(UNode, ["~tp", [{hello, [1024,1025]}]]),
125 = format_max(LNode, ["~tp", [{hello, [1024,1025]}]]),
125 = format_max(DNode, ["~tp", [{hello, [1024,1025]}]]),
@@ -2040,8 +2101,47 @@ print_max(Node, Args) ->
format_max(Node, Args) ->
rpc_call_max(Node, io_lib, format, Args).
-rpc_call_max(Node, M, F, Args) ->
- lists:max(lists:flatten(rpc:call(Node, M, F, Args))).
+rpc_call_max(Node, io_lib_pretty=M, print=F, [A1,A2]=Args) ->
+ BinAs = [A1, maps:from_list(A2)],
+ Orig = lists:flatten(rpc:call(Node, M, print, Args)),
+ try
+ {Utf8, _, _} = rpc:call(Node, M, print_bin, BinAs),
+ true = is_binary(Utf8),
+ case unicode:characters_to_list(Utf8) of
+ Orig ->
+ lists:max(Orig);
+ Other ->
+ io:format("Exp: ~w~nGot: ~w~n", [Orig, Other]),
+ io:format("Binary failed:~n~w:~w(~0p). ", [M, print_bin, BinAs]),
+ Utf8
+ end
+ catch _:Reason:ST ->
+ io:format("Exp: ~w~n~n", [Orig]),
+ io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+ io:format("Binary crashed: ~w:~w(~0p). ", [M, print_bin, BinAs]),
+ Reason
+ end;
+rpc_call_max(Node, M, format=F, Args) ->
+ {BinF,BinAs} = {bformat, Args},
+ Orig = lists:flatten(rpc:call(Node, M, F, Args)),
+ try
+ Utf8 = rpc:call(Node, M, BinF, BinAs),
+ true = is_binary(Utf8),
+ case unicode:characters_to_list(Utf8) of
+ Orig ->
+ lists:max(Orig);
+ Other ->
+ io:format("Exp: ~w~nGot: ~w~n", [Orig, Other]),
+ io:format("Binary failed:~n~w:~w(~0p). ", [M, BinF, BinAs]),
+ Utf8
+ end
+ catch _:Reason:ST ->
+ io:format("Exp: ~w~n~n", [Orig]),
+ io:format("GOT CRASH: ~p in ~p~n",[Reason, ST]),
+ io:format("Binary crashed: ~w:~w(~0p). ", [M, BinF, BinAs]),
+ Reason
+ end.
+
%% Make sure that a bad specification for a printable range is rejected.
bad_printable_range(Config) when is_list(Config) ->
@@ -2126,14 +2226,16 @@ pretty(Term, Depth) when is_integer(Depth) ->
pretty(Term, Opts);
pretty(Term, Opts) when is_list(Opts) ->
R = io_lib_pretty:print(Term, Opts),
- lists:flatten(io_lib:format("~ts", [R])).
+ RF = lists:flatten(R),
+ RF = check_bin_p(RF, Term, Opts),
+ fmt("~ts", [R]).
is_latin1(S) ->
S >= 0 andalso S =< 255.
%% OTP-10836. ~ts extended to latin1.
otp_10836(Suite) when is_list(Suite) ->
- S = io_lib:format("~ts", [[<<"äpple"/utf8>>, <<"äpple">>]]),
+ S = fmt("~ts", [[<<"äpple"/utf8>>, <<"äpple">>]]),
"äppleäpple" = lists:flatten(S),
ok.
@@ -2208,8 +2310,8 @@ compile_file(File, Text, Config) ->
end.
io_lib_width_too_small(_Config) ->
- "**" = lists:flatten(io_lib:format("~2.3w", [3.14])),
- "**" = lists:flatten(io_lib:format("~2.5w", [3.14])),
+ "**" = fmt("~2.3w", [3.14]),
+ "**" = fmt("~2.5w", [3.14]),
ok.
%% Test that the time for a huge message queue is not
@@ -2423,11 +2525,12 @@ otp_14178_unicode_atoms(_Config) ->
bad_io_lib_format(F, S) ->
try io_lib:format(F, S) of
- _ ->
- ct:fail({should_fail,F,S})
- catch
- error:badarg ->
- ok
+ _ -> ct:fail({should_fail,F,S})
+ catch error:badarg -> ok
+ end,
+ try io_lib:bformat(F, S) of
+ _ -> ct:fail({should_fail,F,S})
+ catch error:badarg -> ok
end.
otp_14175(_Config) ->
@@ -2741,7 +2844,7 @@ limt(Term, Depth) when is_integer(Depth) ->
{{S, S1, S2}, R}.
form(Term, Depth) ->
- lists:flatten(io_lib:format("~W", [Term, Depth])).
+ lists:flatten(fmt("~W", [Term, Depth])).
limt_pp(Term, Depth) when is_integer(Depth) ->
T1 = io_lib:limit_term(Term, Depth),
@@ -2750,7 +2853,7 @@ limt_pp(Term, Depth) when is_integer(Depth) ->
S1 =:= S.
pp(Term, Depth) ->
- lists:flatten(io_lib:format("~P", [Term, Depth])).
+ lists:flatten(fmt("~P", [Term, Depth])).
otp_14983(_Config) ->
trunc_depth(-1, fun trp/3),
@@ -2865,44 +2968,45 @@ trp(Term, D, T) ->
trp(Term, D, T, [{record_print_fun, fun rfd/2}]).
trp(Term, D, T, Opts) ->
- R = io_lib_pretty:print(Term, [{depth, D},
- {chars_limit, T}|Opts]),
+ R = io_lib_pretty:print(Term, [{depth, D}, {chars_limit, T}|Opts]),
+ FR = lists:flatten(R),
+ FR = check_bin_p(FR, Term, [{depth, D}, {chars_limit, T}|Opts]),
lists:flatten(io_lib:format("~s", [R])).
trw(Term, D, T) ->
- lists:flatten(io_lib:format("~W", [Term, D], [{chars_limit, T}])).
+ lists:flatten(fmt("~W", [Term, D], [{chars_limit, T}])).
trf(Format, Args, T) ->
trf(Format, Args, T, [{record_print_fun, fun rfd/2}]).
trf(Format, Args, T, Opts) ->
- lists:flatten(io_lib:format(Format, Args, [{chars_limit, T}|Opts])).
+ lists:flatten(fmt(Format, Args, [{chars_limit, T}|Opts])).
otp_15103(_Config) ->
T = lists:duplicate(5, {a,b,c}),
- S1 = io_lib:format("~0p", [T]),
+ S1 = fmt("~0p", [T]),
"[{a,b,c},{a,b,c},{a,b,c},{a,b,c},{a,b,c}]" = lists:flatten(S1),
- S2 = io_lib:format("~-0p", [T]),
+ S2 = fmt("~-0p", [T]),
"[{a,b,c},{a,b,c},{a,b,c},{a,b,c},{a,b,c}]" = lists:flatten(S2),
- S3 = io_lib:format("~1p", [T]),
+ S3 = fmt("~1p", [T]),
"[{a,\n b,\n c},\n {a,\n b,\n c},\n {a,\n b,\n c},\n {a,\n b,\n"
" c},\n {a,\n b,\n c}]" = lists:flatten(S3),
- S4 = io_lib:format("~0P", [T, 5]),
+ S4 = fmt("~0P", [T, 5]),
"[{a,b,c},{a,b,...},{a,...},{...}|...]" = lists:flatten(S4),
- S5 = io_lib:format("~1P", [T, 5]),
+ S5 = fmt("~1P", [T, 5]),
"[{a,\n b,\n c},\n {a,\n b,...},\n {a,...},\n {...}|...]" =
lists:flatten(S5),
ok.
otp_15159(_Config) ->
"[atom]" =
- lists:flatten(io_lib:format("~p", [[atom]], [{chars_limit,5}])),
+ lists:flatten(fmt("~p", [[atom]], [{chars_limit,5}])),
ok.
otp_15076(_Config) ->
- {'EXIT', {badarg, _}} = (catch io_lib:format("~c", [a])),
+ ok = bad_io_lib_format("~c", [a]),
L = io_lib:scan_format("~c", [a]),
{"~c", [a]} = io_lib:unscan_format(L),
{'EXIT', {badarg, _}} = (catch io_lib:build_text(L)),
@@ -2975,12 +3079,12 @@ otp_15847(_Config) ->
otp_15875(_Config) ->
%% This test is moot due to the fix in GH-4842.
- S = io_lib:format("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]),
+ S = fmt("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]),
"[{0,[<<\"00\">>]}]" = lists:flatten(S).
github_4801(_Config) ->
- <<"{[81.6]}">> = iolist_to_binary(io_lib:format("~p", [{[81.6]}], [{chars_limit,40}])).
+ <<"{[81.6]}">> = iolist_to_binary(fmt("~p", [{[81.6]}], [{chars_limit,40}])).
%% GH-4824, GH-4842, OTP-17459.
chars_limit(_Config) ->
@@ -2999,7 +3103,8 @@ chars_limit(_Config) ->
Test = fun (F, N, Lim) ->
Opts = [{chars_limit, Lim},
{record_print_fun, fun rfd/2}],
- [_|_] = io_lib_pretty:print(F(N), Opts)
+ [_|_] = RL = io_lib_pretty:print(F(N), Opts),
+ check_bin_p(lists:flatten(RL),F(N), Opts)
end,
%% Used to loop:
Test(List, 1000, 1000),
@@ -3178,19 +3283,19 @@ otp_17525(_Config) ->
{ddddddddd,1111111111},
{gggggggggggggggggggg,cccc},
{uuuuuuuuuuuu,11}],
- S = io_lib:format("aaaaaaaaaaaaaaaaaa ~p bbbbbbbbbbb ~p",
- ["cccccccccccccccccccccccccccccccccccccc", L],
- [{chars_limit, 155}]),
- "aaaaaaaaaaaaaaaaaa \"cccccccccccccccccccccccccccccccccccccc\" bbbbbbbbbbb [{xxxxxxxxx,\n"
- " aaaa},\n"
- " {yyyyyyyyyyyy,\n"
- " 1},\n"
- " {eeeeeeeeeeeee,\n"
- " bbbb},\n"
- " {ddddddddd,\n"
- " 1111111111},\n"
- " {...}|...]" =
- lists:flatten(S),
+ S = fmt("aaaaaaaaaaaaaaaaaa ~p bbbbbbbbbbb ~p",
+ ["cccccccccccccccccccccccccccccccccccccc", L],
+ [{chars_limit, 155}]),
+ Corr = "aaaaaaaaaaaaaaaaaa \"cccccccccccccccccccccccccccccccccccccc\" bbbbbbbbbbb [{xxxxxxxxx,\n"
+ " aaaa},\n"
+ " {yyyyyyyyyyyy,\n"
+ " 1},\n"
+ " {eeeeeeeeeeeee,\n"
+ " bbbb},\n"
+ " {ddddddddd,\n"
+ " 1111111111},\n"
+ " {...}|...]",
+ Corr = lists:flatten(S),
ok.
unscan_format_without_maps_order(_Config) ->
--
2.43.0