The timeless repository

Temple

Written by Magnus Holm.

I have a theory:

Every compilable template can be compiled to ERB.

Well, not ERB-the-syntax, but ERB-the-concept:

Every compilable template consists of three elements:

Nothing revolutionary at all. Just a theory.

Compiling it

There are several ways to render such a template, but the fastest one is to compile it to pure Ruby:

Hello <%= @world %>!
<% if @me.happy? %>
  <%= @greeting %>
<% end %>

Becomes:

_buf = []
_buf << "Hello "
_buf << (@world)
_buf << "!\n"
if @me.happy?
_buf << "\n  "
_buf << (@greeting)
_buf << "\n"
end
_buf.join

You probably also want to optimize it:

_buf = []
_buf << ("Hello #{@world}!\n")
if @me.happy?
_buf << ("\n  #{@greeting}\n")
end
_buf.join

Or maybe you rather want _buf to be a String? Or print it directly to stdout?

These options are already written and implemented in Erubis, but none of the other template engines can take advantage of that. Which is a shame, because we know that every compilable template can be compiled to ERB. So technically it shouldn’t be a problem to share such code.

Abstractions

The problem with today’s template engines is that they aren’t using enough abstractions. The first version of Mustache parsed and compiled at the same time. Haml does parsing/compiling/optimization in some tightly coupled modules. Parkaby separates parsing and compiling, but could share a lot of code with Haml since they’re both compiling HTML tags. While I haven’t checked it out, I assume the same applies to RuHL. Liquid doesn’t really compile (to Ruby) at all, but rather uses a VM approach.

Every template developers faces the same questions: Should I use an Array or String as buffer? How should I escape it? How can I optimize it? Or, they don’t even compile it at all.

Enter the Temple

Temple attempts to solve these problems. Your goal as a template developer is to end up with an Array like this:

[:multi,
 [:static, "Hello "],
 [:dynamic, "@world"],
 [:static, "!\n"],
 [:code, "if @me.happy?"],
 [:static, "\n  "],
 [:dynamic, "@greeting"],
 [:static, "\n"],
 [:code, "end"]]

Then let Temple take over. You could use Temple::Filters::DynamicInliner to optimize sequential statics/dynamics into a single dynamic. Temple::Filters::Escapable handles escaping, and Temple::Generators::ArrayBuffer generates the Ruby code.

The idea is to build an engine based on a chain of compilers. A compiler is simply a class which has a method called #compile which takes one arguments. It’s illegal for a compiler to mutate the argument, and it should be possible to use the same instance several times.

Very much like you have middlewares in Rack, you use compilers in Temple (except everything is a compiler in Temple):

class ERBEngine < Temple::Engine
  use Temple::Parsers::ERB
  use Temple::Filters::DynamicInliner
  use Temple::Generators::ArrayBuffer
end  

Step 1: The parser

In Temple, a parser is also a compiler, because a compiler is just something that takes some input and produces some output. A parser is then something that takes a String and returns an Array.

A dead simple ERB parser could look like this:

