]> git.lizzy.rs Git - metalua.git/blob - src/samples/ifexpr.mlua
Merge remote branch 'origin/master'
[metalua.git] / src / samples / ifexpr.mlua
1 ----------------------------------------------------------------------------------
2 -- This samples walks you through the writing of a simple extension.
3 --
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:
7 --
8 -- > local foo = if bar then 1 else 2
9 --
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:
13 --
14 -- > local foo
15 -- > if bar then foo=1 else foo=2 end
16 --
17 --
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.
21 --
22 -- Stuff you probably need to understand, at least summarily, to follow this
23 -- code:
24 -- * Lua syntax
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).
33 --
34 ----------------------------------------------------------------------------------
35
36
37 ----------------------------------------------------------------------------------
38 -- How to turn this file in a proper syntax extension.
39 -- ===================================================
40 --
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
48 --   use it.
49 ----------------------------------------------------------------------------------
50
51 -{ block: -- Enter metalevel 0, where we'll start hacking the parser.
52
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    -------------------------------------------------------------------------------
59
60    -------------------------------------------------------------------------------
61    -- Back-end:
62    -- =========
63    -- This is the builder that turns the parser's result into an expression AST.
64    -- Local vars:
65    -- -----------
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{}
72    --
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
76    -- is expected.
77    --
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)
85
86       local v = mlp.gensym 'ife' -- the selected expr will be stored in this var.
87       local ifstat = `If{ }
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.
92       end
93       if opt_else then -- the same for else clause, except that there's no cond.
94          table.insert (ifstat, { `Set{ {v}, {opt_else} } })
95       end
96       return `Stat{ +{block: local -{v}; -{ifstat}}, v }
97    end
98
99    -------------------------------------------------------------------------------
100    -- Front-end:
101    -- ==========
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;
105    --
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',
113       'if',
114       gg.list { gg.sequence{mlp.expr, "then", mlp.expr}, separators="elseif" },
115       gg.onkeyword{ 'else', mlp.expr }, 
116       builder = builder } 
117
118 } -- Back to metalevel 1, with the new feature enabled
119
120 local foo, bar
121
122 ------------------------------------------------------------
123 -- The parser will read this as:
124 --    { { { `Id 'foo', `Number 1 }, 
125 --        { `Id 'bar', `Number 2 } }, 
126 --      `Number 3 },
127 -- then feed it to 'builder', which will turn it into an AST
128 ------------------------------------------------------------
129
130 local x = if false then 1 elseif bar then 2 else 3
131
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
137 --                    else            $v$ = 3
138 --                    end }, `Id "$v$" }
139 ------------------------------------------------------------
140
141 assert (x == 3)
142 print "It seems to work..."