|
|
@ -26,8 +26,14 @@ |
|
|
|
|
|
|
|
-module(lager_stdlib). |
|
|
|
|
|
|
|
-export([string_p/1, maybe_utc/1]). |
|
|
|
-export([string_p/1]). |
|
|
|
-export([write_time/2, maybe_utc/1]). |
|
|
|
-export([is_my_error_report/1, is_my_info_report/1]). |
|
|
|
-export([sup_get/2]). |
|
|
|
-export([proc_lib_format/2]). |
|
|
|
|
|
|
|
|
|
|
|
%% from error_logger_file_h |
|
|
|
string_p([]) -> |
|
|
|
false; |
|
|
|
string_p(Term) -> |
|
|
@ -60,6 +66,16 @@ string_p1(_) -> false. |
|
|
|
-type t_time() :: {hour(),minute(),second()}. |
|
|
|
-type t_datetime1970() :: {{year1970(),month(),day()},t_time()}. |
|
|
|
|
|
|
|
%% From OTP stdlib's error_logger_tty_h.erl ... These functions aren't |
|
|
|
%% exported. |
|
|
|
-spec write_time({utc, t_datetime1970()} | t_datetime1970(), string()) -> string(). |
|
|
|
write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) -> |
|
|
|
io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n", |
|
|
|
[Type,D,month(Mo),Y,t(H),t(Mi),t(S)]); |
|
|
|
write_time({{Y,Mo,D},{H,Mi,S}},Type) -> |
|
|
|
io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n", |
|
|
|
[Type,D,month(Mo),Y,t(H),t(Mi),t(S)]). |
|
|
|
|
|
|
|
-spec maybe_utc(t_datetime1970()) -> {utc, t_datetime1970()} | t_datetime1970(). |
|
|
|
maybe_utc(Time) -> |
|
|
|
UTC = case application:get_env(sasl, utc_log) of |
|
|
@ -85,4 +101,407 @@ maybe_utc(Time) -> |
|
|
|
Time |
|
|
|
end. |
|
|
|
|
|
|
|
t(X) when is_integer(X) -> |
|
|
|
t1(integer_to_list(X)); |
|
|
|
t(_) -> |
|
|
|
"". |
|
|
|
t1([X]) -> [$0,X]; |
|
|
|
t1(X) -> X. |
|
|
|
|
|
|
|
month(1) -> "Jan"; |
|
|
|
month(2) -> "Feb"; |
|
|
|
month(3) -> "Mar"; |
|
|
|
month(4) -> "Apr"; |
|
|
|
month(5) -> "May"; |
|
|
|
month(6) -> "Jun"; |
|
|
|
month(7) -> "Jul"; |
|
|
|
month(8) -> "Aug"; |
|
|
|
month(9) -> "Sep"; |
|
|
|
month(10) -> "Oct"; |
|
|
|
month(11) -> "Nov"; |
|
|
|
month(12) -> "Dec". |
|
|
|
|
|
|
|
%% From OTP sasl's sasl_report.erl ... These functions aren't |
|
|
|
%% exported. |
|
|
|
-spec is_my_error_report(atom()) -> boolean(). |
|
|
|
is_my_error_report(supervisor_report) -> true; |
|
|
|
is_my_error_report(crash_report) -> true; |
|
|
|
is_my_error_report(_) -> false. |
|
|
|
|
|
|
|
-spec is_my_info_report(atom()) -> boolean(). |
|
|
|
is_my_info_report(progress) -> true; |
|
|
|
is_my_info_report(_) -> false. |
|
|
|
|
|
|
|
-spec sup_get(term(), [proplists:property()]) -> term(). |
|
|
|
sup_get(Tag, Report) -> |
|
|
|
case lists:keysearch(Tag, 1, Report) of |
|
|
|
{value, {_, Value}} -> |
|
|
|
Value; |
|
|
|
_ -> |
|
|
|
"" |
|
|
|
end. |
|
|
|
|
|
|
|
%% From OTP stdlib's proc_lib.erl ... These functions aren't exported. |
|
|
|
-spec proc_lib_format([term()], pos_integer()) -> string(). |
|
|
|
proc_lib_format([OwnReport,LinkReport], FmtMaxBytes) -> |
|
|
|
OwnFormat = format_report(OwnReport, FmtMaxBytes), |
|
|
|
LinkFormat = format_report(LinkReport, FmtMaxBytes), |
|
|
|
%% io_lib:format here is OK because we're limiting max length elsewhere. |
|
|
|
Str = io_lib:format(" crasher:~n~s neighbours:~n~s",[OwnFormat,LinkFormat]), |
|
|
|
lists:flatten(Str). |
|
|
|
|
|
|
|
format_report(Rep, FmtMaxBytes) when is_list(Rep) -> |
|
|
|
format_rep(Rep, FmtMaxBytes); |
|
|
|
format_report(Rep, FmtMaxBytes) -> |
|
|
|
{Str, _} = trunc_io:print(Rep, FmtMaxBytes), |
|
|
|
io_lib:format("~p~n", [Str]). |
|
|
|
|
|
|
|
format_rep([{initial_call,InitialCall}|Rep], FmtMaxBytes) -> |
|
|
|
[format_mfa(InitialCall, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; |
|
|
|
format_rep([{error_info,{Class,Reason,StackTrace}}|Rep], FmtMaxBytes) -> |
|
|
|
[format_exception(Class, Reason, StackTrace, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; |
|
|
|
format_rep([{Tag,Data}|Rep], FmtMaxBytes) -> |
|
|
|
[format_tag(Tag, Data, FmtMaxBytes)|format_rep(Rep, FmtMaxBytes)]; |
|
|
|
format_rep(_, _S) -> |
|
|
|
[]. |
|
|
|
|
|
|
|
format_exception(Class, Reason, StackTrace, FmtMaxBytes) -> |
|
|
|
PF = pp_fun(FmtMaxBytes), |
|
|
|
StackFun = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end, |
|
|
|
%% EI = " exception: ", |
|
|
|
EI = " ", |
|
|
|
[EI, lib_format_exception(1+length(EI), Class, Reason, |
|
|
|
StackTrace, StackFun, PF), "\n"]. |
|
|
|
|
|
|
|
format_mfa({M,F,Args}=StartF, FmtMaxBytes) -> |
|
|
|
try |
|
|
|
A = length(Args), |
|
|
|
[" initial call: ",atom_to_list(M),$:,atom_to_list(F),$/, |
|
|
|
integer_to_list(A),"\n"] |
|
|
|
catch |
|
|
|
error:_ -> |
|
|
|
format_tag(initial_call, StartF, FmtMaxBytes) |
|
|
|
end. |
|
|
|
|
|
|
|
pp_fun(FmtMaxBytes) -> |
|
|
|
fun(Term, _I) -> |
|
|
|
{Str, _} = trunc_io:print(Term, FmtMaxBytes), |
|
|
|
io_lib:format("~s", [Str]) |
|
|
|
end. |
|
|
|
|
|
|
|
format_tag(Tag, Data, FmtMaxBytes) -> |
|
|
|
{Str, _} = trunc_io:print(Data, FmtMaxBytes), |
|
|
|
io_lib:format(" ~p: ~s~n", [Tag, Str]). |
|
|
|
|
|
|
|
%% From OTP stdlib's lib.erl ... These functions aren't exported. |
|
|
|
|
|
|
|
lib_format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun) |
|
|
|
when is_integer(I), I >= 1, is_function(StackFun, 3), |
|
|
|
is_function(FormatFun, 2) -> |
|
|
|
Str = n_spaces(I-1), |
|
|
|
{Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace), |
|
|
|
Expl0 = explain_reason(Term, Class, Trace1, FormatFun, Str), |
|
|
|
Expl = io_lib:fwrite(<<"~s~s">>, [exited(Class), Expl0]), |
|
|
|
case format_stacktrace1(Str, Trace, FormatFun, StackFun) of |
|
|
|
[] -> Expl; |
|
|
|
Stack -> [Expl, $\n, Stack] |
|
|
|
end. |
|
|
|
|
|
|
|
analyze_exception(error, Term, Stack) -> |
|
|
|
case {is_stacktrace(Stack), Stack, Term} of |
|
|
|
{true, [{_M,_F,As}=MFA|MFAs], function_clause} when is_list(As) -> |
|
|
|
{Term,[MFA],MFAs}; |
|
|
|
{true, [{shell,F,A}], function_clause} when is_integer(A) -> |
|
|
|
{Term, [{F,A}], []}; |
|
|
|
{true, [{_M,_F,_AorAs}=MFA|MFAs], undef} -> |
|
|
|
{Term,[MFA],MFAs}; |
|
|
|
{true, _, _} -> |
|
|
|
{Term,[],Stack}; |
|
|
|
{false, _, _} -> |
|
|
|
{{Term,Stack},[],[]} |
|
|
|
end; |
|
|
|
analyze_exception(_Class, Term, Stack) -> |
|
|
|
case is_stacktrace(Stack) of |
|
|
|
true -> |
|
|
|
{Term,[],Stack}; |
|
|
|
false -> |
|
|
|
{{Term,Stack},[],[]} |
|
|
|
end. |
|
|
|
|
|
|
|
is_stacktrace([]) -> |
|
|
|
true; |
|
|
|
is_stacktrace([{M,F,A}|Fs]) when is_atom(M), is_atom(F), is_integer(A) -> |
|
|
|
is_stacktrace(Fs); |
|
|
|
is_stacktrace([{M,F,As}|Fs]) when is_atom(M), is_atom(F), length(As) >= 0 -> |
|
|
|
is_stacktrace(Fs); |
|
|
|
is_stacktrace(_) -> |
|
|
|
false. |
|
|
|
|
|
|
|
%% ERTS exit codes (some of them are also returned by erl_eval): |
|
|
|
explain_reason(badarg, error, [], _PF, _Str) -> |
|
|
|
<<"bad argument">>; |
|
|
|
explain_reason({badarg,V}, error=Cl, [], PF, Str) -> % orelse, andalso |
|
|
|
format_value(V, <<"bad argument: ">>, Cl, PF, Str); |
|
|
|
explain_reason(badarith, error, [], _PF, _Str) -> |
|
|
|
<<"bad argument in an arithmetic expression">>; |
|
|
|
explain_reason({badarity,{Fun,As}}, error, [], _PF, _Str) |
|
|
|
when is_function(Fun) -> |
|
|
|
%% Only the arity is displayed, not the arguments As. |
|
|
|
io_lib:fwrite(<<"~s called with ~s">>, |
|
|
|
[format_fun(Fun), argss(length(As))]); |
|
|
|
explain_reason({badfun,Term}, error=Cl, [], PF, Str) -> |
|
|
|
format_value(Term, <<"bad function ">>, Cl, PF, Str); |
|
|
|
explain_reason({badmatch,Term}, error=Cl, [], PF, Str) -> |
|
|
|
format_value(Term, <<"no match of right hand side value ">>, Cl, PF, Str); |
|
|
|
explain_reason({case_clause,V}, error=Cl, [], PF, Str) -> |
|
|
|
%% "there is no case clause with a true guard sequence and a |
|
|
|
%% pattern matching..." |
|
|
|
format_value(V, <<"no case clause matching ">>, Cl, PF, Str); |
|
|
|
explain_reason(function_clause, error, [{F,A}], _PF, _Str) -> |
|
|
|
%% Shell commands |
|
|
|
FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]), |
|
|
|
[<<"no function clause matching call to ">> | FAs]; |
|
|
|
explain_reason(function_clause, error=Cl, [{M,F,As}], PF, Str) -> |
|
|
|
String = <<"no function clause matching ">>, |
|
|
|
format_errstr_call(String, Cl, {M,F}, As, PF, Str); |
|
|
|
explain_reason(if_clause, error, [], _PF, _Str) -> |
|
|
|
<<"no true branch found when evaluating an if expression">>; |
|
|
|
explain_reason(noproc, error, [], _PF, _Str) -> |
|
|
|
<<"no such process or port">>; |
|
|
|
explain_reason(notalive, error, [], _PF, _Str) -> |
|
|
|
<<"the node cannot be part of a distributed system">>; |
|
|
|
explain_reason(system_limit, error, [], _PF, _Str) -> |
|
|
|
<<"a system limit has been reached">>; |
|
|
|
explain_reason(timeout_value, error, [], _PF, _Str) -> |
|
|
|
<<"bad receive timeout value">>; |
|
|
|
explain_reason({try_clause,V}, error=Cl, [], PF, Str) -> |
|
|
|
%% "there is no try clause with a true guard sequence and a |
|
|
|
%% pattern matching..." |
|
|
|
format_value(V, <<"no try clause matching ">>, Cl, PF, Str); |
|
|
|
explain_reason(undef, error, [{M,F,A}], _PF, _Str) -> |
|
|
|
%% Only the arity is displayed, not the arguments, if there are any. |
|
|
|
io_lib:fwrite(<<"undefined function ~s">>, |
|
|
|
[mfa_to_string(M, F, n_args(A))]); |
|
|
|
explain_reason({shell_undef,F,A}, error, [], _PF, _Str) -> |
|
|
|
%% Give nicer reports for undefined shell functions |
|
|
|
%% (but not when the user actively calls shell_default:F(...)). |
|
|
|
io_lib:fwrite(<<"undefined shell command ~s/~w">>, [F, n_args(A)]); |
|
|
|
%% Exit codes returned by erl_eval only: |
|
|
|
explain_reason({argument_limit,_Fun}, error, [], _PF, _Str) -> |
|
|
|
io_lib:fwrite(<<"limit of number of arguments to interpreted function" |
|
|
|
" exceeded">>, []); |
|
|
|
explain_reason({bad_filter,V}, error=Cl, [], PF, Str) -> |
|
|
|
format_value(V, <<"bad filter ">>, Cl, PF, Str); |
|
|
|
explain_reason({bad_generator,V}, error=Cl, [], PF, Str) -> |
|
|
|
format_value(V, <<"bad generator ">>, Cl, PF, Str); |
|
|
|
explain_reason({unbound,V}, error, [], _PF, _Str) -> |
|
|
|
io_lib:fwrite(<<"variable ~w is unbound">>, [V]); |
|
|
|
%% Exit codes local to the shell module (restricted shell): |
|
|
|
explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, Str) -> |
|
|
|
String = <<"restricted shell module returned bad value ">>, |
|
|
|
format_value(V, String, Cl, PF, Str); |
|
|
|
explain_reason({restricted_shell_disallowed,{ForMF,As}}, |
|
|
|
exit=Cl, [], PF, Str) -> |
|
|
|
%% ForMF can be a fun, but not a shell fun. |
|
|
|
String = <<"restricted shell does not allow ">>, |
|
|
|
format_errstr_call(String, Cl, ForMF, As, PF, Str); |
|
|
|
explain_reason(restricted_shell_started, exit, [], _PF, _Str) -> |
|
|
|
<<"restricted shell starts now">>; |
|
|
|
explain_reason(restricted_shell_stopped, exit, [], _PF, _Str) -> |
|
|
|
<<"restricted shell stopped">>; |
|
|
|
%% Other exit code: |
|
|
|
explain_reason(Reason, Class, [], PF, Str) -> |
|
|
|
PF(Reason, (iolist_size(Str)+1) + exited_size(Class)). |
|
|
|
|
|
|
|
n_spaces(N) -> |
|
|
|
lists:duplicate(N, $\s). |
|
|
|
|
|
|
|
exited_size(Class) -> |
|
|
|
iolist_size(exited(Class)). |
|
|
|
|
|
|
|
exited(error) -> |
|
|
|
<<"exception error: ">>; |
|
|
|
exited(exit) -> |
|
|
|
<<"exception exit: ">>; |
|
|
|
exited(throw) -> |
|
|
|
<<"exception throw: ">>. |
|
|
|
|
|
|
|
format_stacktrace1(S0, Stack0, PF, SF) -> |
|
|
|
Stack1 = lists:dropwhile(fun({M,F,A}) -> SF(M, F, A) |
|
|
|
end, lists:reverse(Stack0)), |
|
|
|
S = [" " | S0], |
|
|
|
Stack = lists:reverse(Stack1), |
|
|
|
format_stacktrace2(S, Stack, 1, PF). |
|
|
|
|
|
|
|
format_stacktrace2(S, [{M,F,A}|Fs], N, PF) when is_integer(A) -> |
|
|
|
[io_lib:fwrite(<<"~s~s ~s">>, |
|
|
|
[sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A)]) |
|
|
|
| format_stacktrace2(S, Fs, N + 1, PF)]; |
|
|
|
format_stacktrace2(S, [{M,F,As}|Fs], N, PF) when is_list(As) -> |
|
|
|
A = length(As), |
|
|
|
CalledAs = [S,<<" called as ">>], |
|
|
|
C = format_call("", CalledAs, {M,F}, As, PF), |
|
|
|
[io_lib:fwrite(<<"~s~s ~s\n~s~s">>, |
|
|
|
[sep(N, S), origin(N, M, F, A), mfa_to_string(M, F, A), |
|
|
|
CalledAs, C]) |
|
|
|
| format_stacktrace2(S, Fs, N + 1, PF)]; |
|
|
|
format_stacktrace2(_S, [], _N, _PF) -> |
|
|
|
"". |
|
|
|
|
|
|
|
argss(0) -> |
|
|
|
<<"no arguments">>; |
|
|
|
argss(1) -> |
|
|
|
<<"one argument">>; |
|
|
|
argss(2) -> |
|
|
|
<<"two arguments">>; |
|
|
|
argss(I) -> |
|
|
|
io_lib:fwrite(<<"~w arguments">>, [I]). |
|
|
|
|
|
|
|
format_value(V, ErrStr, Class, PF, Str) -> |
|
|
|
Pre1Sz = exited_size(Class), |
|
|
|
Str1 = PF(V, Pre1Sz + iolist_size([Str, ErrStr])+1), |
|
|
|
[ErrStr | case count_nl(Str1) of |
|
|
|
N1 when N1 > 1 -> |
|
|
|
Str2 = PF(V, iolist_size(Str) + 1 + Pre1Sz), |
|
|
|
case count_nl(Str2) < N1 of |
|
|
|
true -> |
|
|
|
[$\n, Str, n_spaces(Pre1Sz) | Str2]; |
|
|
|
false -> |
|
|
|
Str1 |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
Str1 |
|
|
|
end]. |
|
|
|
|
|
|
|
format_fun(Fun) when is_function(Fun) -> |
|
|
|
{module, M} = erlang:fun_info(Fun, module), |
|
|
|
{name, F} = erlang:fun_info(Fun, name), |
|
|
|
{arity, A} = erlang:fun_info(Fun, arity), |
|
|
|
case erlang:fun_info(Fun, type) of |
|
|
|
{type, local} when F =:= "" -> |
|
|
|
io_lib:fwrite(<<"~w">>, [Fun]); |
|
|
|
{type, local} when M =:= erl_eval -> |
|
|
|
io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]); |
|
|
|
{type, local} -> |
|
|
|
mfa_to_string(M, F, A); |
|
|
|
{type, external} -> |
|
|
|
mfa_to_string(M, F, A) |
|
|
|
end. |
|
|
|
|
|
|
|
format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0) -> |
|
|
|
Pre1 = [Pre0 | n_spaces(exited_size(Class))], |
|
|
|
format_call(ErrStr, Pre1, ForMForFun, As, PF). |
|
|
|
|
|
|
|
format_call(ErrStr, Pre1, ForMForFun, As, PF) -> |
|
|
|
Arity = length(As), |
|
|
|
[ErrStr | |
|
|
|
case is_op(ForMForFun, Arity) of |
|
|
|
{yes,Op} -> |
|
|
|
format_op(ErrStr, Pre1, Op, As, PF); |
|
|
|
no -> |
|
|
|
MFs = mf_to_string(ForMForFun, Arity), |
|
|
|
I1 = iolist_size([Pre1,ErrStr|MFs]), |
|
|
|
S1 = pp_arguments(PF, As, I1), |
|
|
|
S2 = pp_arguments(PF, As, iolist_size([Pre1|MFs])), |
|
|
|
Long = count_nl(pp_arguments(PF, [a2345,b2345], I1)) > 0, |
|
|
|
case Long or (count_nl(S2) < count_nl(S1)) of |
|
|
|
true -> |
|
|
|
[$\n, Pre1, MFs, S2]; |
|
|
|
false -> |
|
|
|
[MFs, S1] |
|
|
|
end |
|
|
|
end]. |
|
|
|
|
|
|
|
mfa_to_string(M, F, A) -> |
|
|
|
io_lib:fwrite(<<"~s/~w">>, [mf_to_string({M, F}, A), A]). |
|
|
|
|
|
|
|
mf_to_string({M, F}, A) -> |
|
|
|
case erl_internal:bif(M, F, A) of |
|
|
|
true -> |
|
|
|
io_lib:fwrite(<<"~w">>, [F]); |
|
|
|
false -> |
|
|
|
case is_op({M, F}, A) of |
|
|
|
{yes, '/'} -> |
|
|
|
io_lib:fwrite(<<"~w">>, [F]); |
|
|
|
{yes, F} -> |
|
|
|
atom_to_list(F); |
|
|
|
no -> |
|
|
|
io_lib:fwrite(<<"~w:~w">>, [M, F]) |
|
|
|
end |
|
|
|
end; |
|
|
|
mf_to_string(Fun, _A) when is_function(Fun) -> |
|
|
|
format_fun(Fun); |
|
|
|
mf_to_string(F, _A) -> |
|
|
|
io_lib:fwrite(<<"~w">>, [F]). |
|
|
|
|
|
|
|
n_args(A) when is_integer(A) -> |
|
|
|
A; |
|
|
|
n_args(As) when is_list(As) -> |
|
|
|
length(As). |
|
|
|
|
|
|
|
origin(1, M, F, A) -> |
|
|
|
case is_op({M, F}, n_args(A)) of |
|
|
|
{yes, F} -> <<"in operator ">>; |
|
|
|
no -> <<"in function ">> |
|
|
|
end; |
|
|
|
origin(_N, _M, _F, _A) -> |
|
|
|
<<"in call from">>. |
|
|
|
|
|
|
|
sep(1, S) -> S; |
|
|
|
sep(_, S) -> [$\n | S]. |
|
|
|
|
|
|
|
count_nl([E | Es]) -> |
|
|
|
count_nl(E) + count_nl(Es); |
|
|
|
count_nl($\n) -> |
|
|
|
1; |
|
|
|
count_nl(Bin) when is_binary(Bin) -> |
|
|
|
count_nl(binary_to_list(Bin)); |
|
|
|
count_nl(_) -> |
|
|
|
0. |
|
|
|
|
|
|
|
is_op(ForMForFun, A) -> |
|
|
|
try |
|
|
|
{erlang,F} = ForMForFun, |
|
|
|
_ = erl_internal:op_type(F, A), |
|
|
|
{yes,F} |
|
|
|
catch error:_ -> no |
|
|
|
end. |
|
|
|
|
|
|
|
format_op(ErrStr, Pre, Op, [A1, A2], PF) -> |
|
|
|
I1 = iolist_size([ErrStr,Pre]), |
|
|
|
S1 = PF(A1, I1+1), |
|
|
|
S2 = PF(A2, I1+1), |
|
|
|
OpS = atom_to_list(Op), |
|
|
|
Pre1 = [$\n | n_spaces(I1)], |
|
|
|
case count_nl(S1) > 0 of |
|
|
|
true -> |
|
|
|
[S1,Pre1,OpS,Pre1|S2]; |
|
|
|
false -> |
|
|
|
OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]), |
|
|
|
S2_2 = PF(A2, iolist_size([ErrStr,Pre,S1|OpS2])+1), |
|
|
|
case count_nl(S2) < count_nl(S2_2) of |
|
|
|
true -> |
|
|
|
[S1,Pre1,OpS,Pre1|S2]; |
|
|
|
false -> |
|
|
|
[S1,OpS2|S2_2] |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
pp_arguments(PF, As, I) -> |
|
|
|
case {As, io_lib:printable_list(As)} of |
|
|
|
{[Int | T], true} -> |
|
|
|
L = integer_to_list(Int), |
|
|
|
Ll = length(L), |
|
|
|
A = list_to_atom(lists:duplicate(Ll, $a)), |
|
|
|
S0 = binary_to_list(iolist_to_binary(PF([A | T], I+1))), |
|
|
|
brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)]); |
|
|
|
_ -> |
|
|
|
brackets_to_parens(PF(As, I+1)) |
|
|
|
end. |
|
|
|
|
|
|
|
brackets_to_parens(S) -> |
|
|
|
B = iolist_to_binary(S), |
|
|
|
Sz = byte_size(B) - 2, |
|
|
|
<<$[,R:Sz/binary,$]>> = B, |
|
|
|
[$(,R,$)]. |
|
|
|
|