|
|
@ -1,7 +1,7 @@ |
|
|
|
%% |
|
|
|
%% %CopyrightBegin% |
|
|
|
%% |
|
|
|
%% Copyright Ericsson AB 1996-2017. All Rights Reserved. |
|
|
|
%% Copyright Ericsson AB 1996-2019. All Rights Reserved. |
|
|
|
%% |
|
|
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
%% you may not use this file except in compliance with the License. |
|
|
@ -17,11 +17,12 @@ |
|
|
|
%% |
|
|
|
%% %CopyrightEnd% |
|
|
|
%% |
|
|
|
-module(eFmt_format). |
|
|
|
-module(eFmtFormat). |
|
|
|
|
|
|
|
%% Formatting functions of io library. |
|
|
|
|
|
|
|
-export([fwrite/2,fwrite_g/1,indentation/2,scan/2,unscan/1,build/1]). |
|
|
|
-export([fwrite/2,fwrite/3,fwrite_g/1,indentation/2,scan/2,unscan/1, |
|
|
|
build/1, build/2]). |
|
|
|
|
|
|
|
%% Format the arguments in Args after string Format. Just generate |
|
|
|
%% an error if there is an error in the arguments. |
|
|
@ -37,29 +38,55 @@ |
|
|
|
%% and it also splits the handling of the control characters into two |
|
|
|
%% parts. |
|
|
|
|
|
|
|
-spec fwrite(Format, Data) -> FormatList when |
|
|
|
-spec fwrite(Format, Data) -> io_lib:chars() when |
|
|
|
Format :: io:format(), |
|
|
|
Data :: [term()], |
|
|
|
FormatList :: [char() | eFmt:format_spec()]. |
|
|
|
Data :: [term()]. |
|
|
|
|
|
|
|
fwrite(Format, Args) -> |
|
|
|
build(scan(Format, Args)). |
|
|
|
|
|
|
|
-spec fwrite(Format, Data, Options) -> io_lib:chars() when |
|
|
|
Format :: io:format(), |
|
|
|
Data :: [term()], |
|
|
|
Options :: [Option], |
|
|
|
Option :: {'chars_limit', CharsLimit}, |
|
|
|
CharsLimit :: io_lib:chars_limit(). |
|
|
|
|
|
|
|
fwrite(Format, Args, Options) -> |
|
|
|
build(scan(Format, Args), Options). |
|
|
|
|
|
|
|
%% Build the output text for a pre-parsed format list. |
|
|
|
|
|
|
|
-spec build(FormatList) -> eFmt:chars() when |
|
|
|
FormatList :: [char() | eFmt:format_spec()]. |
|
|
|
-spec build(FormatList) -> io_lib:chars() when |
|
|
|
FormatList :: [char() | io_lib:format_spec()]. |
|
|
|
|
|
|
|
build(Cs) -> |
|
|
|
Pc = pcount(Cs), |
|
|
|
build(Cs, Pc, 0). |
|
|
|
build(Cs, []). |
|
|
|
|
|
|
|
-spec build(FormatList, Options) -> io_lib:chars() when |
|
|
|
FormatList :: [char() | io_lib:format_spec()], |
|
|
|
Options :: [Option], |
|
|
|
Option :: {'chars_limit', CharsLimit}, |
|
|
|
CharsLimit :: io_lib:chars_limit(). |
|
|
|
|
|
|
|
build(Cs, Options) -> |
|
|
|
CharsLimit = get_option(chars_limit, Options, -1), |
|
|
|
Res1 = build_small(Cs), |
|
|
|
{P, S, W, Other} = count_small(Res1), |
|
|
|
case P + S + W of |
|
|
|
0 -> |
|
|
|
Res1; |
|
|
|
NumOfLimited -> |
|
|
|
RemainingChars = sub(CharsLimit, Other), |
|
|
|
build_limited(Res1, P, NumOfLimited, RemainingChars, 0) |
|
|
|
end. |
|
|
|
|
|
|
|
%% Parse all control sequences in the format string. |
|
|
|
|
|
|
|
-spec scan(Format, Data) -> FormatList when |
|
|
|
Format :: io:format(), |
|
|
|
Data :: [term()], |
|
|
|
FormatList :: [char() | eFmt:format_spec()]. |
|
|
|
FormatList :: [char() | io_lib:format_spec()]. |
|
|
|
|
|
|
|
scan(Format, Args) when is_atom(Format) -> |
|
|
|
scan(atom_to_list(Format), Args); |
|
|
@ -72,7 +99,7 @@ scan(Format, Args) -> |
|
|
|
%% list of arguments. |
|
|
|
|
|
|
|
-spec unscan(FormatList) -> {Format, Data} when |
|
|
|
FormatList :: [char() | eFmt:format_spec()], |
|
|
|
FormatList :: [char() | io_lib:format_spec()], |
|
|
|
Format :: io:format(), |
|
|
|
Data :: [term()]. |
|
|
|
|
|
|
@ -95,7 +122,7 @@ print([]) -> |
|
|
|
[]. |
|
|
|
|
|
|
|
print(C, F, Ad, P, Pad, Encoding, Strings) -> |
|
|
|
[$~] ++ print_field_width(F, Ad) ++ print_precision(P) ++ |
|
|
|
[$~] ++ print_field_width(F, Ad) ++ print_precision(P, Pad) ++ |
|
|
|
print_pad_char(Pad) ++ print_encoding(Encoding) ++ |
|
|
|
print_strings(Strings) ++ [C]. |
|
|
|
|
|
|
@ -103,8 +130,9 @@ print_field_width(none, _Ad) -> ""; |
|
|
|
print_field_width(F, left) -> integer_to_list(-F); |
|
|
|
print_field_width(F, right) -> integer_to_list(F). |
|
|
|
|
|
|
|
print_precision(none) -> ""; |
|
|
|
print_precision(P) -> [$. | integer_to_list(P)]. |
|
|
|
print_precision(none, $\s) -> ""; |
|
|
|
print_precision(none, _Pad) -> "."; % pad must be second dot |
|
|
|
print_precision(P, _Pad) -> [$. | integer_to_list(P)]. |
|
|
|
|
|
|
|
print_pad_char($\s) -> ""; % default, no need to make explicit |
|
|
|
print_pad_char(Pad) -> [$., Pad]. |
|
|
@ -126,25 +154,23 @@ collect_cseq(Fmt0, Args0) -> |
|
|
|
{F,Ad,Fmt1,Args1} = field_width(Fmt0, Args0), |
|
|
|
{P,Fmt2,Args2} = precision(Fmt1, Args1), |
|
|
|
{Pad,Fmt3,Args3} = pad_char(Fmt2, Args2), |
|
|
|
{Encoding,Fmt4,Args4} = encoding(Fmt3, Args3), |
|
|
|
{Strings,Fmt5,Args5} = strings(Fmt4, Args4), |
|
|
|
{C,As,Fmt6,Args6} = collect_cc(Fmt5, Args5), |
|
|
|
FormatSpec = #{control_char => C, args => As, width => F, adjust => Ad, |
|
|
|
precision => P, pad_char => Pad, encoding => Encoding, |
|
|
|
strings => Strings}, |
|
|
|
{FormatSpec,Fmt6,Args6}. |
|
|
|
|
|
|
|
encoding([$t|Fmt],Args) -> |
|
|
|
true = hd(Fmt) =/= $l, |
|
|
|
{unicode,Fmt,Args}; |
|
|
|
encoding(Fmt,Args) -> |
|
|
|
{latin1,Fmt,Args}. |
|
|
|
|
|
|
|
strings([$l|Fmt],Args) -> |
|
|
|
true = hd(Fmt) =/= $t, |
|
|
|
{false,Fmt,Args}; |
|
|
|
strings(Fmt,Args) -> |
|
|
|
{true,Fmt,Args}. |
|
|
|
Spec0 = #{width => F, |
|
|
|
adjust => Ad, |
|
|
|
precision => P, |
|
|
|
pad_char => Pad, |
|
|
|
encoding => latin1, |
|
|
|
strings => true}, |
|
|
|
{Spec1,Fmt4} = modifiers(Fmt3, Spec0), |
|
|
|
{C,As,Fmt5,Args4} = collect_cc(Fmt4, Args3), |
|
|
|
Spec2 = Spec1#{control_char => C, args => As}, |
|
|
|
{Spec2,Fmt5,Args4}. |
|
|
|
|
|
|
|
modifiers([$t|Fmt], Spec) -> |
|
|
|
modifiers(Fmt, Spec#{encoding => unicode}); |
|
|
|
modifiers([$l|Fmt], Spec) -> |
|
|
|
modifiers(Fmt, Spec#{strings => false}); |
|
|
|
modifiers(Fmt, Spec) -> |
|
|
|
{Spec, Fmt}. |
|
|
|
|
|
|
|
field_width([$-|Fmt0], Args0) -> |
|
|
|
{F,Fmt,Args} = field_value(Fmt0, Args0), |
|
|
@ -203,45 +229,88 @@ collect_cc([$~|Fmt], Args) when is_list(Args) -> {$~,[],Fmt,Args}; |
|
|
|
collect_cc([$n|Fmt], Args) when is_list(Args) -> {$n,[],Fmt,Args}; |
|
|
|
collect_cc([$i|Fmt], [A|Args]) -> {$i,[A],Fmt,Args}. |
|
|
|
|
|
|
|
%% pcount([ControlC]) -> Count. |
|
|
|
%% Count the number of print requests. |
|
|
|
|
|
|
|
pcount(Cs) -> pcount(Cs, 0). |
|
|
|
|
|
|
|
pcount([#{control_char := $p}|Cs], Acc) -> pcount(Cs, Acc+1); |
|
|
|
pcount([#{control_char := $P}|Cs], Acc) -> pcount(Cs, Acc+1); |
|
|
|
pcount([_|Cs], Acc) -> pcount(Cs, Acc); |
|
|
|
pcount([], Acc) -> Acc. |
|
|
|
|
|
|
|
%% build([Control], Pc, Indentation) -> eFmt:chars(). |
|
|
|
%% count_small([ControlC]) -> Count. |
|
|
|
%% Count the number of big (pPwWsS) print requests and |
|
|
|
%% number of characters of other print (small) requests. |
|
|
|
|
|
|
|
count_small(Cs) -> |
|
|
|
count_small(Cs, #{p => 0, s => 0, w => 0, other => 0}). |
|
|
|
|
|
|
|
count_small([#{control_char := $p}|Cs], #{p := P} = Cnts) -> |
|
|
|
count_small(Cs, Cnts#{p := P + 1}); |
|
|
|
count_small([#{control_char := $P}|Cs], #{p := P} = Cnts) -> |
|
|
|
count_small(Cs, Cnts#{p := P + 1}); |
|
|
|
count_small([#{control_char := $w}|Cs], #{w := W} = Cnts) -> |
|
|
|
count_small(Cs, Cnts#{w := W + 1}); |
|
|
|
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(Cs, Cnts#{other := Other + io_lib:chars_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}) -> |
|
|
|
{P, S, W, Other}. |
|
|
|
|
|
|
|
%% build_small([Control]) -> io_lib:chars(). |
|
|
|
%% Interpret the control structures, but only the small ones. |
|
|
|
%% The big ones are saved for later. |
|
|
|
%% build_limited([Control], NumberOfPps, NumberOfLimited, |
|
|
|
%% CharsLimit, Indentation) |
|
|
|
%% Interpret the control structures. Count the number of print |
|
|
|
%% remaining and only calculate indentation when necessary. Must also |
|
|
|
%% be smart when calculating indentation for characters in format. |
|
|
|
|
|
|
|
build([#{control_char := C, args := As, width := F, adjust := Ad, |
|
|
|
precision := P, pad_char := Pad, encoding := Enc, |
|
|
|
strings := Str} | Cs], Pc0, I) -> |
|
|
|
S = control(C, As, F, Ad, P, Pad, Enc, Str, I), |
|
|
|
Pc1 = decr_pc(C, Pc0), |
|
|
|
build_small([#{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(Cs)]; |
|
|
|
S -> lists:flatten(S) ++ build_small(Cs) |
|
|
|
end; |
|
|
|
build_small([C|Cs]) -> [C|build_small(Cs)]; |
|
|
|
build_small([]) -> []. |
|
|
|
|
|
|
|
build_limited([#{control_char := C, args := As, width := F, adjust := Ad, |
|
|
|
precision := P, pad_char := Pad, encoding := Enc, |
|
|
|
strings := Str} | Cs], NumOfPs0, Count0, MaxLen0, I) -> |
|
|
|
MaxChars = if |
|
|
|
MaxLen0 < 0 -> MaxLen0; |
|
|
|
true -> MaxLen0 div Count0 |
|
|
|
end, |
|
|
|
S = control_limited(C, As, F, Ad, P, Pad, Enc, Str, MaxChars, I), |
|
|
|
NumOfPs = decr_pc(C, NumOfPs0), |
|
|
|
Count = Count0 - 1, |
|
|
|
MaxLen = if |
|
|
|
MaxLen0 < 0 -> % optimization |
|
|
|
MaxLen0; |
|
|
|
true -> |
|
|
|
Len = io_lib:chars_length(S), |
|
|
|
sub(MaxLen0, Len) |
|
|
|
end, |
|
|
|
if |
|
|
|
Pc1 > 0 -> [S|build(Cs, Pc1, indentation(S, I))]; |
|
|
|
true -> [S|build(Cs, Pc1, I)] |
|
|
|
NumOfPs > 0 -> [S|build_limited(Cs, NumOfPs, Count, |
|
|
|
MaxLen, indentation(S, I))]; |
|
|
|
true -> [S|build_limited(Cs, NumOfPs, Count, MaxLen, I)] |
|
|
|
end; |
|
|
|
build([$\n|Cs], Pc, _I) -> [$\n|build(Cs, Pc, 0)]; |
|
|
|
build([$\t|Cs], Pc, I) -> [$\t|build(Cs, Pc, ((I + 8) div 8) * 8)]; |
|
|
|
build([C|Cs], Pc, I) -> [C|build(Cs, Pc, I+1)]; |
|
|
|
build([], _Pc, _I) -> []. |
|
|
|
build_limited([$\n|Cs], NumOfPs, Count, MaxLen, _I) -> |
|
|
|
[$\n|build_limited(Cs, NumOfPs, Count, MaxLen, 0)]; |
|
|
|
build_limited([$\t|Cs], NumOfPs, Count, MaxLen, I) -> |
|
|
|
[$\t|build_limited(Cs, NumOfPs, Count, MaxLen, ((I + 8) div 8) * 8)]; |
|
|
|
build_limited([C|Cs], NumOfPs, Count, MaxLen, I) -> |
|
|
|
[C|build_limited(Cs, NumOfPs, Count, MaxLen, I+1)]; |
|
|
|
build_limited([], _, _, _, _) -> []. |
|
|
|
|
|
|
|
decr_pc($p, Pc) -> Pc - 1; |
|
|
|
decr_pc($P, Pc) -> Pc - 1; |
|
|
|
decr_pc(_, Pc) -> Pc. |
|
|
|
|
|
|
|
|
|
|
|
%% 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 :: eFmt:chars(), |
|
|
|
String :: io_lib:chars(), |
|
|
|
StartIndent :: integer(). |
|
|
|
|
|
|
|
indentation([$\n|Cs], _I) -> indentation(Cs, 0); |
|
|
@ -252,67 +321,74 @@ indentation([C|Cs], I) -> |
|
|
|
indentation(Cs, indentation(C, I)); |
|
|
|
indentation([], I) -> I. |
|
|
|
|
|
|
|
%% control(FormatChar, [Argument], FieldWidth, Adjust, Precision, PadChar, |
|
|
|
%% Encoding, Indentation) -> String |
|
|
|
%% 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, _Str, _I) -> |
|
|
|
term(eFmt:write(A, [{depth,-1}, {encoding, Enc}]), F, Adj, P, Pad); |
|
|
|
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(eFmt:write(A, [{depth,Depth}, {encoding, Enc}]), F, Adj, P, Pad); |
|
|
|
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, latin1, _Str, _I) when is_atom(A) -> |
|
|
|
%% control_small(FormatChar, [Argument], FieldWidth, Adjust, Precision, |
|
|
|
%% PadChar, Encoding) -> String |
|
|
|
%% control_limited(FormatChar, [Argument], FieldWidth, Adjust, Precision, |
|
|
|
%% PadChar, Encoding, StringP, ChrsLim, Indentation) -> String |
|
|
|
%% These are the dispatch functions for the various formatting controls. |
|
|
|
|
|
|
|
control_small($s, [A], F, Adj, P, Pad, latin1=Enc) when is_atom(A) -> |
|
|
|
L = iolist_to_chars(atom_to_list(A)), |
|
|
|
string(L, F, Adj, P, Pad); |
|
|
|
control($s, [A], F, Adj, P, Pad, unicode, _Str, _I) when is_atom(A) -> |
|
|
|
string(atom_to_list(A), F, Adj, P, Pad); |
|
|
|
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, _Str, _I) -> |
|
|
|
L = cdata_to_chars(L0), |
|
|
|
uniconv(string(L, F, Adj, P, Pad)); |
|
|
|
control($e, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) -> |
|
|
|
string(L, F, Adj, P, Pad, Enc); |
|
|
|
control_small($s, [A], F, Adj, P, Pad, unicode=Enc) when is_atom(A) -> |
|
|
|
string(atom_to_list(A), F, Adj, P, Pad, Enc); |
|
|
|
control_small($e, [A], F, Adj, P, Pad, _Enc) when is_float(A) -> |
|
|
|
fwrite_e(A, F, Adj, P, Pad); |
|
|
|
control($f, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) -> |
|
|
|
control_small($f, [A], F, Adj, P, Pad, _Enc) when is_float(A) -> |
|
|
|
fwrite_f(A, F, Adj, P, Pad); |
|
|
|
control($g, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_float(A) -> |
|
|
|
control_small($g, [A], F, Adj, P, Pad, _Enc) when is_float(A) -> |
|
|
|
fwrite_g(A, F, Adj, P, Pad); |
|
|
|
control($b, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($b, [A], F, Adj, P, Pad, _Enc) when is_integer(A) -> |
|
|
|
unprefixed_integer(A, F, Adj, base(P), Pad, true); |
|
|
|
control($B, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($B, [A], F, Adj, P, Pad, _Enc) when is_integer(A) -> |
|
|
|
unprefixed_integer(A, F, Adj, base(P), Pad, false); |
|
|
|
control($x, [A,Prefix], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A), |
|
|
|
is_atom(Prefix) -> |
|
|
|
control_small($x, [A,Prefix], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A) -> |
|
|
|
true = eFmt:deep_char_list(Prefix), %Check if Prefix a character list |
|
|
|
control_small($x, [A,Prefix], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A), |
|
|
|
is_atom(Prefix) -> |
|
|
|
control_small($X, [A,Prefix], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A) -> |
|
|
|
true = eFmt:deep_char_list(Prefix), %Check if Prefix a character list |
|
|
|
control_small($X, [A,Prefix], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($+, [A], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($#, [A], F, Adj, P, Pad, _Enc) 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, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($c, [A], F, Adj, P, Pad, unicode) when is_integer(A) -> |
|
|
|
char(A, F, Adj, P, Pad); |
|
|
|
control($c, [A], F, Adj, P, Pad, _Enc, _Str, _I) when is_integer(A) -> |
|
|
|
control_small($c, [A], F, Adj, P, Pad, _Enc) when is_integer(A) -> |
|
|
|
char(A band 255, F, Adj, P, Pad); |
|
|
|
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) -> []. |
|
|
|
control_small($~, [], F, Adj, P, Pad, _Enc) -> char($~, F, Adj, P, Pad); |
|
|
|
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, 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, CL, _I) -> |
|
|
|
L = cdata_to_chars(L0, F, CL), |
|
|
|
uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc)); |
|
|
|
control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, CL, _I) -> |
|
|
|
Chars = io_lib:write(A, [{depth, -1}, {encoding, Enc}, {chars_limit, CL}]), |
|
|
|
term(Chars, F, Adj, P, Pad); |
|
|
|
control_limited($p, [A], F, Adj, P, Pad, Enc, Str, CL, I) -> |
|
|
|
print(A, -1, F, Adj, P, Pad, Enc, Str, CL, I); |
|
|
|
control_limited($W, [A,Depth], F, Adj, P, Pad, Enc, _Str, CL, _I) |
|
|
|
when is_integer(Depth) -> |
|
|
|
Chars = io_lib:write(A, [{depth, Depth}, {encoding, Enc}, {chars_limit, CL}]), |
|
|
|
term(Chars, F, Adj, P, Pad); |
|
|
|
control_limited($P, [A,Depth], F, Adj, P, Pad, Enc, Str, CL, I) |
|
|
|
when is_integer(Depth) -> |
|
|
|
print(A, Depth, F, Adj, P, Pad, Enc, Str, CL, I). |
|
|
|
|
|
|
|
-ifdef(UNICODE_AS_BINARIES). |
|
|
|
uniconv(C) -> |
|
|
@ -335,7 +411,7 @@ base(B) when is_integer(B) -> |
|
|
|
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 = lists:flatlength(T), |
|
|
|
L = io_lib:chars_length(T), |
|
|
|
P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end), |
|
|
|
if |
|
|
|
L > P -> |
|
|
@ -349,12 +425,13 @@ 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, 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}, |
|
|
|
print(T, D, none, Adj, P, Pad, E, Str, ChLim, I) -> |
|
|
|
print(T, D, 80, Adj, P, Pad, E, Str, ChLim, I); |
|
|
|
print(T, D, F, Adj, none, Pad, E, Str, ChLim, I) -> |
|
|
|
print(T, D, F, Adj, I+1, Pad, E, Str, ChLim, I); |
|
|
|
print(T, D, F, right, P, _Pad, Enc, Str, ChLim, _I) -> |
|
|
|
Options = [{chars_limit, ChLim}, |
|
|
|
{column, P}, |
|
|
|
{line_length, F}, |
|
|
|
{depth, D}, |
|
|
|
{encoding, Enc}, |
|
|
@ -380,7 +457,7 @@ float_e(_Fl, {Ds,E}, P) -> |
|
|
|
{Fs,false} -> [Fs|float_exp(E-1)] |
|
|
|
end. |
|
|
|
|
|
|
|
%% float_man([Digit], Icount, Dcount) -> {[Chars],CarryFlag}. |
|
|
|
%% float_man([Digit], Icount, Dcount) -> {[Char],CarryFlag}. |
|
|
|
%% Generate the characters in the mantissa from the digits with Icount |
|
|
|
%% characters before the '.' and Dcount decimals. Handle carry and let |
|
|
|
%% caller decide what to do at top. |
|
|
@ -395,7 +472,7 @@ float_man([D|Ds], I, Dc) -> |
|
|
|
{Cs,false} -> {[D|Cs],false} |
|
|
|
end; |
|
|
|
float_man([], I, Dc) -> %Pad with 0's |
|
|
|
{string:chars($0, I, [$.|string:chars($0, Dc)]),false}. |
|
|
|
{lists:duplicate(I, $0) ++ [$.|lists:duplicate(Dc, $0)],false}. |
|
|
|
|
|
|
|
float_man([D|_], 0) when D >= $5 -> {[],true}; |
|
|
|
float_man([_|_], 0) -> {[],false}; |
|
|
@ -405,7 +482,7 @@ float_man([D|Ds], Dc) -> |
|
|
|
{Cs,true} -> {[D+1|Cs],false}; |
|
|
|
{Cs,false} -> {[D|Cs],false} |
|
|
|
end; |
|
|
|
float_man([], Dc) -> {string:chars($0, Dc),false}. %Pad with 0's |
|
|
|
float_man([], Dc) -> {lists:duplicate(Dc, $0),false}. %Pad with 0's |
|
|
|
|
|
|
|
%% float_exp(Exponent) -> [Char]. |
|
|
|
%% Generate the exponent of a floating point number. Always include sign. |
|
|
@ -429,7 +506,7 @@ fwrite_f(Fl, F, Adj, P, Pad) when P >= 1 -> |
|
|
|
float_f(Fl, Fd, P) when Fl < 0.0 -> |
|
|
|
[$-|float_f(-Fl, Fd, P)]; |
|
|
|
float_f(Fl, {Ds,E}, P) when E =< 0 -> |
|
|
|
float_f(Fl, {string:chars($0, -E+1, Ds),1}, P); %Prepend enough 0's |
|
|
|
float_f(Fl, {lists:duplicate(-E+1, $0)++Ds,1}, P); %Prepend enough 0's |
|
|
|
float_f(_Fl, {Ds,E}, P) -> |
|
|
|
case float_man(Ds, E, P) of |
|
|
|
{Fs,true} -> "1" ++ Fs; %Handle carry |
|
|
@ -641,7 +718,10 @@ fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 -> |
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
%% iolist_to_chars(iolist()) -> deep_char_list() |
|
|
|
iolist_to_chars(Cs, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F -> |
|
|
|
iolist_to_chars(Cs); |
|
|
|
iolist_to_chars(Cs, _, CharsLimit) -> |
|
|
|
limit_iolist_to_chars(Cs, sub(CharsLimit, 3), [], normal). % three dots |
|
|
|
|
|
|
|
iolist_to_chars([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 -> |
|
|
|
[C | iolist_to_chars(Cs)]; |
|
|
@ -652,12 +732,34 @@ iolist_to_chars([]) -> |
|
|
|
iolist_to_chars(B) when is_binary(B) -> |
|
|
|
binary_to_list(B). |
|
|
|
|
|
|
|
%% cdata() :: clist() | cbinary() |
|
|
|
%% clist() :: maybe_improper_list(char() | cbinary() | clist(), |
|
|
|
%% cbinary() | nil()) |
|
|
|
%% cbinary() :: unicode:unicode_binary() | unicode:latin1_binary() |
|
|
|
limit_iolist_to_chars(Cs, 0, S, normal) -> |
|
|
|
L = limit_iolist_to_chars(Cs, 4, S, final), |
|
|
|
case iolist_size(L) of |
|
|
|
N when N < 4 -> L; |
|
|
|
4 -> "..." |
|
|
|
end; |
|
|
|
limit_iolist_to_chars(_Cs, 0, _S, final) -> []; |
|
|
|
limit_iolist_to_chars([C|Cs], Limit, S, Mode) when C >= $\000, C =< $\377 -> |
|
|
|
[C | limit_iolist_to_chars(Cs, Limit - 1, S, Mode)]; |
|
|
|
limit_iolist_to_chars([I|Cs], Limit, S, Mode) -> |
|
|
|
limit_iolist_to_chars(I, Limit, [Cs|S], Mode); |
|
|
|
limit_iolist_to_chars([], _Limit, [], _Mode) -> |
|
|
|
[]; |
|
|
|
limit_iolist_to_chars([], Limit, [Cs|S], Mode) -> |
|
|
|
limit_iolist_to_chars(Cs, Limit, S, Mode); |
|
|
|
limit_iolist_to_chars(B, Limit, S, Mode) when is_binary(B) -> |
|
|
|
case byte_size(B) of |
|
|
|
Sz when Sz > Limit -> |
|
|
|
{B1, B2} = split_binary(B, Limit), |
|
|
|
[binary_to_list(B1) | limit_iolist_to_chars(B2, 0, S, Mode)]; |
|
|
|
Sz -> |
|
|
|
[binary_to_list(B) | limit_iolist_to_chars([], Limit-Sz, S, Mode)] |
|
|
|
end. |
|
|
|
|
|
|
|
%% cdata_to_chars(cdata()) -> eFmt:deep_char_list() |
|
|
|
cdata_to_chars(Cs, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F -> |
|
|
|
cdata_to_chars(Cs); |
|
|
|
cdata_to_chars(Cs, _, CharsLimit) -> |
|
|
|
limit_cdata_to_chars(Cs, sub(CharsLimit, 3), normal). % three dots |
|
|
|
|
|
|
|
cdata_to_chars([C|Cs]) when is_integer(C), C >= $\000 -> |
|
|
|
[C | cdata_to_chars(Cs)]; |
|
|
@ -671,32 +773,58 @@ cdata_to_chars(B) when is_binary(B) -> |
|
|
|
_ -> binary_to_list(B) |
|
|
|
end. |
|
|
|
|
|
|
|
limit_cdata_to_chars(Cs, 0, normal) -> |
|
|
|
L = limit_cdata_to_chars(Cs, 4, final), |
|
|
|
case string:length(L) of |
|
|
|
N when N < 4 -> L; |
|
|
|
4 -> "..." |
|
|
|
end; |
|
|
|
limit_cdata_to_chars(_Cs, 0, final) -> []; |
|
|
|
limit_cdata_to_chars(Cs, Limit, Mode) -> |
|
|
|
case string:next_grapheme(Cs) of |
|
|
|
{error, <<C,Cs1/binary>>} -> |
|
|
|
%% This is how ~ts handles Latin1 binaries with option |
|
|
|
%% chars_limit. |
|
|
|
[C | limit_cdata_to_chars(Cs1, Limit - 1, Mode)]; |
|
|
|
{error, [C|Cs1]} -> % not all versions of module string return this |
|
|
|
[C | limit_cdata_to_chars(Cs1, Limit - 1, Mode)]; |
|
|
|
[] -> |
|
|
|
[]; |
|
|
|
[GC|Cs1] -> |
|
|
|
[GC | limit_cdata_to_chars(Cs1, Limit - 1, Mode)] |
|
|
|
end. |
|
|
|
|
|
|
|
limit_field(F, CharsLimit) when CharsLimit < 0; F =:= none -> |
|
|
|
F; |
|
|
|
limit_field(F, CharsLimit) -> |
|
|
|
max(3, min(F, CharsLimit)). |
|
|
|
|
|
|
|
%% string(String, Field, Adjust, Precision, PadChar) |
|
|
|
|
|
|
|
string(S, none, _Adj, none, _Pad) -> S; |
|
|
|
string(S, F, Adj, none, Pad) -> |
|
|
|
string_field(S, F, Adj, lists:flatlength(S), Pad); |
|
|
|
string(S, none, _Adj, P, Pad) -> |
|
|
|
string_field(S, P, left, lists:flatlength(S), Pad); |
|
|
|
string(S, F, Adj, P, Pad) when F >= P -> |
|
|
|
N = lists:flatlength(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) -> |
|
|
|
string_field(S, P, left, io_lib:chars_length(S), Pad, Enc); |
|
|
|
string(S, F, Adj, P, Pad, Enc) when F >= P -> |
|
|
|
N = io_lib:chars_length(S), |
|
|
|
if F > P -> |
|
|
|
if N > P -> |
|
|
|
adjust(flat_trunc(S, P), chars(Pad, F-P), Adj); |
|
|
|
adjust(flat_trunc(S, P, Enc), chars(Pad, F-P), Adj); |
|
|
|
N < P -> |
|
|
|
adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj); |
|
|
|
true -> % N == P |
|
|
|
adjust(S, chars(Pad, F-P), Adj) |
|
|
|
end; |
|
|
|
true -> % F == P |
|
|
|
string_field(S, F, Adj, N, Pad) |
|
|
|
string_field(S, F, Adj, N, Pad, Enc) |
|
|
|
end. |
|
|
|
|
|
|
|
string_field(S, F, _Adj, N, _Pad) when N > F -> |
|
|
|
flat_trunc(S, F); |
|
|
|
string_field(S, F, Adj, N, Pad) when N < F -> |
|
|
|
string_field(S, F, _Adj, N, _Pad, Enc) when N > F -> |
|
|
|
flat_trunc(S, F, Enc); |
|
|
|
string_field(S, F, Adj, N, Pad, _Enc) when N < F -> |
|
|
|
adjust(S, chars(Pad, F-N), Adj); |
|
|
|
string_field(S, _, _, _, _) -> % N == F |
|
|
|
string_field(S, _, _, _, _, _) -> % N == F |
|
|
|
S. |
|
|
|
|
|
|
|
%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase) |
|
|
@ -748,21 +876,13 @@ adjust(Data, Pad, right) -> [Pad|Data]. |
|
|
|
|
|
|
|
%% Flatten and truncate a deep list to at most N elements. |
|
|
|
|
|
|
|
flat_trunc(List, N) when is_integer(N), N >= 0 -> |
|
|
|
flat_trunc(List, N, [], []). |
|
|
|
|
|
|
|
flat_trunc(L, 0, _, R) when is_list(L) -> |
|
|
|
lists:reverse(R); |
|
|
|
flat_trunc([H|T], N, S, R) when is_list(H) -> |
|
|
|
flat_trunc(H, N, [T|S], R); |
|
|
|
flat_trunc([H|T], N, S, R) -> |
|
|
|
flat_trunc(T, N-1, S, [H|R]); |
|
|
|
flat_trunc([], N, [H|S], R) -> |
|
|
|
flat_trunc(H, N, S, R); |
|
|
|
flat_trunc([], _, [], R) -> |
|
|
|
lists:reverse(R). |
|
|
|
flat_trunc(List, N, latin1) when 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). |
|
|
|
|
|
|
|
%% A deep version of string:chars/2,3 |
|
|
|
%% A deep version of lists:duplicate/2 |
|
|
|
|
|
|
|
chars(_C, 0) -> |
|
|
|
[]; |
|
|
@ -795,3 +915,15 @@ lowercase([H|T]) -> |
|
|
|
[H|lowercase(T)]; |
|
|
|
lowercase([]) -> |
|
|
|
[]. |
|
|
|
|
|
|
|
%% Make sure T does change sign. |
|
|
|
sub(T, _) when T < 0 -> T; |
|
|
|
sub(T, E) when T >= E -> T - E; |
|
|
|
sub(_, _) -> 0. |
|
|
|
|
|
|
|
get_option(Key, TupleList, Default) -> |
|
|
|
case lists:keyfind(Key, 1, TupleList) of |
|
|
|
false -> Default; |
|
|
|
{Key, Value} -> Value; |
|
|
|
_ -> Default |
|
|
|
end. |