« Previous | Next »

Fixing Underscore Assignments

12 Aug 2012

Goran Krampe's HttpView2 web programming framework has a nice utility called MacroProcessor, which provides PHP-ish expansions, as per the class comment:

| proc |
proc := MacroProcessor block: [ :x | (Compiler evaluate: '3+4') asString
            between: ''.
proc process: 'This code: 3+4 evaluates to: .'.

The above code produces the string 'This code: 3+4 evaluates to: 7.'

I've previously used MacroProcessor to good effect to generate Linux- and Windows-specific Makefiles from templates.

MacroProcessor's code contains a sprinkling of underscores as assignment operators. The code base is small, so it is possible to convert underscores to the ":=" assignment operator by hand, but what fun is that?

What is fun is to do that in Smalltalk. The following is done in Pharo 1.2, which is where I have a copy of MacroProcessor. The starting points were Compiler, RBParser, Decompiler, etc. After poking around - running code in workspaces and exploring their results, looking at test cases - I come to RBConfigurableFormatter>>acceptAssignmentNode: which looks like this:

acceptAssignmentNode: anAssignmentNode
    self visitNode: anAssignmentNode variable.
    codeStream space; nextPutAll: anAssignmentNode assignmentOperator; space.
    self visitNode: anAssignmentNode value

Ok... And assignmentOperator turns out to be RBAssignmentNode>>assignmentOperator, which is:

    ^ (self assignmentPosition notNil and: [ self source notNil and: [ (self source at: self assignmentPosition ifAbsent: [ nil ]) = $_ ]])
        ifTrue: [ '_' ]
        ifFalse: [ ':=' ]

Aha! The next step is obvious:

RBConfigurableFormatter subclass: #PNUnderscoreRewriter
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'PN-Tools'

acceptAssignmentNode: anAssignmentNode 
    self visitNode: anAssignmentNode variable.
    codeStream space; nextPutAll: ':='; space.
    self visitNode: anAssignmentNode value

With the above, and a bit more exploratory programming in workspaces, I end up with the following:

MacroProcessor selectorsDo: [ :s |
    | formatter method oldSrc newSrc |
        formatter := PNUnderscoreRewriter new.
        method := MacroProcessor compiledMethodAt: s.
        oldSrc := MacroProcessor sourceCodeAt: s.
        newSrc := formatter format: (RBParser parseMethod: oldSrc).
        MacroProcessor compile: newSrc classified: method catergory ]

Figure 1: Here's what a method looked like originally.

Figure 2: Here's what the method looks like afterwards.

It remains to file MacroProcessor out from the Pharo 1.2 image, then file it into my current working Pharo 1.4 image.

Tags: metaprogramming