---------------------------------------------------------------------------------- -- This samples walks you through the writing of a simple extension. -- -- Lua makes a difference between statements and expressions, and it's sometimes -- cumbersome to put a statement where an expression is expected. Among others, -- if-then-else constructs are statements, so you cannot write: -- -- > local foo = if bar then 1 else 2 -- -- Indeed, an expression is expected at the right of the equal, and "if ..." is -- a statement, which expects nested statements as "then" and "else" clauses. -- The example above must therefore be written: -- -- > local foo -- > if bar then foo=1 else foo=2 end -- -- -- Let's allow if-then-[elseif*]-[else] constructs to be used in an expression's -- context. In such a context, 'then' and 'else' are expected to be followed by -- expressions, not statement blocks. -- -- Stuff you probably need to understand, at least summarily, to follow this -- code: -- * Lua syntax -- * the fact that -{ ... } switches metalua into compile time mode -- * mlp, the dynamically extensible metalua parser, which will be extended with -- the new construct at compile time. -- * gg, the grammar generator that allows to build and extend parsers, and with -- which mlp is implemented. -- * the fact that code can be interchangeably represented as abstract syntax -- trees with `Foo{ bar } notations (easy to manipulate) or as quotes inside a -- +{ ... } (easy to read). -- ---------------------------------------------------------------------------------- ---------------------------------------------------------------------------------- -- How to turn this file in a proper syntax extension. -- =================================================== -- -- To turn this example's metalevel 0 code into a regular extension: -- * Put everything inside the -{block: ... } in a separate .mlua file; -- * save it in a directory called 'extension', which is itself -- in your $LUA_MPATH. For instance, if your $LUA_MPATH contains -- '~/local/metalua/?.mlua', you can save it as -- '~/local/metalua/extension-compiler/ifexpr.mlua' -- * Load the extension with "-{ extension 'ifexpr' }", whenever you want to -- use it. ---------------------------------------------------------------------------------- -{ block: -- Enter metalevel 0, where we'll start hacking the parser. ------------------------------------------------------------------------------- -- Most extension implementations are cut in two parts: a front-end which -- parses the syntax into some custom tree, and a back-end which turns that -- tree into a compilable AST. Since the front-end calls the back-end, the -- later has to be declared first. ------------------------------------------------------------------------------- ------------------------------------------------------------------------------- -- Back-end: -- ========= -- This is the builder that turns the parser's result into an expression AST. -- Local vars: -- ----------- -- elseifthen_list : list of { condition, expression_if_true } pairs, -- opt_else: either the expression in the 'else' final clause if any, -- or false if there's no else clause. -- v: the variable in which the result will be stored. -- ifstat: the if-then-else statement that will be generated from -- then if-then-else expression, then embedded in a `Stat{} -- -- The builder simply turns all expressions into blocks, so that they fit in -- a regular if-then-else statement. Then the resulting if-then-else is -- embedded in a `Stat{ } node, so that it can be placed where an expression -- is expected. -- -- The variable in which the result is stored has its name generated by -- mlp.gensym(). This way we're sure there will be no variable capture. -- When macro hygiene problems are more complex, it's generally a good -- idea to give a look at the extension 'H'. ------------------------------------------------------------------------------- local function builder (x) local elseifthen_list, opt_else = unpack (x) local v = mlp.gensym 'ife' -- the selected expr will be stored in this var. local ifstat = `If{ } for y in ivalues (elseifthen_list) do local cond, val = unpack (y) table.insert (ifstat, cond) table.insert (ifstat, { `Set{ {v}, {val} } }) -- change expr into stat. end if opt_else then -- the same for else clause, except that there's no cond. table.insert (ifstat, { `Set{ {v}, {opt_else} } }) end return `Stat{ +{block: local -{v}; -{ifstat}}, v } end ------------------------------------------------------------------------------- -- Front-end: -- ========== -- This is mostly the same as the regular if-then-else parser, except that: -- * it's added to the expression parser, not the statement parser; -- * blocks after conditions are replaced by exprs; -- -- In Lua, 'end' traditionally terminates a block, not an -- expression. Should there be a 'end' to respect if-then-else -- usual syntax, or should there be none, to respect usual implicit -- expression ending? I chose not to put an 'end' here, but other people -- might have other tastes... ------------------------------------------------------------------------------- mlp.expr:add{ name = 'if-expression', 'if', gg.list { gg.sequence{mlp.expr, "then", mlp.expr}, separators="elseif" }, gg.onkeyword{ 'else', mlp.expr }, builder = builder } } -- Back to metalevel 1, with the new feature enabled local foo, bar ------------------------------------------------------------ -- The parser will read this as: -- { { { `Id 'foo', `Number 1 }, -- { `Id 'bar', `Number 2 } }, -- `Number 3 }, -- then feed it to 'builder', which will turn it into an AST ------------------------------------------------------------ local x = if false then 1 elseif bar then 2 else 3 ------------------------------------------------------------ -- The result after builder will be: -- `Stat{ +{block: local $v$ -- if foo then $v$ = 1 -- elseif bar then $v$ = 2 -- else $v$ = 3 -- end }, `Id "$v$" } ------------------------------------------------------------ assert (x == 3) print "It seems to work..."