From 21adbb7706932ca9b06d3ba2b5f243731662ac54 Mon Sep 17 00:00:00 2001 From: Sergio Siccha Date: Fri, 6 Jul 2018 15:52:16 +0200 Subject: [PATCH] Print traceback when encountering errors 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. For versions older than 4.10 a backwards compatible version of gap/JupyterError.gi is loaded. Fixes #38. --- bin/jupyter-kernel-gap | 2 +- gap/JupyterError.gi | 198 +++++++++++++++++++++++++++++++--------- gap/JupyterError_4.9.gi | 162 ++++++++++++++++++++++++++++++++ init.g | 8 +- 4 files changed, 326 insertions(+), 44 deletions(-) create mode 100644 gap/JupyterError_4.9.gi diff --git a/bin/jupyter-kernel-gap b/bin/jupyter-kernel-gap index 262adc6..50f3d43 100755 --- a/bin/jupyter-kernel-gap +++ b/bin/jupyter-kernel-gap @@ -7,7 +7,7 @@ elif [ -z "$GAP" ] ; then fi echo "GAP Jupyter Kernel Starting using $GAP" -exec $GAP -q -T < 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, "( )\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, + 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(); JUMP_TO_CATCH(1); - fi; - + fi; + if IsBound(arg[1].justQuit) then justQuit := arg[1].justQuit; if not justQuit in [false, true] then @@ -30,7 +78,7 @@ BIND_GLOBAL("ErrorInner", else justQuit := false; fi; - + if IsBound(arg[1].mayReturnVoid) then mayReturnVoid := arg[1].mayReturnVoid; if not mayReturnVoid in [false, true] then @@ -41,7 +89,7 @@ BIND_GLOBAL("ErrorInner", else mayReturnVoid := false; fi; - + if IsBound(arg[1].mayReturnObj) then mayReturnObj := arg[1].mayReturnObj; if not mayReturnObj in [false, true] then @@ -52,7 +100,7 @@ BIND_GLOBAL("ErrorInner", else mayReturnObj := false; fi; - + if IsBound(arg[1].printThisStatement) then printThisStatement := arg[1].printThisStatement; if not printThisStatement in [false, true] then @@ -63,7 +111,7 @@ BIND_GLOBAL("ErrorInner", else printThisStatement := true; fi; - + if IsBound(arg[1].lateMessage) then lateMessage := arg[1].lateMessage; if not lateMessage in [false, true] and not IsString(lateMessage) then @@ -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(); @@ -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; @@ -147,10 +261,10 @@ BIND_GLOBAL("ErrorInner", if IsBound(OnQuit) and IsFunction(OnQuit) then OnQuit(); fi; - if ErrorLevel = 0 then LEAVE_ALL_NAMESPACES(); 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; diff --git a/gap/JupyterError_4.9.gi b/gap/JupyterError_4.9.gi new file mode 100644 index 0000000..dc39a3e --- /dev/null +++ b/gap/JupyterError_4.9.gi @@ -0,0 +1,162 @@ +# +# This is a humongously bad hack to display error messages +# + +GAP_ERROR_STREAM := "*errout*"; + +MakeReadWriteGlobal("ErrorInner"); +Unbind(ErrorInner); + +BIND_GLOBAL("ErrorInner", + function( arg ) + local context, mayReturnVoid, mayReturnObj, lateMessage, earlyMessage, + x, prompt, res, errorLVars, justQuit, printThisStatement, + location; + + 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(); + JUMP_TO_CATCH(1); + fi; + + if IsBound(arg[1].justQuit) then + justQuit := arg[1].justQuit; + if not justQuit in [false, true] then + PrintTo(GAP_ERROR_STREAM, "ErrorInner: option justQuit must be true or false\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + else + justQuit := false; + fi; + + if IsBound(arg[1].mayReturnVoid) then + mayReturnVoid := arg[1].mayReturnVoid; + if not mayReturnVoid in [false, true] then + PrintTo(GAP_ERROR_STREAM, "ErrorInner: option mayReturnVoid must be true or false\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + else + mayReturnVoid := false; + fi; + + if IsBound(arg[1].mayReturnObj) then + mayReturnObj := arg[1].mayReturnObj; + if not mayReturnObj in [false, true] then + PrintTo(GAP_ERROR_STREAM, "ErrorInner: option mayReturnObj must be true or false\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + else + mayReturnObj := false; + fi; + + if IsBound(arg[1].printThisStatement) then + printThisStatement := arg[1].printThisStatement; + if not printThisStatement in [false, true] then + PrintTo(GAP_ERROR_STREAM, "ErrorInner: option printThisStatement must be true or false\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + else + printThisStatement := true; + fi; + + if IsBound(arg[1].lateMessage) then + lateMessage := arg[1].lateMessage; + if not lateMessage in [false, true] and not IsString(lateMessage) then + PrintTo(GAP_ERROR_STREAM, "ErrorInner: option lateMessage must be a string or false\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + else + lateMessage := ""; + fi; + + earlyMessage := arg[2]; + if Length(arg) <> 2 then + PrintTo(GAP_ERROR_STREAM,"ErrorInner: new format takes exactly two arguments\n"); + LEAVE_ALL_NAMESPACES(); + JUMP_TO_CATCH(1); + fi; + + ErrorLevel := ErrorLevel+1; + ERROR_COUNT := ERROR_COUNT+1; + errorLVars := ErrorLVars; + ErrorLVars := context; + 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"); + 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; + + if SHOULD_QUIT_ON_BREAK() then + FORCE_QUIT_GAP(1); + fi; + + if IsBound(OnBreak) and IsFunction(OnBreak) then + OnBreak(); + fi; + if IsString(lateMessage) then + PrintTo(GAP_ERROR_STREAM,lateMessage,"\n"); + elif lateMessage then + if IsBound(OnBreakMessage) and IsFunction(OnBreakMessage) then + OnBreakMessage(); + fi; + fi; + if ErrorLevel > 1 then + prompt := Concatenation("brk_",String(ErrorLevel),"> "); + else + prompt := "brk> "; + fi; + if not justQuit then + res := SHELL(context,mayReturnVoid,mayReturnObj,3,false,prompt,false,"*errin*",GAP_ERROR_STREAM,false); + else + res := fail; + fi; + ErrorLevel := ErrorLevel-1; + ErrorLVars := errorLVars; + if res = fail then + if IsBound(OnQuit) and IsFunction(OnQuit) then + OnQuit(); + 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); + fi; + JUMP_TO_CATCH(3); + fi; + if Length(res) > 0 then + return res[1]; + else + return; + fi; +end); diff --git a/init.g b/init.g index ba8358b..a8453a2 100644 --- a/init.g +++ b/init.g @@ -5,7 +5,13 @@ # # HACK -ReadPackage( "JupyterKernel", "gap/JupyterError.gi"); +# Until GAP 4.9 PRINT_CURRENT_STATEMENT does not support +# redirecting its output. +if CompareVersionNumbers(GAPInfo.Version, "4.10") then + ReadPackage( "JupyterKernel", "gap/JupyterError.gi"); +else + ReadPackage( "JupyterKernel", "gap/JupyterError_4.9.gi"); +fi; ReadPackage( "JupyterKernel", "gap/JupyterStream.gd");