Just In Time Development

By: on January 30, 2013

Since the dark ages of yesteryear Squeak has had a very interesting button in its Debugger – “create”. Today we’re going to teach it a new trick.

Suppose you write a test, showing how Foos bar. Your very first test case is simply

FooTest >> testFoosCanBar
    Foo new bar.

You try save the method. Of course, Foo doesn’t exist yet, so Squeak prompts you to define the new class:

OK, you can now save the method. You run the test. Of course, it fails. So you click on the failing method, and see that you haven’t yet taught a Foo how to bar.

Ah, there’s a “Create” button. What’s it do? Ah, I see. It asks us which object in Foo‘s hierarchy ought to understand this message. Let’s keep it at Foo for now. OK, and now we see a stub implementation where we can enter our real code.

So far so good. Now suppose we need Foo to supply a template method for its subclasses to impleement. In C# we’d mark the method as abstract. In Smalltalk we mark the method:

Foo >> templateMethod: anObject
    self subclassResponsibility

Now we need to implement this behaviour in a subclass, Bar. We write a trivial test, run the test, see it fail. If we click on the failing test we see a decent error message…

… but we have to leave the debugger to fix this. Can’t have that! Alright, let’s see what we can do. First we need to distinguish this kind of error from other kinds of errors:

Error subclass: #SubclassResponsibility
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Exceptions-Kernel'.

Then we must actually throw the error:

    "This message sets up a framework for the behavior of the class' subclasses.
    Announce that the subclass should have implemented this message."

        signal: ('My {1} subclass should have overridden {2}'
            format: {self className. thisContext sender selector}).

Next we need to teach the Debugger how to respond to this special error:

Debugger >> buildNotifierWith: builder label: label message: messageString
    "Snip a whole pile of stuff for brevity's sake"

    "This stanza drives the first bit of JIT coding we saw"
    (self interruptedContext selector == #doesNotUnderstand:) ifTrue: [
        quads := quads copyWith:
            { 'Create'. #createMethod. #magenta. 'create the missing method' }
    "And now we recognise when we're dealing with an override method."
    (self interruptedContext selector == #subclassResponsibility) ifTrue: [
        quads := quads copyWith:
            { 'Create'. #createOverridingMethod. #magenta. 'create the missing overriding method' }
    "Snip a whole bunch more"

Debugger >> createOverridingMethod
    "Should only be called when this Debugger was created in response to a
    SubclassResponsibility exception. Create a stub for the method that was
    missing and proceed into it."

    | err msg |
    err := self contextStackTop exceptionMessage.
    msg := Message selector: err selector arguments: err calledArguments.
    self implement: msg inClass: err offendingClass inCategory: msg selectorCategory.

Here we see the other side of the comment in Object >> #error:. A ContextPart stores the variables relevant to that frame, and in a parameterless method, the first temp will be the first local variable, containing our exception. And we can simply reuse the (mildly extended) existing mechanisms for creating a stub method.

Hey, presto! Another reason not to leave the debugger!


Post a comment

Your email address will not be published.


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>