|
|
@ -35,7 +35,7 @@ |
|
|
|
|
|
|
|
format(String, Args, Max) -> |
|
|
|
Parts = re:split(String, |
|
|
|
"(~(?:-??\\d+\\.|\\*\\.|)(?:-??\d+\\.|\\*\\.|)(?:-??\\d+|\\*|)(?:t|)(?:[cfegswpWPBX#bx+ni]))", |
|
|
|
"(~(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+\\.|\\*\\.|\\.|)(?:-??\\d+|\\*|)(?:t|)(?:[cfegswpWPBX#bx+ni~]))", |
|
|
|
[{return, list}, trim]), |
|
|
|
Maxlen = Max - length(String), |
|
|
|
format(Parts, Args, Maxlen, [], []). |
|
|
@ -46,25 +46,37 @@ format([], _Args, Max, Acc, ArgAcc) -> |
|
|
|
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 == "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], [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} when AT /= [] -> |
|
|
|
{_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]); |
|
|
@ -80,16 +92,18 @@ format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) when length(H) == 1 -> |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
% whatever, just pass them on through |
|
|
|
format(T, AT, Max, [[$~ | H], Acc], [AH | ArgAcc]) |
|
|
|
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" -> |
|
|
|
%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} when AT /= [] -> |
|
|
|
{_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]); |
|
|
@ -103,18 +117,31 @@ format([[$~|H]| T], [AH | AT], Max, Acc, ArgAcc) -> |
|
|
|
end, |
|
|
|
format(T, AT, Max + length(H) + 1 - RealLen, ["~s" | Acc], [Value | ArgAcc]) |
|
|
|
end; |
|
|
|
{match, [_F, _P, _Pad, _Mod, C]} when C == "P"; C=="W" -> |
|
|
|
% 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. |
|
|
|
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]); |
|
|
|
{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]) |
|
|
|
_ -> |
|
|
|
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). |
|
|
|