class ERB
  def compile(src)
    result = [:multi]
    while src =~ /<%(.*?)%>/
      result << [:static, $`]
      case $1[0]
      when ?#
        next
      when ?=
        text = $1[1..-1].strip
        head = :dynamic
      else
        text = $1
        head = :code
      end
      result << [head, text]
      src = $'
    end
    result << [:static, src]
    result
  end
end

It’s important to remember that the parser should be dumb. No optimization, no guesses. It should produce an Array that is as close to the source as possible. That means you’ll probably have to invent your own abstraction, but that is exactly the point!

The Mustache parser compiles to an Array like this:

[:multi,
  [:static, "Hello "],
  [:mustache, :evar, :world],
  [:mustache, :section, :happy?, 
    [:mustache, :evar, :greeting]]]

And that’s fine. The parser should not care about how to compile this further down. That’s the job to a filter.

Step N: Filters

A filter is a compiler which takes an Array and returns an Array. It might turn convert it one step closer to the core-abstraction, it might create a new abstraction, or it might just optimize in the current abstraction. Ultimately, it’s still just a compiler which takes an Array and returns an Array.

Temple::_Filters_::Mustache takes an Array in the Mustache-abstraction and compiles it down to core. Then it has to be ran through Temple::Filters::Escapable which handles HTML escaping.

You might wonder why we split Mustache into a parser and a filter, and there are several reasons. Basically it’s because the parser shouldn’t need to worry about the code it should generate. Now it’s possible to benchmark, rewrite, test and improve the parser, and the parser only.

It’s also because there isn’t one definite way to go from a Mustache-string to the core-abstraction, but there is only one way to go from a Mustache string to the Mustache-abstraction. If we want to experiment with another way of ending up at core, we can now write it without duplicating the parsing code.

Anyway, after you’ve run it through a few filters, you probably want to generate some Ruby code, and that’s what a generator does.

Step N+1: The generator

A generator is a compiler which takes an Array and returns a String. Generators, just like parsers, are dumb too. Here’s the ArrayBuffer:

class ArrayBuffer < Generator
  def buffer(str = '')
    '_buf' + str
  end
  
  def preamble;  buffer " = []\n" end
  def postamble; buffer ".join"   end
  
  def on_static(text)
    buffer " << #{text.inspect}\n"
  end
  
  def on_dynamic(code)
    buffer " << (#{code})\n"
  end
  
  def on_code(code)
    code + "\n"
  end
end

Temple includes Array and StringBuffer too.

Engines

The user however would only see an engine, which is a chain of compilers:

class ERBEngine < Temple::Engine
  # Here using some helpers, but it's important to remember that
  # it's only sugar around the #use method shown above.
  parser :ERB
  filter :DynamicInliner
  generator :ArrayBuffer
end  

The core of Temple::Engine is simple. So simple I’d like to show it to you (without the helpers):

class Engine
  def self.filters
    @filters ||= []
  end
  
  def self.use(filter, *args, &blk)
    filters << [filter, args, blk]
  end
  
  def initialize
    @chain = self.class.filters.map do |filter, args, blk|
      filter.new(*args, &blk)
    end
  end
  
  def compile(thing)
    @chain.inject(thing) { |prev_thing, compiler| compiler.compile(prev_thing) }
  end
end

No magic at all (as long as you understand how inject works). I really like how Temple contains many small pieces which does one thing, and they all easily stacks up and produce some fairy good Ruby code.

Another abstraction: Haml and HTML

Okay, so I’ve shown you two examples: ERB which compiles directly to core, and Mustache which uses one abstraction. Now let’s have a look at Haml.

Because Haml is so complex, going directly to core can be difficult. Instead it might be smart to introduce an HTML-abstraction:

[:multi,
  [:html, :tag,
    :a,
    [:basicattr, :href, "http://judofyr.net/"],
    [:static, "Magnus Holm's blog"]]]  

There can be several HTML-compilers. One aims for speed (Haml’s ugly option), one that aims for pretty indentation (Haml’s pretty option). And I can use the same compiler in Parkaby. If one of these compilers improves and generates better code, it’s going to improve performance in both Haml and Parkaby. Performance wars are going to be so boring!

Nathan Weizenbaum (the maintainer of Haml) told me targeting Haml could be difficult, but I’m quite optimistic. It’s better to try, fail and learn, than not try at all. And to be honest, I’m willing to bend things around to make Haml a happy citizen in Temple.

I’d also love to see how Liquid can fit into this mix.

The goal

So what do I want with all this? I want to experiment! I want to see if it’s possible to improve on the lowest level of the abstractions, and I’d like to see how it affects the upper layers. I’d like to learn more about different template engines; how they work, how they are parsed, how they perform. I simply want to learn. And I want to share the knowledge.

I want people to experiment and create new concepts in templating. I want to see Domain-Specific Template-Languages. I want people to realize that creating a template engine is just like creating a programming language, just on a smaller scale. Testing frameworks are so boring, why don’t try to create The Perfect Template Language™?

And, of course, I want fast template engines that shares code which each other.

But in the end, it’s just an experiment. Maybe it’s successful, probably not, it doesn’t matter so much as long as I have fun.

Join the fun!

I’m the dumb one here, and if you know anything about template engines I’d love to hear your thoughts on Temple. In fact, I’d love to hear everybody’s thoughts on Temple!

If you’re interested, please join the mailing list. The documentation is pretty non-existing at the moment, if there are any questions at all, please do not hesitate to ask.

The code is available at github.com/judofyr/temple