Tuesday, October 17, 2006

Introducing ErlTL: A Simple Erlang Template Language

Have you ever wanted to build the kind of mail-merge program that never crashes, that runs on a scalable fault tolerant cluster, that you can easily scale by adding more boxes, that you can hot-swap its code without taking it offline, that runs on an optimized VM that can handle hundreds of thousands of simultaneous tasks at a time, and that's built with the same technology that powers England's phone network with a long string of nines after the dot?

If you do, you're probably a dirty scumbag spammer, so go to hell :)

But assuming you're not a spammer and/or you want an easy way of embedding small logic snippets in large swaths of Erlang binary data, you will probably enjoy ErlTL: A Simple Erlang Template Language.

ErlTL does not aim to be the most feature-rich template language. It may not have any time soon that feature that you really like in another template language and that makes your web designers oh so happy. (Of course, you're always welcome to implement it yourself and share it with us. If it's useful, I'll add it to the standard distribution). Currently, ErlTL has 4 main objectives:

- Speed. ErlTL compiles template files into BEAM files prior to execution for top-notch performance.
- Simplicity. ErlTL has the smallest number of syntactic elements that are sufficient for it to be useful (at least, useful for me :) ).
- Reusability. ErlTL lets you reuse templates and template fragments, even from different template files.
- Good error reporting. When you write an invalid expression, the ErlTL compiler makes sure you know on which line the problem is.

Let's take a look at an example. Below is a simple template file called song.et (ErlTL files have the '.et' extension by convention).


<%! This template composes beautiful songs %>
<%? [What, Where, Objects, Action] = Data %>
<% What %> singing in <% Where %>,
Take these <% Objects %> and learn to <% Action %>


Now, let's use this template. Fire up the shell and type:


erltl:compile("/path/to/song.et"),
song:render([<<"Blackbird">>,
<<"the dead of the night">>, <<"broken wings">>,
<<"fly">>]).


If you didn't mess up, this is the output you'll get:


[<<"\n\n">>,
<<"Blackbird">>,
<<" singing in ">>,
<<"the dead of the night">>,
<<",\nTake these ">>,
<<"broken wings">>,
<<" and learn to ">>,
<<"fly">>,
<<"\n">>]


If this isn't exactly what you were expecting, don't panic. For efficiency, ErlTL doesn't try to concatenate all outputs. If you need to concatenate them, you can do it manually, e.g. with iolist_to_binary/1. However, doing so is often unnecessary when you want to send the result to an IO device such as a socket.

So what's happening here? ErlTL creates a module called 'song', with a function called 'render'. This function takes one parameter called 'Data'. (ErlTL also creates a zero-parameter function that calls the 1 parameter function with 'undefined' as the value for 'Data'.) After you compile the template, you can call these functions from template code snippets as well as from any Erlang module.

The template syntax consists of the following elements:


<% [Erlang code block] %>


An Erlang code block is a sequence of Erlang expressions (separated by commas). The result of the expressions is included in the function's output.


<%! [comment] %>


Comments are ignored by the compiler


<%? [top-level expressions] %>


The results of top-level expressions are excluded from the function's output. They are used primary to bind variables to elements of the Data parameter. Top level expressions must go before all standard expressions in the same function.


<%@ [function declaration] %>


Function declarations tell the compiler that all the code following the declaration belongs to a new function with the given name. This is useful when you want to reuse a template snippet. Note that all functions are exported, so you can reuse code by calling functions from other templates. Let's look at a simple example, called album.et:


<%! This template prints an album data in HTML %>
<%? {Title, Artist, Songs} = Data %>


Title: <% Title %>
Artist: <% Artist %>
Songs:

<% [song(Song) || Song <- Songs] %>




<%@ song %>
<%? {Number, Name} = Data %>

<% integer_to_list(Number) %>
<% Name %>



In the shell, type the following:

erltl:compile("/path/to/album.et"),
album:render(
{<<"Abbey Road">>, <<"The Beatles">>,
[{1, <<"Come Together">>},
{2, <<"Something">>},
{3, <<"Maxwell's Silver Hammer">>},
{4, <<"Oh! Darling">>},
{5, <<"Octopus's Garden">>},
{6, <<"I Want You (She's So Heavy)">>}]
}).


(Beatles fans, please forgive me for not typing all the song names! I got tired :) ) This will give you the following output:


[<<"\n\n\n\nTitle: ">>,
<<"Abbey Road">>,
<<"

\nArtist: ">>,
<<"Beatles">>,
<<"

\nSongs:
\n\n">>,
[[<<"\n\n\n \n \n\n">>],
[<<"\n\n\n \n \n\n">>],
[<<"\n\n\n \n \n\n">>],
[<<"\n\n\n \n \n\n">>],
[<<"\n\n\n \n \n\n">>],
[<<"\n\n\n \n \n\n">>]],
<<"\n
">>,
"1",
<<"
">>,
<<"Come Together">>,
<<"
">>,
"2",
<<"
">>,
<<"Something">>,
<<"
">>,
"3",
<<"
">>,
<<"Maxwell's Silver Hammer">>,
<<"
">>,
"4",
<<"
">>,
<<"Oh! Darling">>,
<<"
">>,
"5",
<<"
">>,
<<"Octopus's Garden">>,
<<"
">>,
"6",
<<"
">>,
<<"I Want You (She's So Heavy)">>,
<<"
\n\n\n\n">>]


That's pretty much all these is to ErlTL at the moment. At some point, it may be beneficial to add some iteration syntax, but I'm not planning on doing this in the near future. I hope you find ErlTL useful. As always, please let me know if you have any comments, suggestions or bug reports.

Enjoy!

Note: the current release is in the 0.9 branch in the repository.

3 comments:

Dru Nelson said...

just saw this on Reddit, I think this is a great tool for Erlang!

Alieniloquent: Blog said...

[...] the views. Not YAWS pages. This will involve creating my own template language. I don’t like ErlTL for this as it is too [...]

Erlang Examples » Record introspection at compile time said...

[...] template language like ErlyDTL or ErlTL [...]