-module(rumStdlib). -compile(inline). -compile({inline_size, 128}). -export([ isErrorReport/1, is_my_info_report/1 ]). -export([sup_get/2]). -export([proc_lib_format/2]). %% From OTP sasl's sasl_report.erl ... These functions aren't %% exported. -spec isErrorReport(atom()) -> boolean(). isErrorReport(supervisor_report) -> true; isErrorReport(crash_report) -> true; isErrorReport(_) -> 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) -> eFmt:formatBin(<<"~p~n">>, [Rep], [{charsLimit, FmtMaxBytes}]). 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) -> eFmt:formatBin(<<"~p">>, [Term], [{charsLimit ,FmtMaxBytes}]) end. format_tag(Tag, Data, FmtMaxBytes) -> eFmt:formatBin(<<"~p: ~p~n">>, [Tag, Data], [{charsLimit ,FmtMaxBytes}]). %% 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, $)].