Current section
Files
Jump to
Current section
Files
src/lfe_error.erl
%% Copyright (c) 2024 Robert Virding
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% File : lfe_error.erl
%% Author : Robert Virding
%% Purpose : Lisp Flavoured Erlang error report functions.
-module(lfe_error).
%% Supported and documented exported functions in this module.
-export([format_exception/6,format_stacktrace/3]).
%% format_exception(Class, Error, Stacktrace, SkipFun, FormatFun, Indentation)
%% -> DeepCharList.
%% Format an exception. Class, Error and Stacktrace describe the
%% exception; SkipFun is used to trim the end of stack; FormatFun is
%% used to format terms; and Indentation is the current column.
format_exception(Cl, Error0, St0, Skip, Format, I) ->
Cs = case Cl of %Class type as string
throw -> "throw";
exit -> "exit";
error -> "error"
end,
{Error1,St1} = case is_stacktrace(St0) of
true -> {Error0,St0};
false -> {{Error0,St0},[]}
end,
P = "exception " ++ Cs ++ ": ", %Class description string
[P,format_reason(Error1, length(P)+I-1),"\n",
format_stacktrace(St1, Skip, Format)].
%% format_reason(Error, Indentation) -> DeepCharList.
%% Format an error giving a little better information. Explicitly
%% handle errors known from ERTS here, anything is assumed to come
%% from lfe_eval.
%% The ERTS exit codes.
format_reason(badarg, _I) ->
<<"bad argument">>;
format_reason({badarg,V}, I) ->
format_value(V, <<"bad argument ">>, I);
format_reason(badarith, _I) ->
<<"error in arithmetic expression">>;
format_reason({badarity,{Fun,As}}, _I)
when is_function(Fun) ->
%% Only the arity is displayed, not the arguments As.
lfe_io:format1(<<"~s called with ~s">>,
[format_fun(Fun),argss(length(As))]);
format_reason({badfun,Term}, I) ->
format_value(Term, <<"bad function ">>, I);
format_reason({badmatch,V}, I) ->
format_value(V, <<"no match of value ">>, I);
format_reason({case_clause,V}, I) ->
%% "there is no case clause with a true guard sequence and a
%% pattern matching..."
format_value(V, <<"no case clause matching ">>, I);
format_reason(function_clause, _I) ->
<<"no function clause matching">>;
format_reason(if_clause, _I) ->
<<"no if clause matching">>;
format_reason(noproc, _I) -> <<"no such process or port">>;
format_reason(notalive, _I) ->
<<"the node cannot be part of a distributed system">>;
format_reason(system_limit, _I) ->
<<"a system limit has been reached">>;
format_reason(timeout_value, _I) ->
<<"bad receive timeout value">>;
format_reason({try_clause,V}, I) ->
%% "there is no try clause with a true guard sequence and a
%% pattern matching..."
format_value(V, <<"no try clause matching ">>, I);
format_reason(undef, _I) ->
<<"undefined function">>;
%% We now pass the buck to lfe_eval.
format_reason(Error, _) ->
lfe_eval:format_error(Error).
argss(0) -> <<"no arguments">>;
argss(1) -> <<"one argument">>;
argss(2) -> <<"two arguments">>;
argss(N) -> lfe_io:format1(<<"~w arguments">>, [N]).
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 M =:= lfe_eval ->
lfe_io:format1(<<"interpreted function with arity ~w">>, [A]);
_ -> lfe_io:print1(Fun)
end.
format_value(Val, ErrStr, I) ->
Sz = I + iolist_size(ErrStr),
lfe_io:format1(<<"~s~.*P">>, [ErrStr,Sz,Val,10]).
%% format_stacktrace(Stacktrace, SkipFun, FormatFun) -> DeepCharList.
%% Format a stacktrace. SkipFun is used to trim the end of stack;
%% FormatFun is used to format terms.
format_stacktrace(St0, Skip, Format) ->
Drop = fun ({M,F,A,_}) -> Skip(M, F, A);
({M,F,A}) -> Skip(M, F, A); %Pre R15
(_Other) -> false %To be safe
end,
St1 = lists:reverse(lists:dropwhile(Drop, lists:reverse(St0))),
Print = fun (F) -> format_stackcall(F, Format) end,
lists:map(Print, St1).
format_stackcall({M,F,A}, _) when is_integer(A) -> %Pre R15
lfe_io:format1(" in ~w:~w/~w\n", [M,F,A]);
format_stackcall({M,F,A}, Format) ->
[" in ",Format([M,':',F|A], 5),"\n"];
format_stackcall({M,F,A,Loc},_) when is_integer(A) -> %R15 and later.
lfe_io:format1(" in ~w:~w/~w ~s\n", [M,F,A,location(Loc)]);
format_stackcall({M,F,A,_}, Format) ->
[" in ",Format([M,':',F|A], 5),"\n"].
location(Loc) ->
File = proplists:get_value(file, Loc),
Line = proplists:get_value(line, Loc),
if File =/= undefined, Line =/= undefined ->
lfe_io:format1("(~s, line ~w)", [File,Line]);
true -> ""
end.
is_stacktrace([{M,F,A}|Fs]) %Pre R15
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([{M,F,A,I}|Fs]) %R15 and later
when is_atom(M), is_atom(F), is_integer(A), is_list(I) -> is_stacktrace(Fs);
is_stacktrace([{M,F,As,I}|Fs])
when is_atom(M), is_atom(F), length(As) >= 0, is_list(I) -> is_stacktrace(Fs);
is_stacktrace([]) -> true;
is_stacktrace(_) -> false.