+++ /dev/null
-----------------------------------------------------------------------------------
--- 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..."
\ No newline at end of file