How does the debugger interface to the “evaluator” (where the evaluator means all of native code, byte-code and interpreted IR1)? It seems that it would be much more straightforward to have a consistent user interface to debugging all code representations if there was a uniform debugger interface to the underlying stuff, and vice-versa.
Of course, some operations might not be supported by some representations, etc. For example, fine-control stepping might not be available in native code. In other cases, we might reduce an operation to the lowest common denominator, for example fetching lexical variables by string and admitting the possibility of ambiguous matches. [Actually, it would probably be a good idea to store the package if we are going to allow variables to be closed over.]
Some objects we would need:
Location: The constant information about the place where a value is stored, everything but which particular frame it is in. Operations: location name, type, etc. location-value frame location (setf'able) monitor-location location function Function is called whenever location is set with the location, frame and old value. If active values aren't supported, then we dummy the effect using breakpoints, in which case the change won't be noticed until the end of the block (and intermediate changes will be lost.) debug info: All the debug information for a component. Frame: frame-changed-locations frame => location* Return a list of the locations in frame that were changed since the last time this function was called. Or something. This is for displaying interesting state changes at breakpoints. save-frame-state frame => frame-state restore-frame-state frame frame-state These operations allow the debugger to back up evaluation, modulo side-effects and non-local control transfers. This copies and restores all variables, temporaries, etc, local to the frame, and also the current PC and dynamic environment (current catch, etc.) At the time of the save, the frame must be for the running function (not waiting for a call to return.) When we restore, the frame becomes current again, effectively exiting from any frames on top. (Of course, frame must not already be exited.) Thread: Representation of which stack to use, etc. Block: What successors the block has, what calls there are in the block. (Don't need to know where calls are as long as we know called function, since can breakpoint at the function.) Whether code in this block is wildly out of order due to being the result of loop-invariant optimization, etc. Operations: block-successors block => code-location* block-forms block => (source-location code-location)* Return the corresponding source locations and code locations for all forms (and form fragments) in the block.