blob: c6ca15a9cf4d347c1e29ad31d6bad6e7e6d077d6 [file] [log] [blame]
-------------------------------------------------------------------------------
-- 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..."