Debugging in Yacas


Introduction

When writing a code segment, it is generally a good idea to separate the problem into many small functions. Not only can you then reuse these functions on other problems, but it makes debugging easier too.

For debugging a faulty function, in addition to the usual trial-and-error method and the "print everything" method, Yacas offers some trace facilities. You can try to trace applications of rules during evaluation of the function (TraceRule(), TraceExp()) or see the stack after an error has occurred (TraceStack()).

There is also an interactive debugger, which shall be introduced in this chapter.

Finally, you may want to run a debugging version of Yacas. This version of the executable maintains more information about the operations it performs, and can report on this.

This chapter will start with the interactive debugger, as it is the easiest and most useful feature to use, and then proceed to explain the trace and profiling facilities. Finally, the internal workings of the debugger will be explained. It is highly customizable (in fact, most of the debugging code is written in Yacas itself), so for bugs that are really difficult to track one can write custom code to track it.


The interactive command line debugger

Yacas comes with a full interactive command line based debugger. The evaluator has hooks so that a programmer can customize evaluation. A very advanced use is to roll a custom debugger for a specific bug. This section will not go into that, but show the current facilities offered by the interactive command line debugger.


An example

Let us start with a simple example. Suppose we ran into the problem where a function called returns an error:

In> Contains(a,{a,b,c})
In function "Head" : 
bad argument number 1 (counting from 1)
The offending argument list evaluated to a
CommandLine(1) : Argument is not a list

and suppose we want to examine what went wrong. We can invoke the debugger by calling Debug, with the expression to debug as argument:

In> Debug(Contains(a,{a,b,c}))
>>> Contains(a,{a,b,c}) 
Debug> 

The screen now shows the expression we passed in, and a Debug> prompt. The debugger has essentially been started and put in interactive mode. This would for instance be a good moment to add breakpoints. For now, we will just start by running the code, to see where it fails:

Debug> DebugRun()
DebugOut> False 
CommandLine(1) : Argument is not a list
>>> Head(list) 
Debug> 

The interpreter runs into a problem and falls back to the interactive mode of the debugger. We can now enter expressions on the command line, and they will be evaluated in the context the interpreter was stopped in. For instance, it appears the interpreter tried to evaluate Head(list), but list does not seem to be a list. So, to check this, we examine the contents of the variable list:

Debug> list;
DebugOut> a 

Indeed list is bound to a, which is not a list. Examining all the local variables on the stack, we find:

Debug> DebugLocals()

*************** Current locals on the stack ****************
      list  : a 
      element  : {a,b,c} 
      result  : False 

DebugOut> True 

So it seems we swapped the two arguments, as the values of list and element should be swapped. We first drop out of the debugger, and then try the call with the arguments swapped:

Debug> DebugStop();
DebugOut> True 
CommandLine(1) : Debugging halted

In> Contains({a,b,c},a)
Out> True;

so we found the problem.


Debugging functions supported

After the debugger has been invoked, the following commands can be used in interactive mode while debugging:


Stepping through the files while debugging

The debug version of Yacas (which can be obtained by passing the --enable-debug parameter to ./configure) keeps track of where each function was defined. For each object in the tree kept in memory it maintains a filename and line number. This is used by the debug code. When using the debug version, the part of the file that is currently being executed will be shown. In front of the file lines there can be a sign ">". This points at the current line being executed. In addition, one can put breakpoints at specific lines in specific files. These will be shown with a "*" in front of the lines.

The predicate InDebugMode() can be used to determine if the executable currently running supports file names and line numbers of objects. It returns True if the executable was compiled with debug support, False otherwise. The debugger can use the functions DebugFile(object) and DebugLine(object) to determine the file and line of the code being executed. Typically the argument passed to these functions is CustomEval'Expression(), which returns the expression currently being executed.

The additional commands available when the debug version of the Yacas executable is used are:


The trace facilities

The trace facilities are:

The online manual pages (e.g. ?TraceStack) have more information about the use of these functions.

An example invocation of TraceRule is

In> TraceRule(x+y)2+3*5+4;

Which should then show something to the effect of

  TrEnter(2+3*5+4);
    TrEnter(2+3*5);
       TrArg(2,2);
          TrArg(3*5,15);
       TrLeave(2+3*5,17);
        TrArg(2+3*5,17);
        TrArg(4,4);
    TrLeave(2+3*5+4,21);
Out> 21;