smalltalk programming for the web

Metaprogramming Pascal with Mustache in Smalltalk

6 February 2016

A while back I wrote about command line scripting of Pharo to backup an SQLite database, specifically, this server's iptables log. Eventually I wrote a program in Pascal to archive and rollover the log database. Reason being, using the excellent Free Pascal compiler, I link SQLite statically into the final executable, meaning I only deploy a single binary executable, which is a tad more convenient than deploying the Pharo VM, image, sources, and script files.

Well, not quite just one file.

My program, iptlb, uses the SQLite online backup API to back up the running log database to an archive database file, then "backs up" an empty template database to the running log database file, effectively overwriting it. Because I want the flexibility to change the database schema during each backup run, iptlb creates a new template database each time it is invoked. Because I also want just one Pascal source file, I want to store the database schema in the source file itself, so that the schema gets version-controlled along with the Pascal code.

However, Pascal does not support multi-line string literals:

(* This Pascal code is invalid. *)
  dbSchema = '
    create table x (xk int, xv varchar);
    create table y (yk int, yv int);

This means that I cannot embed the database schema into iptlb's Pascal source directly as a multi-line string. In production, I have to also deploy the database schema SQL as a separate file which is read by iptlb to create its template database.

There is a workaround in Pascal for lack of multi-line strings, as follows:

  sl: TStringList;
  dbSchema: String;
  sl := TStringList.create;
  sl.add('create table x (xk int, xv varchar);');
  sl.add('create table y (yk int, yv int);');
  dbSchema := sl.text; // dbSchema is now the multiline string.;

This looks like a templating thing. And that calls for Mustache!

After fiddling around, I created a simple class PMMultiLineStringTemplate which wraps the work with Mustache. Here's the motivating use case:

| tmpls mst |

"The Mustache template of the Pascal code."
tmpls := '
function {{functionName}}: String;
  sl: TStringList;
  sl := TStringList.create;{{#stringList}}
  {{functionName}} := sl.text;;

mst := PMMultilineStringTemplate new.
mst atAttribute: 'functionName' putValue: 'dbSchema'.
mst template: tmpls.

FileSystem disk workingDirectory / 'ulog-schema.sql' readStreamDo: [ :rs |
    rs ascii.
    [ rs atEnd ] whileFalse: [
        | x |
        x := rs nextLine.
        (x size > 0) ifTrue: [
            mst atAttribute: 'stringList' withSubKey: 'sline' putValue: x ]]].

FileSystem disk workingDirectory / 'dbschema.pas' writeStreamDo: [ :ws |
    ws ascii;
        lineEndConvention: #lf; 
        nextPutAll: mst value ]

Copying the output of dbschema.pas (generated with a proper schema as above) into iptlb.pas, I now have one source file iptlb.pas embedding the SQL schema, and I deploy one binary executable iptlb only.

Fixing Underscore Assignments

12 August 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.