| I've been working on techniques to enable a low-level interpreter engine to present (meta level) user function names in stack traces and (meta level) user variables on the debugger watch window, instead of just spilling the guts of the interpreter when you break into it with a C++ debugger. The basic idea is to design the API of the expression-building library so that the user code is always written in the form of callback functions which support staged execution. The first stage builds the expression object to be interpreted; subsequent stages could be all nops yet still serve the purpose of tracing execution and explaining generated plans. It's encouraging to me that starting from a different goal, I reached a similar point in design-space as you describe in this paper. I want to ask you about the role of continuation passing style (CPS) in towers of interpreters, and whether you view it as fundamental or merely a feature which is possible to support or not support? The reason I ask is that in my work on improving debugging, I didn't start out with the a priori intent to use CPS style, but I keep ending up with continuations popping up somewhere. Even when I try to wring them out (to simplify the use of the library) they are still "there", just maybe hidden or disguised. The intuitive explanation is that, during "tracing mode," it is not enough to simply invoke the callback (and allow it to return). Instead, we want to jump to the callback function, and then leave it's frame open and on the stack while we do additional work. How does the interpreter regain control before the user's callback function has returned? Probably, via a continuation. However I think there is also an interesting parallel or connection to coroutines. Yes, CPS can be used to implement coroutines. However, suppose coroutines are instead a language primitive, then similar staging and polymorphism techniques could be used to debug coroutines. By inverting the relationship between "main coroutine" and "worker coroutine", instead of treating the worker as a function called-by the outside world, redefine the worker as the thing driving the program and treat "everything that the rest of the world does between coro-yield and resume" as a subroutine call from the worker coroutine. What I haven't worked out yet is if and how the third leg of this triangle may be completed: using coroutines to implement the staging and debug tracing mechanism (instead of CPS). |