1 ----------------------------------------------------------------------------------
2 -- This samples walks you through the writing of a simple extension.
4 -- Lua makes a difference between statements and expressions, and it's sometimes
5 -- cumbersome to put a statement where an expression is expected. Among others,
6 -- if-then-else constructs are statements, so you cannot write:
8 -- > local foo = if bar then 1 else 2
10 -- Indeed, an expression is expected at the right of the equal, and "if ..." is
11 -- a statement, which expects nested statements as "then" and "else" clauses.
12 -- The example above must therefore be written:
15 -- > if bar then foo=1 else foo=2 end
18 -- Let's allow if-then-[elseif*]-[else] constructs to be used in an expression's
19 -- context. In such a context, 'then' and 'else' are expected to be followed by
20 -- expressions, not statement blocks.
22 -- Stuff you probably need to understand, at least summarily, to follow this
25 -- * the fact that -{ ... } switches metalua into compile time mode
26 -- * mlp, the dynamically extensible metalua parser, which will be extended with
27 -- the new construct at compile time.
28 -- * gg, the grammar generator that allows to build and extend parsers, and with
29 -- which mlp is implemented.
30 -- * the fact that code can be interchangeably represented as abstract syntax
31 -- trees with `Foo{ bar } notations (easy to manipulate) or as quotes inside a
32 -- +{ ... } (easy to read).
34 ----------------------------------------------------------------------------------
37 ----------------------------------------------------------------------------------
38 -- How to turn this file in a proper syntax extension.
39 -- ===================================================
41 -- To turn this example's metalevel 0 code into a regular extension:
42 -- * Put everything inside the -{block: ... } in a separate .mlua file;
43 -- * save it in a directory called 'extension', which is itself
44 -- in your $LUA_MPATH. For instance, if your $LUA_MPATH contains
45 -- '~/local/metalua/?.mlua', you can save it as
46 -- '~/local/metalua/extension-compiler/ifexpr.mlua'
47 -- * Load the extension with "-{ extension 'ifexpr' }", whenever you want to
49 ----------------------------------------------------------------------------------
51 -{ block: -- Enter metalevel 0, where we'll start hacking the parser.
53 -------------------------------------------------------------------------------
54 -- Most extension implementations are cut in two parts: a front-end which
55 -- parses the syntax into some custom tree, and a back-end which turns that
56 -- tree into a compilable AST. Since the front-end calls the back-end, the
57 -- later has to be declared first.
58 -------------------------------------------------------------------------------
60 -------------------------------------------------------------------------------
63 -- This is the builder that turns the parser's result into an expression AST.
66 -- elseifthen_list : list of { condition, expression_if_true } pairs,
67 -- opt_else: either the expression in the 'else' final clause if any,
68 -- or false if there's no else clause.
69 -- v: the variable in which the result will be stored.
70 -- ifstat: the if-then-else statement that will be generated from
71 -- then if-then-else expression, then embedded in a `Stat{}
73 -- The builder simply turns all expressions into blocks, so that they fit in
74 -- a regular if-then-else statement. Then the resulting if-then-else is
75 -- embedded in a `Stat{ } node, so that it can be placed where an expression
78 -- The variable in which the result is stored has its name generated by
79 -- mlp.gensym(). This way we're sure there will be no variable capture.
80 -- When macro hygiene problems are more complex, it's generally a good
81 -- idea to give a look at the extension 'H'.
82 -------------------------------------------------------------------------------
83 local function builder (x)
84 local elseifthen_list, opt_else = unpack (x)
86 local v = mlp.gensym 'ife' -- the selected expr will be stored in this var.
88 for y in ivalues (elseifthen_list) do
89 local cond, val = unpack (y)
90 table.insert (ifstat, cond)
91 table.insert (ifstat, { `Set{ {v}, {val} } }) -- change expr into stat.
93 if opt_else then -- the same for else clause, except that there's no cond.
94 table.insert (ifstat, { `Set{ {v}, {opt_else} } })
96 return `Stat{ +{block: local -{v}; -{ifstat}}, v }
99 -------------------------------------------------------------------------------
102 -- This is mostly the same as the regular if-then-else parser, except that:
103 -- * it's added to the expression parser, not the statement parser;
104 -- * blocks after conditions are replaced by exprs;
106 -- In Lua, 'end' traditionally terminates a block, not an
107 -- expression. Should there be a 'end' to respect if-then-else
108 -- usual syntax, or should there be none, to respect usual implicit
109 -- expression ending? I chose not to put an 'end' here, but other people
110 -- might have other tastes...
111 -------------------------------------------------------------------------------
112 mlp.expr:add{ name = 'if-expression',
114 gg.list { gg.sequence{mlp.expr, "then", mlp.expr}, separators="elseif" },
115 gg.onkeyword{ 'else', mlp.expr },
118 } -- Back to metalevel 1, with the new feature enabled
122 ------------------------------------------------------------
123 -- The parser will read this as:
124 -- { { { `Id 'foo', `Number 1 },
125 -- { `Id 'bar', `Number 2 } },
127 -- then feed it to 'builder', which will turn it into an AST
128 ------------------------------------------------------------
130 local x = if false then 1 elseif bar then 2 else 3
132 ------------------------------------------------------------
133 -- The result after builder will be:
134 -- `Stat{ +{block: local $v$
135 -- if foo then $v$ = 1
136 -- elseif bar then $v$ = 2
138 -- end }, `Id "$v$" }
139 ------------------------------------------------------------
142 print "It seems to work..."