Backtraces in Valeri
At the time of writing this post, I've finished implementing the first version of backtraces in Valeri. In case of runtime errors, you'll now get the call stack printed to stdout, pretty much as you would expect in other programming languages. Let's look at an example:
(fn get-foo (obj)
(get obj :foo)
)
;; This will fail becaue :foo is not present
;; in the dictionary
(get-foo {:bar 42})
When executed, this code will result in:
Error KeyError at /home/knazarov/dev/lisp/src/common.cpp:735
Function get
Function get-foo
The backtrace should look familiar, with the only possible problem being lack of source location information except in the last frame. This is because I don't yet produce debug information with line number / filename mappings.
What may not be immediately obvious is that get
is a "native" function that is implemented in C++. The reason it appears in the backtrace at all is due to how native functions are called. This is not just an FFI call, but such functions actually use the normal VM stack, as regular byte-compiled functions do.
This means that if I implement something like map
or fold
in C++ and put it into the standard library, debugging code that uses them is going to be quite easy, even if the C++ routines are nested in the middle of the callstack.
Overall, backtraces and the transition to tree-structured stack were the hardest parts of the language so far. Not in terms of the implementation, but just design-wise. The new stack structure is going to be used in lots of places starting from exceptions, and ending with the coroutines, effect system and the debugger.