I’ve been working on some improvements to the debugging features in uLisp, and I would be interested in feedback on them with a view to possibly incorporating them in the next release of uLisp.
Error messages
I’m generally fairly happy with the error reporting in uLisp. The error messages seem clear, and they are generally descriptive, with information about the context and the value causing the problem:
For a full list of the error messages, and an explanation of when they can occur, see Error messages.
However, while working on a recent uLisp project (A Lisp compiler for ARM written in Lisp) I became aware of one area in which the current error reporting falls short; it’s often not easy to locate the cause of the error in a large program.
Here’s a rather contrived example:
(defun eqn (x) (* x (/ (- 4 x))))
(defun sq (x) (/ (* (eqn x) (eqn x)) x))
(defun myprog () (dotimes (x 2) (print (sq (+ 3 x)))))
The main program, (myprog) , prints the result of calling sq which in turn calls a function eqn. There’s a bug that causes a runtime error. In current versions of uLisp, running myprog would display this error message:
22925> (myprog)
3
Error: '/' division by zero
22925>
But it doesn’t help you work out where the division operation is that’s causing the error.
Backtrace
My proposed improvement to uLisp is to provide a backtrace in each error message, which shows in square brackets the sequence of function calls that caused the error:
22925> (myprog)
3
Error [eqn <- sq <- myprog]: '/' division by zero
22925>
This makes it clear that the error occurred in the user function eqn, and this was called by sq which in turn was called by myprog.
By default I propose to show a call sequence of up to ten functions; this should be enough for most applications.
Compile-time option
if you preferred the way uLisp used to be you could disable the feature with the directive:
#define backtrace
There is no performance penalty, but it uses a small amount of RAM to store the backtrace, which should only be significant on very constrained platforms.
Trace
I’ve also added the ability to display the backtrace from within the break function. For example, suppose you insert a call to break within the eqn function above:
(defun eqn (x) (break) (* x (/ (- 4 x))))
Running myprog will execute the break and put you in the REPL from where you can examine the value of variables such as x :
22922> (myprog)
Break!
22901 : 1> x
3
Previously you could continue execution by typing nil. I propose to add the following options:
Option | Description |
---|---|
nil or :c | Continue execution |
:b | Display backtrace |
:a | Abort |
The :b option will display a backtrace in the same format as in the error messages:
22901 : 1> :b
[eqn <- sq <- myprog]
The :a option aborts execution and returns to the main prompt. It’s equivalent to typing :c repeatedly.
For a full description of break, and the other debugging features in uLisp, see Debugging in uLisp.
Feedback
I look forward to hearing feedback about these proposed new features!