I'm currently building an early release of the webapp database project I've been working on in my spare time. The app is predictably written in factor which has a neat deployment mechanism: you create an image file with just the compiled code that you need to run the app and then ship that for each platform.

This is a nice solution, except I found that using the built in 'fhtml' templating language in factor requires the whole parser-compiler runtime which ramps up the image footprint of my app from 3MB to about 20MB - D'oh!

So instead I've written a very simple web templating language which doesn't require the factor parser at all:


DEFER: parse-text

: display-variable ( elem -- )
  dup callable? [ call ] [ write ] if ;

: parse-variable ( -- )
  "}" read-until [ get display-variable parse-text ]
                 [ drop "missing closing }" throw ] if ;

: maybe-variable ( -- )
  read1 dup CHAR: { = [ drop parse-variable ] [ write1 parse-text ] if ;

: parse-text ( -- )
  "$" read-until [ write maybe-variable ] [ write ] if ;

: eval-template ( path -- )
  utf8 <file-reader> [ parse-text ] with-input-stream ;

To run it you just call 'eval-template' with the path to the template file.

The syntax for variables within the template is similar to that of korn shell scripts: e.g. '${MYVAR}'. On processing the template, variable strings are just looked up in the current factor namespace. If the result is a quotation it gets invoked, otherwise the value of the variable is output as a string.

The namespace thing is cool because in factor you can 'bind' a hashtable to the top of the namespace stack before invoking eval-template, so this removes the need to pass tables around and makes the lookup code very neat.

You'll also notice that the template input is bound to stdin using the with-input-stream word prior to processing, which means I also don't have to pass the template stream around. Similarly the code expects the output stream to be bound so there's no passing that around either. Dynamic variables are a really neat factor feature and it alludes me as to why they don't appear in more mainstream languages.

Finally, something I've learnt about html templating languages over the last couple of years:

Html templates are best when the html boilerplate is clean and free of control statements (loops, conditionals), but they begin to suck as the page gets sophisticated about its rendering; usually when you hit tables and nested lists.

However the opposite seems to be true of html DSLs in lisp and factor: they're great when there's plenty of logic but become overkill when you're just doing mostly static html, especially header and footer boilerplate.

The best compromise I've found is to drive the page from the html template, but then call into real code to render tables, lists and conditional content. This also has the nice side effect of making the template language trivial.