diff --git a/src/eFmt.erl b/src/eFmt.erl index 9ff9938..7e0b7f1 100644 --- a/src/eFmt.erl +++ b/src/eFmt.erl @@ -67,6 +67,17 @@ , chars_length/1 ]). +-record(fmtSpec, { + ctlChar :: char() %% 控制序列的类型 $p $w + , args :: [any()] %% 是控制序列使用的参数的列表,如果控制序列不带任何参数,则为空列表。 + , width :: 'none' | integer() %% 字段宽度 + , adjust :: 'left' | 'right' %% 对齐方式 + , precision :: 'none' | integer() %% 打印参数的精度 + , padChar :: char() %% 填充字符 + , encoding :: 'unicode' | 'latin1' %% 如果存在翻译修饰符t,则编码设置为true + , strings :: boolean() %% 如果存在修饰符l,则将 string设置为false。 +}). + -export_type([ chars/0 , latin1_string/0 @@ -74,6 +85,7 @@ , fread_error/0 , fread_item/0 , format_spec/0 + , fmtSpec/0 , chars_limit/0 ]). @@ -105,6 +117,8 @@ integer() | float(). +-type fmtSpec() :: #fmtSpec{}. + -type format_spec() :: #{ control_char := char(), diff --git a/src/eFmtFormat.erl b/src/eFmtFormat.erl index bb43c98..263cc79 100644 --- a/src/eFmtFormat.erl +++ b/src/eFmtFormat.erl @@ -12,55 +12,39 @@ , build/2 ]). -%% Format the arguments in Args after string Format. Just generate -%% an error if there is an error in the arguments. +%% 在字符串格式之后将参数格式化为Args。刚产生 +%% 如果参数中有错误,则为错误。 %% -%% To do the printing command correctly we need to calculate the -%% current indentation for everything before it. This may be very -%% expensive, especially when it is not needed, so we first determine -%% if, and for how long, we need to calculate the indentations. We do -%% this by first collecting all the control sequences and -%% corresponding arguments, then counting the print sequences and -%% then building the output. This method has some drawbacks, it does -%% two passes over the format string and creates more temporary data, -%% and it also splits the handling of the control characters into two -%% parts. - --spec fwrite(Format, Data) -> io_lib:chars() when - Format :: io:format(), - Data :: [term()]. - +%% 要正确执行打印命令,我们需要计算 +%% 当前缩进的所有内容。这可能非常 +%% 价格昂贵,尤其是在不需要时,因此我们首先确定 +%% 是否以及需要多长时间来计算缩进。我们的确是 +%% 首先收集所有控制序列,然后 +%% 相应的参数,然后计算打印顺序,然后 +%% 然后构建输出。这种方法有一些缺点,它确实 +%% 在格式字符串上两次传递并创建更多临时数据, +%% 并且还将控制字符的处理分为两个 +%% 部分。 + +-spec fwrite(Format :: io:format(), Data :: [term()]) -> eFmt:chars(). 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(). - +-spec fwrite(Format :: io:format(), Data :: [term()], Options :: [{'chars_limit', CharsLimit :: integer()}]) -> eFmt:chars(). fwrite(Format, Args, Options) -> build(scan(Format, Args), Options). %% Build the output text for a pre-parsed format list. --spec build(FormatList) -> io_lib:chars() when - FormatList :: [char() | io_lib:format_spec()]. - +-spec build(FormatList :: [char() | eFmt:format_spec()]) -> eFmt:chars(). build(Cs) -> 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(). - +-spec build(FormatList :: [char() | eFmt:format_spec()], Options :: [{'chars_limit', CharsLimit :: integer()}]) -> eFmt:chars(). build(Cs, Options) -> - CharsLimit = get_option(chars_limit, Options, -1), + CharsLimit = getOpt(chars_limit, Options, -1), Res1 = build_small(Cs), - {P, S, W, Other} = count_small(Res1), + {P, S, W, Other} = count_small(Res1, 0, 0, 0, 0), case P + S + W of 0 -> Res1; @@ -74,62 +58,17 @@ build(Cs, Options) -> -spec scan(Format, Data) -> FormatList when Format :: io:format(), Data :: [term()], - FormatList :: [char() | io_lib:format_spec()]. + FormatList :: [char() | eFmt:format_spec()]. -scan(Format, Args) when is_atom(Format) -> - scan(atom_to_list(Format), Args); -scan(Format, Args) when is_binary(Format) -> - scan(binary_to_list(Format), Args); scan(Format, Args) -> - collect(Format, Args). - -%% Revert a pre-parsed format list to a plain character list and a -%% list of arguments. - --spec unscan(FormatList) -> {Format, Data} when - FormatList :: [char() | io_lib:format_spec()], - Format :: io:format(), - Data :: [term()]. - -unscan(Cs) -> - {print(Cs), args(Cs)}. - -args([#{args := As} | Cs]) -> - As ++ args(Cs); -args([_C | Cs]) -> - args(Cs); -args([]) -> - []. - -print([#{control_char := C, width := F, adjust := Ad, precision := P, - pad_char := Pad, encoding := Encoding, strings := Strings} | Cs]) -> - print(C, F, Ad, P, Pad, Encoding, Strings) ++ print(Cs); -print([C | Cs]) -> - [C | print(Cs)]; -print([]) -> - []. - -print(C, F, Ad, P, Pad, Encoding, Strings) -> - [$~] ++ print_field_width(F, Ad) ++ print_precision(P, Pad) ++ - print_pad_char(Pad) ++ print_encoding(Encoding) ++ - print_strings(Strings) ++ [C]. - -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, $\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]. - -print_encoding(unicode) -> "t"; -print_encoding(latin1) -> "". - -print_strings(false) -> "l"; -print_strings(true) -> "". + if + is_atom(Format) -> + collect(atom_to_binary(Format, utf8), Args); + is_list(Format) -> + collect(list_to_binary(Format), Args); + true -> + collect(Format, Args) + end. collect([$~ | Fmt0], Args0) -> {C, Fmt1, Args1} = collect_cseq(Fmt0, Args0), @@ -217,32 +156,25 @@ 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}. -%% 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}) -> +%% count_small([ControlC])->计数。计算大(pPwWsS)打印请求的数量和其他打印(小)请求的字符数。 +count_small([#{control_char := $p} | Cs], P, S, W, Other) -> + count_small(Cs, P + 1, S, W, Other); +count_small([#{control_char := $P} | Cs], P, S, W, Other) -> + count_small(Cs, P + 1, S, W, Other); +count_small([#{control_char := $w} | Cs], P, S, W, Other) -> + count_small(Cs, P, S, W + 1, Other); +count_small([#{control_char := $W} | Cs], P, S, W, Other) -> + count_small(Cs, P, S, W + 1, Other); +count_small([#{control_char := $s} | Cs], P, S, W, Other) -> + count_small(Cs, P, S, W + 1, Other); +count_small([S | Cs], P, S, W, Other) when is_list(S);is_binary(S) -> + count_small(Cs, P, S, W, Other + eFmt:chars_length(S)); +count_small([C | Cs], P, S, W, Other) when is_integer(C) -> + count_small(Cs, P, S, W, Other + 1); +count_small([], P, S, W, Other) -> {P, S, W, Other}. -%% build_small([Control]) -> io_lib:chars(). +%% build_small([Control]) -> eFmt:chars(). %% Interpret the control structures, but only the small ones. %% The big ones are saved for later. %% build_limited([Control], NumberOfPps, NumberOfLimited, @@ -251,8 +183,7 @@ count_small([], #{p := P, s := S, w := W, other := Other}) -> %% remaining and only calculate indentation when necessary. Must also %% be smart when calculating indentation for characters in format. -build_small([#{control_char := C, args := As, width := F, adjust := Ad, - precision := P, pad_char := Pad, encoding := Enc} = CC | Cs]) -> +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) @@ -274,7 +205,7 @@ build_limited([#{control_char := C, args := As, width := F, adjust := Ad, MaxLen0 < 0 -> % optimization MaxLen0; true -> - Len = io_lib:chars_length(S), + Len = eFmt:chars_length(S), sub(MaxLen0, Len) end, if @@ -298,7 +229,7 @@ decr_pc(_, Pc) -> Pc. %% indentation. We assume tabs at 8 cols. -spec indentation(String, StartIndent) -> integer() when - String :: io_lib:chars(), + String :: eFmt:chars(), StartIndent :: integer(). indentation([$\n | Cs], _I) -> indentation(Cs, 0); @@ -334,13 +265,13 @@ 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_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 + true = eFmt:deep_char_list(Prefix), %Check if Prefix a character list prefixed_integer(A, F, Adj, base(P), Pad, Prefix, true); 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_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 + true = eFmt:deep_char_list(Prefix), %Check if Prefix a character list prefixed_integer(A, F, Adj, base(P), Pad, Prefix, false); control_small($+, [A], F, Adj, P, Pad, _Enc) when is_integer(A) -> Base = base(P), @@ -366,13 +297,13 @@ 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}]), + Chars = eFmt: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}]), + Chars = eFmt: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) -> @@ -399,7 +330,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 = io_lib:chars_length(T), + L = eFmt:chars_length(T), P = erlang:min(L, case P0 of none -> F; _ -> min(P0, F) end), if L > P -> @@ -424,7 +355,7 @@ print(T, D, F, right, P, _Pad, Enc, Str, ChLim, _I) -> {depth, D}, {encoding, Enc}, {strings, Str}], - io_lib_pretty:print(T, Options). + eFmt_pretty:print(T, Options). %% fwrite_e(Float, Field, Adjust, Precision, PadChar) @@ -791,11 +722,11 @@ limit_field(F, CharsLimit) -> 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_field(S, F, Adj, eFmt: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_field(S, P, left, eFmt:chars_length(S), Pad, Enc); string(S, F, Adj, P, Pad, Enc) when F >= P -> - N = io_lib:chars_length(S), + N = eFmt:chars_length(S), if F > P -> if N > P -> adjust(flat_trunc(S, P, Enc), chars(Pad, F - P), Adj); @@ -909,9 +840,56 @@ sub(T, _) when T < 0 -> T; sub(T, E) when T >= E -> T - E; sub(_, _) -> 0. -get_option(Key, TupleList, Default) -> +getOpt(Key, TupleList, Default) -> case lists:keyfind(Key, 1, TupleList) of - false -> Default; - {Key, Value} -> Value; - _ -> Default + {_, Value} -> + Value; + _ -> + Default end. + +%% 将预先解析的格式列表还原为纯字符列表和参数列表。 +-spec unscan(FormatList) -> {Format, Data} when + FormatList :: [char() | eFmt:format_spec()], + Format :: io:format(), + Data :: [term()]. + +unscan(Cs) -> + {print(Cs), args(Cs)}. + +args([#{args := As} | Cs]) -> + As ++ args(Cs); +args([_C | Cs]) -> + args(Cs); +args([]) -> + []. + +print([#{control_char := C, width := F, adjust := Ad, precision := P, + pad_char := Pad, encoding := Encoding, strings := Strings} | Cs]) -> + print(C, F, Ad, P, Pad, Encoding, Strings) ++ print(Cs); +print([C | Cs]) -> + [C | print(Cs)]; +print([]) -> + []. + +print(C, F, Ad, P, Pad, Encoding, Strings) -> + [$~] ++ print_field_width(F, Ad) ++ print_precision(P, Pad) ++ + print_pad_char(Pad) ++ print_encoding(Encoding) ++ + print_strings(Strings) ++ [C]. + +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, $\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]. + +print_encoding(unicode) -> "t"; +print_encoding(latin1) -> "". + +print_strings(false) -> "l"; +print_strings(true) -> "".