| ------------------------------------------------------------------------------- |
| -- Copyright (c) 2006-2013 Fabien Fleutot and others. |
| -- |
| -- All rights reserved. |
| -- |
| -- This program and the accompanying materials are made available |
| -- under the terms of the Eclipse Public License v1.0 which |
| -- accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- This program and the accompanying materials are also made available |
| -- under the terms of the MIT public license which accompanies this |
| -- distribution, and is available at http://www.lua.org/license.html |
| -- |
| -- Contributors: |
| -- Fabien Fleutot - API and implementation |
| -- |
| ------------------------------------------------------------------------------- |
| |
| ---------------------------------------------------------------------------------- |
| -- 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..." |