One of the main goals of the current debugger from Pharo is to be easily extensible with new debugging actions and new user interfaces. Towards that goal it models all debugging actions as objects that can be dynamically added to various widgets from the user interface. A high-level view of the debugger is given below.
We can see that a debugger is split into three distinct components:
- the session
- the debugging actions
- the user interface
The session is the base component of the debugger. Its role is to implement the low-level operations needed for working with processes and execution contexts. For simple actions one does not need to extend it. Each debugging action is encapsulated in a subclass of
DebugAction. Debugging actions are then loaded by the user interface. Currently there are two different user interfaces for the debugger one written in Spec and the other in Glamour.
Next we will go through the process of creating, step by step, one debugging action. As a running example we will implement an action for reacting to
doesNotUnderstand exceptions. This exception is raised when an object is asked to execute a method that is not implemented by its class or any superclass. Given that Pharo uses dynamic typing, this error is *caught* at run time and a debugger is opened. The debugger allows us to create the missing method. We can implement this feature using a debugging action.
First we need to create a subclass of
DebugAction. We will name the class
DoesNotUnderstandDebugAction and put it in the category ‘DebuggingActions’, as it should go into the main debugger:
DebugAction subclass: #DoesNotUnderstandDebugAction instanceVariableNames: '' classVariableNames: '' category: 'DebuggerActions'
To customize the action we need to override several methods. The first one is
#id and it should return a symbol uniquely identifying the debugging action.
DoesNotUnderstandDebugAction>>#id ^ #doesNotUnderstand
To change the label of the action we need to override
#defaultLabel and to control the position of this action among the other debugging actions
DoesNotUnderstandDebugAction>>#defaultLabel ^ 'Create' DoesNotUnderstandDebugAction>>#defaultOrder ^ 45
An important aspect of this action is that it should only be active when the debugger is opened as a result of a
doesNotUnderstand exception. To achieve this we need to override
#appliesToDebugger:. This method allows us to control when an action is active.
DoesNotUnderstandDebugAction>>#appliesToDebugger: aDebugger ^ aDebugger session isInterruptedContextDoesNotUnderstand
Last but not least we need to implement the actual logic for creating the missing method. This is achieved by overriding
executeAction. In this case the logic is a bit complicated but you do not need to understand it in details (I also skipped the code for the helper methods).
DoesNotUnderstandDebugAction>>#executeAction | msg msgCategory chosenClass | msg := self interruptedContext tempAt: 1. chosenClass := self askForSuperclassOf: self interruptedContext receiver class toImplement: msg selector ifCancel: [^self]. msgCategory := (self askForCategoryIn: chosenClass default: 'as yet unclassified'). self session implement: msg classified: msgCategory inClass: chosenClass forContext: self interruptedContext. self debugger selectTopContext
Now that the action is ready we can load it in the debugger. Each widget of the user interface loads debugging actions that have on the class side a method with a particular annotation. For example, the SpecDebugger loads in the toolbar action annotated with
debuggingAction; the GTDebugger uses
gtDebuggingAction. In our case the following code is required:
DoesNotUnderstandDebugAction class>>actionType <debuggingAction>
That’s it. Now if we execute code that is calling a missing method (
Morph new backgroundColor) we get the possibility to create that method.
More information about what makes this possible can be found at scg.unibe.ch/research/moldabledebugger.
Also the debugger is not the only extensible tool from Pharo. There are many more, as part of the Glamorous Toolkit.