142 lines
6.4 KiB
Plaintext
Executable File
142 lines
6.4 KiB
Plaintext
Executable File
----------------------------------------------------------------------------------
|
|
-- 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..." |