Skip to content

Commit

Permalink
Print traceback when encountering errors
Browse files Browse the repository at this point in the history
Uses the new command line option --alwaystrace. This lets GAP
also print tracebacks when the break loop is disabled by -T.

This still overwrites the library functions Where, WHERE, and
ErrorInner. This can be avoided once there is a central GAP
error stream where these functions send their output, see
gap-system/gap#1815.

Since redirecting error messages was not available in 4.9
this also sets the required GAP version to >= 4.10.

Fixes #38.
  • Loading branch information
ssiccha authored and markuspf committed Sep 18, 2018
1 parent a6f8e0e commit ac731aa
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 36 deletions.
2 changes: 1 addition & 1 deletion PackageInfo.g
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ PackageDoc := rec(
),

Dependencies := rec(
GAP := ">= 4.9",
GAP := ">= 4.10",
NeededOtherPackages := [ [ "GAPDoc", ">= 1.5" ]
, [ "io", ">= 4.4.6" ]
, [ "json", ">= 1.1.0" ]
Expand Down
2 changes: 1 addition & 1 deletion bin/jupyter-kernel-gap
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ elif [ -z "$GAP" ] ; then
fi

echo "GAP Jupyter Kernel Starting using $GAP"
exec $GAP -q -T <<EOF
exec $GAP -q -T --alwaystrace <<EOF
LoadPackage("JupyterKernel");
JUPYTER_KernelStart_GAP("$1");
QUIT_GAP(0);
Expand Down
182 changes: 148 additions & 34 deletions gap/JupyterError.gi
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,64 @@

GAP_ERROR_STREAM := "*errout*";

MakeReadWriteGlobal("WHERE");
Unbind(WHERE);

BIND_GLOBAL("WHERE", function( context, depth, outercontext)
local bottom, lastcontext, f;
if depth <= 0 then
return;
fi;
bottom := GetBottomLVars();
lastcontext := outercontext;
while depth > 0 and context <> bottom do
PRINT_CURRENT_STATEMENT(GAP_ERROR_STREAM, context);
PrintTo(GAP_ERROR_STREAM, " called from\n");
lastcontext := context;
context := ParentLVars(context);
depth := depth-1;
od;
if depth = 0 then
PrintTo(GAP_ERROR_STREAM, "... ");
else
f := ContentsLVars(lastcontext).func;
PrintTo(GAP_ERROR_STREAM, "<function \"",NAME_FUNC(f)
,"\">( <arguments> )\n called from read-eval loop ");
fi;
end);

MakeReadWriteGlobal("Where");
Unbind(Where);

BIND_GLOBAL("Where", function(arg)
local depth;
if LEN_LIST(arg) = 0 then
depth := 5;
else
depth := arg[1];
fi;

if ErrorLVars = fail or ErrorLVars = GetBottomLVars() then
PrintTo(GAP_ERROR_STREAM, "not in any function ");
else
WHERE(ParentLVars(ErrorLVars),depth, ErrorLVars);
fi;
PrintTo(GAP_ERROR_STREAM, "at ",INPUT_FILENAME(),":",INPUT_LINENUMBER(),"\n");
end);

OnBreak := Where;

MakeReadWriteGlobal("ErrorInner");
Unbind(ErrorInner);

BIND_GLOBAL("ErrorInner",
function( arg )
local context, mayReturnVoid, mayReturnObj, lateMessage, earlyMessage,
x, prompt, res, errorLVars, justQuit, printThisStatement,
location;
printEarlyMessage, printEarlyTraceback, lastErrorStream,
shellOut, shellIn;

context := arg[1].context;
context := arg[1].context;
if not IsLVarsBag(context) then
PrintTo(GAP_ERROR_STREAM, "ErrorInner: option context must be a local variables bag\n");
LEAVE_ALL_NAMESPACES();
Expand Down Expand Up @@ -74,58 +122,116 @@ BIND_GLOBAL("ErrorInner",
else
lateMessage := "";
fi;
earlyMessage := arg[2];

earlyMessage := arg[2];
if Length(arg) <> 2 then
PrintTo(GAP_ERROR_STREAM,"ErrorInner: new format takes exactly two arguments\n");
PrintTo(GAP_ERROR_STREAM, "ErrorInner: new format takes exactly two arguments\n");
LEAVE_ALL_NAMESPACES();
JUMP_TO_CATCH(1);
fi;


# Local functions that print the user feedback.
printEarlyMessage := function(stream, earlyMessage)
PrintTo(stream, "Error, ");
# earlyMessage usually contains information about what went wrong.
for x in earlyMessage do
PrintTo(stream, x);
od;
end;

printEarlyTraceback := function(stream, context, printThisStatement)
local location;
if printThisStatement then
if context <> GetBottomLVars() then
PrintTo(stream, " in\n ");
PRINT_CURRENT_STATEMENT(stream, context);
PrintTo(stream, " called from ");
fi;
else
location := CURRENT_STATEMENT_LOCATION(context);
if location <> fail then
PrintTo(stream, " at ", location[1], ":", location[2]);
fi;
PrintTo(stream, " called from");
fi;
PrintTo(GAP_ERROR_STREAM, "\n");
end;

ErrorLevel := ErrorLevel+1;
ERROR_COUNT := ERROR_COUNT+1;
errorLVars := ErrorLVars;
ErrorLVars := context;
# Do we want to skip the break loop?
# BreakOnError is initialized by the `-T` command line flag in init.g
if QUITTING or not BreakOnError then
PrintTo(GAP_ERROR_STREAM,"Error, ");
for x in earlyMessage do
PrintTo(GAP_ERROR_STREAM,x);
od;
PrintTo(GAP_ERROR_STREAM,"\n");
# If we skip the break loop, the standard behaviour is to print only
# the earlyMessage. If SilentNonInteractiveErrors is true we do not
# print any messages. If AlwaysPrintTracebackOnError is true we also
# call OnBreak(), which by default prints the traceback.
# SilentNonInteractiveErrors superseeds AlwaysPrintTracebackOnError.
# It is used by HPC-GAP to e.g. suppress error messages in worker
# threads.
if not SilentNonInteractiveErrors then
printEarlyMessage(GAP_ERROR_STREAM, earlyMessage);
if AlwaysPrintTracebackOnError then
printEarlyTraceback(GAP_ERROR_STREAM, context, printThisStatement);
if IsBound(OnBreak) and IsFunction(OnBreak) then
OnBreak();
fi;
else
PrintTo(GAP_ERROR_STREAM, "\n");
fi;
fi;
if IsHPCGAP then
# In HPC-GAP we want to access error messages encountered in
# tasks via TaskError. To this end we store the error message
# in the thread local variable LastErrorMessage.
LastErrorMessage := "";
lastErrorStream := OutputTextString(LastErrorMessage, true);
printEarlyMessage(lastErrorStream, earlyMessage);
if AlwaysPrintTracebackOnError then
printEarlyTraceback(lastErrorStream, context, printThisStatement);
# FIXME: Also make HPCGAP work with OnBreak().
# If AlwaysPrintTracebackOnError is true, the output of
# OnBreak() should also be put into LastErrorMessage.
# To do this there needs to be a way to put its output
# into lastErrorStream.
# OnBreak() is documented to not take any arguments.
# One could work around that if there were e.g. a GAP error
# stream which all error functions print to.
fi;
CloseStream(lastErrorStream);
MakeImmutable(LastErrorMessage);
fi;
ErrorLevel := ErrorLevel-1;
ErrorLVars := errorLVars;
if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); fi;
JUMP_TO_CATCH(0);
fi;
PrintTo(GAP_ERROR_STREAM,"Error, ");
for x in earlyMessage do
PrintTo(GAP_ERROR_STREAM,x);
od;
if printThisStatement then
if context <> GetBottomLVars() then
PrintTo(GAP_ERROR_STREAM," in\n \c");
PRINT_CURRENT_STATEMENT(context);
Print("\c");
PrintTo(GAP_ERROR_STREAM," called from \n");
else
PrintTo(GAP_ERROR_STREAM,"\c\n");
fi;
else
location := CURRENT_STATEMENT_LOCATION(context);
if location <> fail then PrintTo(GAP_ERROR_STREAM, " at ", location[1], ":", location[2]);
fi;
PrintTo(GAP_ERROR_STREAM," called from\c\n");
fi;

printEarlyMessage(GAP_ERROR_STREAM, earlyMessage);
printEarlyTraceback(GAP_ERROR_STREAM, context, printThisStatement);

if SHOULD_QUIT_ON_BREAK() then
# Again, the default is to not print the rest of the traceback.
# If AlwaysPrintTracebackOnError is true we do so anyways.
if
AlwaysPrintTracebackOnError
and IsBound(OnBreak) and IsFunction(OnBreak)
then
OnBreak();
fi;
FORCE_QUIT_GAP(1);
fi;

# OnBreak() is set to Where() by default, which prints the traceback.
if IsBound(OnBreak) and IsFunction(OnBreak) then
OnBreak();
fi;

# Now print lateMessage and OnBreakMessage a la "press return; to .."
if IsString(lateMessage) then
PrintTo(GAP_ERROR_STREAM,lateMessage,"\n");
PrintTo(GAP_ERROR_STREAM, lateMessage,"\n");
elif lateMessage then
if IsBound(OnBreakMessage) and IsFunction(OnBreakMessage) then
OnBreakMessage();
Expand All @@ -136,8 +242,16 @@ BIND_GLOBAL("ErrorInner",
else
prompt := "brk> ";
fi;
shellOut := GAP_ERROR_STREAM;
shellIn := "*errin*";
if IsHPCGAP then
if HaveMultiThreadedUI then
shellOut := "*defout*";
shellIn := "*defin*";
fi;
fi;
if not justQuit then
res := SHELL(context,mayReturnVoid,mayReturnObj,3,false,prompt,false,"*errin*",GAP_ERROR_STREAM,false);
res := SHELL(context,mayReturnVoid,mayReturnObj,3,false,prompt,false,shellIn,shellOut,false);
else
res := fail;
fi;
Expand All @@ -149,8 +263,8 @@ BIND_GLOBAL("ErrorInner",
fi;
if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); fi;
if not justQuit then
# dont try and do anything else after this before the longjump
SetUserHasQuit(1);
# dont try and do anything else after this before the longjump
SetUserHasQuit(1);
fi;
JUMP_TO_CATCH(3);
fi;
Expand Down

0 comments on commit ac731aa

Please sign in to comment.