|
|
@ -29,10 +29,133 @@ |
|
|
|
-module(trunc_io). |
|
|
|
-author('matthias@corelatus.se'). |
|
|
|
%% And thanks to Chris Newcombe for a bug fix |
|
|
|
-export([print/2, fprint/2, safe/2]). % interface functions |
|
|
|
-export([format/3, print/2, fprint/2, safe/2]). % interface functions |
|
|
|
-export([perf/0, perf/3, perf1/0, test/0, test/2]). % testing functions |
|
|
|
-version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $"). |
|
|
|
|
|
|
|
format(String, Args, Max) -> |
|
|
|
Parts = re:split(String, |
|
|
|
"(~(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+|\\*|)(?:t|)(?:[cfegswpWPBX#bx+ni~]))", |
|
|
|
[{return, list}, trim]), |
|
|
|
Maxlen = Max - length(String), |
|
|
|
format(Parts, Args, Maxlen, [], []). |
|
|
|
|
|
|
|
format([], _Args, Max, Acc, ArgAcc) -> |
|
|
|
FmtArgs = resolve_futures(Max, ArgAcc), |
|
|
|
io_lib:format(lists:flatten(lists:reverse(Acc)), lists:reverse(FmtArgs)); |
|
|
|
format([[] | T], Args, Max, Acc, ArgAcc) -> |
|
|
|
% discard the null list generated by split |
|
|
|
format(T, Args, Max, Acc, ArgAcc); |
|
|
|
format(["~~" | T], Args, Max, Acc, ArgAcc) -> |
|
|
|
format(T, Args, Max+1, ["~~" | Acc], ArgAcc); |
|
|
|
format(["~n" | T], Args, Max, Acc, ArgAcc) -> |
|
|
|
% ignore newlines for the purposes of argument indexing |
|
|
|
format(T, Args, Max+1, ["~n" | Acc], ArgAcc); |
|
|
|
format(["~i" | T], [AH | AT], Max, Acc, ArgAcc) -> |
|
|
|
% ~i means ignore this argument, but we'll just pass it through |
|
|
|
format(T, AT, Max+2, ["~i" | Acc], [AH | ArgAcc]); |
|
|
|
format([[$~|H]| T], [AH1, AH2 | AT], Max, Acc, ArgAcc) when H == "X"; H == "x" -> |
|
|
|
%% ~X consumes 2 arguments. It only prints integers so we can leave it alone |
|
|
|
format(T, AT, Max, ["~X" | Acc], [AH2, AH1 | ArgAcc]); |
|
|
|
format([[$~|H]| T], [AH1, _AH2 | AT], Max, Acc, ArgAcc) when H == "W"; H == "P" -> |
|
|
|
%% ~P and ~W consume 2 arguments, the second one being a depth limiter. |
|
|
|
%% trunc_io isn't (yet) depth aware, so we can't honor this format string |
|
|
|
%% safely at the moment, so just treat it like a regular ~p |
|
|
|
%% TODO support for depth limiting |
|
|
|
case print(AH1, Max + 2) of |
|
|
|
{_Res, Max} -> |
|
|
|
% this isn't the last argument, but it consumed all available space |
|
|
|
% delay calculating the print size until the end |
|
|
|
format(T, AT, Max + 2, ["~s" | Acc], [{future, AH1} | ArgAcc]); |
|
|
|
{String, Length} -> |
|
|
|
format(T, AT, Max + 2 - Length, ["~s" | Acc], [String | ArgAcc]) |
|
|
|
end; |
|
|
|
format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) when length(H) == 1 -> |
|
|
|
% single character format specifier, relatively simple |
|
|
|
case H of |
|
|
|
_ when H == "p"; H == "w"; H == "s" -> |
|
|
|
%okay, these are prime candidates for rewriting |
|
|
|
case print(AH, Max + 2) of |
|
|
|
{_Res, Max} -> |
|
|
|
% this isn't the last argument, but it consumed all available space |
|
|
|
% delay calculating the print size until the end |
|
|
|
format(T, AT, Max + 2, ["~s" | Acc], [{future, AH} | ArgAcc]); |
|
|
|
{String, Length} -> |
|
|
|
{Value, RealLen} = case H of |
|
|
|
"s" -> |
|
|
|
% strip off the doublequotes |
|
|
|
{string:substr(String, 2, length(String) -2), Length -2}; |
|
|
|
_ -> |
|
|
|
{String, Length} |
|
|
|
end, |
|
|
|
format(T, AT, Max + 2 - RealLen, ["~s" | Acc], [Value | ArgAcc]) |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
% whatever, just pass them on through |
|
|
|
format(T, AT, Max, [[$~ | H] | Acc], [AH | ArgAcc]) |
|
|
|
end; |
|
|
|
format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) -> |
|
|
|
% complicated format specifier, gonna have to parse it.. |
|
|
|
%case re:run(H, "^(?:-??(\\d+|\\*)\\.|\\.|)(?:-??(\\d+|\\*)\\.|\\.|)(-??.|)(t|)([cfegswpWPBX#bx+ni])$", [{capture, all_but_first, list}]) of |
|
|
|
%% its actually simpler to just look at the last character in the string |
|
|
|
case lists:nth(length(H), H) of |
|
|
|
C when C == $p; C == $w; C == $s -> |
|
|
|
%{match, [_F, _P, _Pad, _Mod, C]} when C == "p"; C=="w"; C=="s" -> |
|
|
|
%okay, these are prime candidates for rewriting |
|
|
|
case print(AH, Max + length(H) + 1) of |
|
|
|
{_Res, Max} -> |
|
|
|
% this isn't the last argument, but it consumed all available space |
|
|
|
% delay calculating the print size until the end |
|
|
|
format(T, AT, Max + length(H) + 1, ["~s" | Acc], [{future, AH} | ArgAcc]); |
|
|
|
{String, Length} -> |
|
|
|
{Value, RealLen} = case H of |
|
|
|
"s" -> |
|
|
|
% strip off the doublequotes |
|
|
|
{string:substr(String, 2, length(String) -2), Length -2}; |
|
|
|
_ -> |
|
|
|
{String, Length} |
|
|
|
end, |
|
|
|
format(T, AT, Max + length(H) + 1 - RealLen, ["~s" | Acc], [Value | ArgAcc]) |
|
|
|
end; |
|
|
|
C when C == $P; C == $W -> |
|
|
|
%{match, [_F, _P, _Pad, _Mod, C]} when C == "P"; C == "W" -> |
|
|
|
%% ~P and ~W consume 2 arguments, the second one being a depth limiter. |
|
|
|
%% trunc_io isn't (yet) depth aware, so we can't honor this format string |
|
|
|
%% safely at the moment, so just treat it like a regular ~p |
|
|
|
%% TODO support for depth limiting |
|
|
|
[_ | AT2] = AT, |
|
|
|
case print(AH, Max + 2) of |
|
|
|
{_Res, Max} -> |
|
|
|
% this isn't the last argument, but it consumed all available space |
|
|
|
% delay calculating the print size until the end |
|
|
|
format(T, AT2, Max + 2, ["~s" | Acc], [{future, AH} | ArgAcc]); |
|
|
|
{String, Length} -> |
|
|
|
format(T, AT2, Max + 2 - Length, ["~s" | Acc], [String | ArgAcc]) |
|
|
|
end; |
|
|
|
C when C == $X; C == $x -> |
|
|
|
%{match, [_F, _P, _Pad, _Mod, C]} when C == "X"; C == "x" -> |
|
|
|
%% ~X consumes 2 arguments. It only prints integers so we can leave it alone |
|
|
|
[AH2 | AT2] = AT, |
|
|
|
format(T, AT2, Max, [[$~|H]|Acc], [AH2, AH |ArgAcc]); |
|
|
|
_ -> |
|
|
|
format(T, AT, Max, [[$~|H] | Acc], [AH|ArgAcc]) |
|
|
|
%nomatch -> |
|
|
|
%io:format("unable to match format pattern ~~~p~n", [H]), |
|
|
|
%format(T, AT, Max, [[$~|H] | Acc], [AH|ArgAcc]) |
|
|
|
end; |
|
|
|
format([H | T], Args, Max, Acc, ArgAcc) -> |
|
|
|
format(T, Args, Max, [H | Acc], ArgAcc). |
|
|
|
|
|
|
|
%% for all the really big terms encountered in a format/3 call, try to give each of them an equal share |
|
|
|
resolve_futures(Max, Args) -> |
|
|
|
Count = length(lists:filter(fun({future, _}) -> true; (_) -> false end, Args)), |
|
|
|
case Count of |
|
|
|
0 -> |
|
|
|
Args; |
|
|
|
_ -> |
|
|
|
SingleFmt = Max div Count, |
|
|
|
lists:map(fun({future, Value}) -> element(1, print(Value, SingleFmt)); (X) -> X end, Args) |
|
|
|
end. |
|
|
|
|
|
|
|
%% @doc Returns an flattened list containing the ASCII representation of the given |
|
|
|
%% term. |
|
|
@ -76,18 +199,28 @@ print(Atom, _Max) when is_atom(Atom) -> |
|
|
|
print(<<>>, _Max) -> |
|
|
|
{"<<>>", 4}; |
|
|
|
|
|
|
|
print(Binary, 0) when is_binary(Binary) -> |
|
|
|
{"<<..>>", 6}; |
|
|
|
|
|
|
|
print(Binary, Max) when is_binary(Binary) -> |
|
|
|
B = binary_to_list(Binary, 1, lists:min([Max, size(Binary)])), |
|
|
|
{L, Len} = alist_start(B, Max-4), |
|
|
|
{["<<", L, ">>"], Len}; |
|
|
|
{["<<", L, ">>"], Len+4}; |
|
|
|
|
|
|
|
print(Float, _Max) when is_float(Float) -> |
|
|
|
L = float_to_list(Float), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print(Fun, _Max) when is_function(Fun) -> |
|
|
|
print(Fun, Max) when is_function(Fun) -> |
|
|
|
L = erlang:fun_to_list(Fun), |
|
|
|
{L, length(L)}; |
|
|
|
case length(L) > Max of |
|
|
|
true -> |
|
|
|
S = erlang:max(5, Max), |
|
|
|
Res = string:substr(L, 1, S) ++ "..>", |
|
|
|
{Res, length(Res)}; |
|
|
|
_ -> |
|
|
|
{L, length(L)} |
|
|
|
end; |
|
|
|
|
|
|
|
print(Integer, _Max) when is_integer(Integer) -> |
|
|
|
L = integer_to_list(Integer), |
|
|
@ -146,7 +279,7 @@ alist_start([], _) -> {"[]", 2}; |
|
|
|
alist_start(_, Max) when Max < 4 -> {"...", 3}; |
|
|
|
alist_start([H|T], Max) when H >= 16#20, H =< 16#7e -> % definitely printable |
|
|
|
{L, Len} = alist([H|T], Max-1), |
|
|
|
{[$\"|L], Len + 1}; |
|
|
|
{[$"|L], Len + 1}; |
|
|
|
alist_start([H|T], Max) when H == 9; H == 10; H == 13 -> % show as space |
|
|
|
{L, Len} = alist(T, Max-1), |
|
|
|
{[$ |L], Len + 1}; |
|
|
@ -164,7 +297,7 @@ alist([H|T], Max) when H == 9; H == 10; H == 13 -> % show as space |
|
|
|
{[$ |L], Len + 1}; |
|
|
|
alist(L, Max) -> |
|
|
|
{R, Len} = list_body(L, Max-3), |
|
|
|
{[$\", $[, R, $]], Len + 3}. |
|
|
|
{[$", $[, R, $]], Len + 3}. |
|
|
|
|
|
|
|
|
|
|
|
%%-------------------- |
|
|
|