Text Template

lua-users home
wiki

A good exercise for new Lua programmers is to implement Jon Bentley's Simple Macro Processor [4] in Lua. You can find it at the end of chapter 13 of O'Reilly's sed & awk book [1] , or on the web [2] [3].

One drawback of macro processors is they don't have data structures. An example provided in an older release of Lua (lua-4.0.1/test/examples/www/db.lua) shows a good technique for filling in a template file with values from data structures in a configuration file.

If all you want to do is substitute variables within a string with values from a table, then see StringInterpolation.

The following module, Expand.lua [5] , uses Bentley's algorithm for macro expansion, and the GNU make syntax for variable references within the template string. It also has a few enhancements taken from Mark J. Dominus's Text::Template.pm and Sriram Srinivasan's template-driven code generator Jeeves in chapter 17 of O'Reilly's Advanced Perl Programming.

The goal is to have one function called expand which scans a template string for variable references, and recursively replaces them with values found in one of the listed tables or functions.

The template syntax is pretty simple. References to table members and variables are expressed as either ${varname} or $(varname), or as $x if it is a one character variable name.

Simple Lua expression that return a string can also be wrapped in either ${ expr } or $( expr ).

Lua statements can be wrapped in one the following:

The Lua code can set a variable named OUT which will be substituted in place of the code in the template string.

Lua expressions and statements are evaluated by loadstring with the function environment set to the first table.

The following example shows how to use expand. Note that Expand.lua only works with Lua 5.0.

expand = require'Expand'

template = [[
you can access variables: $v
or environment variables: ${HOME}

you can call functions: ${table.concat(list, ', ')}
this list has ${list.n} elements
   ${string.rep('=', list.n)}
   ${table.concat(list)}
   ${string.rep('=', list.n)}

or evaluate code inline
${for i=1,list.n do
    OUT = table.concat{ OUT, ' list[', i, '] = ', list[i], '\n'}
  end}
you can access global variables:
This example is from ${mjd} at $(mjdweb)

The Lord High Chamberlain has gotten ${L.n}
things for me this year.
${do diff = L.n - 5
    more = 'more'
    if diff == 0 then
      diff = 'no'
    elseif diff < 0 then
      diff = -diff
      more = 'fewer'
    end
  end}
That is $(diff) $(more) than he gave me last year.

values can have other variables: $(ref)
]]

mjd = "Mark J. Dominus"
mjdweb = 'http://perl.plover.com/'
L = { 'A', 'B', 'C', 'D', n=4}
local x = {
  v = 'this is v',
  list = L,
  ref = "$(mjd) made Text::Template.pm"
}
-- fill in the template with values in table x
io.write(expand(template, x, _G, os.getenv))
Variable are found by first searching the local table x, then the global environment, and lastly by calling the os.getenv function.

Here is the output generated by the example above:

you can access variables: this is v
or environment variables: /home/pshook

you can call functions: A, B, C, D
this list has 4 elements
   ====
   ABCD
   ====

or evaluate code inline
 list[1] = A
 list[2] = B
 list[3] = C
 list[4] = D

you can access global variables:
This example is from Mark J. Dominus at http://perl.plover.com/

The Lord High Chamberlain has gotten 4
things for me this year.

That is 1 fewer than he gave me last year.

values can have other variables: Mark J. Dominus made Text::Template.pm
If you want to conditionally expand some parts of the template string, then you can use the $(when varname ...) syntax. If varname is not false, then the enclosed string will undergo macro expansion. If varname is a table, then it will be the first table searched when the enclosed string undergoes macro expansion.

A useful feature for code generation is the $(foreach tabname ...) syntax. If tabname is a table that contains a list of tables, then macro expansion will be performed on the enclosed string for each of the tables in tabname, and the results will be concatenated together.

expand = require'Expand'

fun_temp = [[
==============================================================================
$(foreach funcs

  ${type} x = ${name}( ${table.concat(args, ', ')} ) {
    $(code)
$(when stuff
    x = $x;
    y = $y;
)    reutrn $(exit);
  }
)
==============================================================================
]]

fun_list = {
  exit = 1;
  stuff = false;

  funcs = {
    { type = 'int';
      name = 'bill';
      args = { 'a', 'b', 'c' };
      code = 'something';
      stuff = { x=99, y=34 };
    };
    { type = 'char *';
      name = 'bert';
      args = { 'one', 'two', 'three' };
      code = 'something else';
      exit = 2
    };
  };
}

io.write(expand(fun_temp, fun_list, _G))
The output is:
==============================================================================

  int x = bill( a, b, c ) {
    something
    x = 99;
    y = 34;
    reutrn 1;
  }

  char * x = bert( one, two, three ) {
    something else
    reutrn 2;
  }

==============================================================================

The first version of this page was OldTextTemplate.

See Also


RecentChanges · preferences
edit · history
Last edited May 28, 2007 7:41 pm GMT (diff)