|
|
@ -29,10 +29,90 @@ |
|
|
|
-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) -> |
|
|
|
io_lib:format(lists:flatten(lists:reverse(Acc)), lists:reverse(ArgAcc)); |
|
|
|
format([[] | T], Args, Max, Acc, ArgAcc) -> |
|
|
|
% discard the null list generated by split |
|
|
|
format(T, Args, Max, 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 == "W"; H == "P"; H == "X" -> |
|
|
|
% these crazy ones consume TWO arguments, just pass them through |
|
|
|
% because W and P implicitly limit size so we trust the user knows |
|
|
|
% what they're doing. Unfortunately we can't change Max at all because |
|
|
|
% depth based limits are not of known length. |
|
|
|
format(T, AT, Max, [[$~ | H] | Acc], [AH2, AH1 | ArgAcc]); |
|
|
|
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 |
|
|
|
{String, Length} = case print(AH, Max) of |
|
|
|
{_Res, Max} when AT /= [] -> |
|
|
|
% this isn't the last argument, but it consumed all available space, give it half instead |
|
|
|
print(AH, Max div 2); |
|
|
|
{Res, Len} -> |
|
|
|
{Res, Len} |
|
|
|
end, |
|
|
|
{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]); |
|
|
|
_ -> |
|
|
|
% whatever, just pass them on through |
|
|
|
format(T, AT, Max, [[$~ | H], Acc], [AH | ArgAcc]) |
|
|
|
end; |
|
|
|
format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) -> |
|
|
|
%% TODO |
|
|
|
% complicated format specifier, gonna have to parse it.. |
|
|
|
case re:run(H, "^(?:-??(\\d+|\\*)\\.|)(?:-??(\\d+|\\*)\\.|)(-??\\d+|\\*|)(t|)([cfegswpWPBX#bx+ni])$", [{capture, all_but_first, list}]) of |
|
|
|
{match, [_F, _P, _Pad, _Mod, C]} when C == "p"; C=="w"; C=="s" -> |
|
|
|
%okay, these are prime candidates for rewriting |
|
|
|
{String, Length} = case print(AH, Max) of |
|
|
|
{_Res, Max} when AT /= [] -> |
|
|
|
% this isn't the last argument, but it consumed all available space, give it half instead |
|
|
|
print(AH, Max div 2); |
|
|
|
{Res, Len} -> |
|
|
|
{Res, Len} |
|
|
|
end, |
|
|
|
{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]); |
|
|
|
{match, [_F, _P, _Pad, _Mod, C]} when C == "P"; C=="W" -> |
|
|
|
[AH2 | AT2] = AT, |
|
|
|
format(T, AT2, Max, [[$~|H]|Acc], [AH2, AH |ArgAcc]); |
|
|
|
{match, _} -> |
|
|
|
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). |
|
|
|
|
|
|
|
%% @doc Returns an flattened list containing the ASCII representation of the given |
|
|
|
%% term. |
|
|
@ -79,7 +159,7 @@ print(<<>>, _Max) -> |
|
|
|
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), |
|
|
@ -146,7 +226,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 +244,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}. |
|
|
|
|
|
|
|
|
|
|
|
%%-------------------- |
|
|
|