-{ extension 'match' }
-{ extension 'log' }
+
require 'walk'
+----------------------------------------------------------------------
+-- Back-end:
+----------------------------------------------------------------------
+
-- Parse additional elements in a loop
loop_element = gg.multisequence{
- { 'while', mlp.expr, builder = |x| `Until{ `Op{ 'not', x[1]} } },
- { 'until', mlp.expr, builder = |x| `Until{ x[1] } },
- { 'if', mlp.expr, builder = |x| `If{ x[1] } },
- { 'for', mlp.for_header, builder = |x| x[1] } }
+ { 'while', mlp.expr, builder = |x| `Until{ `Op{ 'not', x[1] } } },
+ { 'until', mlp.expr, builder = |x| `Until{ x[1] } },
+ { 'if', mlp.expr, builder = |x| `If{ x[1] } },
+ { 'unless', mlp.expr, builder = |x| `If{ `Op{ 'not', x[1] } } },
+ { 'for', mlp.for_header, builder = |x| x[1] } }
-- Recompose the loop
function xloop_builder(x)
local first, elements, body = unpack(x)
+ -------------------------------------------------------------------
-- If it's a regular loop, don't bloat the code
+ -------------------------------------------------------------------
if not next(elements) then
table.insert(first, body)
return first
end
+ -------------------------------------------------------------------
-- There's no reason to treat the first element in a special way
+ -------------------------------------------------------------------
table.insert(elements, 1, first)
- -- Breaks might have to escape several loops, so we'll rather use
- -- a goto
+ -------------------------------------------------------------------
+ -- if a header or a break must be able to exit the loops, ti will
+ -- set exit_label and use it (a regular break wouldn't be enough,
+ -- as it couldn't escape several nested loops.)
+ -------------------------------------------------------------------
local exit_label
local function exit()
if not exit_label then exit_label = mlp.gensym 'break' [1] end
return `Goto{ exit_label }
end
+ -------------------------------------------------------------------
+ -- Compile all headers elements, from last to first
+ -------------------------------------------------------------------
for i = #elements, 1, -1 do
local e = elements[i]
match e with
end
end
+ -------------------------------------------------------------------
-- Change breaks into gotos that escape all loops at once.
+ -------------------------------------------------------------------
local cfg = { stat = { }, expr = { } }
function cfg.stat.down(x)
match x with
end
function cfg.expr.down(x) if x.tag=='Function' then return 'break' end end
walk.stat(cfg, body)
+
if exit_label then body = { body, `Label{ exit_label } } end
return body
end
-loop_element_list = gg.list{ loop_element, terminators='do' }
+----------------------------------------------------------------------
+-- Front-end:
+----------------------------------------------------------------------
+
+mlp.lexer:add 'unless'
+mlp.stat:del 'for'
+mlp.stat:del 'while'
-mlp.stat:del 'for'
-mlp.stat:del 'while'
+loop_element_list = gg.list{ loop_element, terminators='do' }
mlp.stat:add{
'for', mlp.for_header, loop_element_list, 'do', mlp.block, 'end',
'while', mlp.expr, loop_element_list, 'do', mlp.block, 'end',
builder = |x| xloop_builder{ `While{x[1]}, x[2], x[3] } }
+mlp.stat:add{
+ 'unless', mlp.expr, 'then', mlp.block, 'end',
+ builder = |x| +{stat: if not -{x[1]} then -{x[2]} end} }
module ('spmatch', package.seeall)\r
-{extension 'log'}\r
\r
-match_function_builder = |tag| function (x) \r
+----------------------------------------------------------------------\r
+-- Back-end for "match function ..." and "local match function..."\r
+-- Tag must be either "Localrec" or "Set".\r
+----------------------------------------------------------------------\r
+named_match_function_builder = |tag| function (x) \r
local func_name, _, cases = unpack(x)\r
local arity = #cases[1][1][1]\r
if arity==0 then \r
-- Get rid of the former parser, it will be blended in a multiseq:\r
mlp.stat:del 'match'\r
\r
+----------------------------------------------------------------------\r
+-- "match function", "match ... with"\r
+----------------------------------------------------------------------\r
mlp.stat:add{ 'match', \r
gg.multisequence{\r
\r
match_cases_list_parser, 'end',\r
builder = match_function_builder 'Localrec' }\r
\r
+----------------------------------------------------------------------\r
+-- "match...with" expressions and "match function..."\r
+----------------------------------------------------------------------\r
mlp.expr:add{ 'match', builder = |x| x[1], gg.multisequence{\r
\r
----------------------------------------------------------------\r
terminators = { "->", "if" } },\r
gg.onkeyword{ "if", mlp.expr, consume = true },\r
"->",\r
- mlp.expr },\r
- separators = "|",\r
- terminators = "end" },\r
- 'end',\r
+ mlp.expr }, -- Notice: expression, not block!\r
+ separators = "|" },\r
+ -- Notice: no "end" keyword!\r
builder = function (x)\r
local tested_term_seq, _, cases = unpack(x)\r
local v = mlp.gensym 'match_expr'\r
+++ /dev/null
--{ extension 'xmatch' }
-
-bind {a, b} = {'o', 'k'}
-print(a..b)
-
-c, d = 'k', 'o'
-do
- local bind {c, {d}} = {'o', {'k'}}
- print(c..d)
-end
-
-print(d..c)
-
n += 1
end
+print "Prime numbers < 100, computed with lists by comprehension:"
table.print(sieve)
----------------------------------------------------------------------------------
+-- 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:
+-- 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 statements are
--- expected after a 'then' or an 'else'. The example above must be written:
+-- 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
--
--- The present sample allows 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 an expression, not a block of statements.
--
--- Stuff you probably need to understand, at least summarily, to follow this code:
+-- 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-compiler', which is itself
+-- * 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.
--
-- 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.
- -- This won't be necessary anymore once hygienic macros will be in place.
+ -- 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)
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;
------------------------------------------------------------
-- The parser will read this as:
---
--- { { { `Id 'foo', `Number 1 },
--- { `Id 'bar', `Number 2 } },
--- `Number 3 }
---
+-- { { { `Id 'foo', `Number 1 },
+-- { `Id 'bar', `Number 2 } },
+-- `Number 3 },
-- then feed it to 'builder', which will turn it into an AST
------------------------------------------------------------
------------------------------------------------------------
-- 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$" }
+-- `Stat{ +{block: local $v$
+-- if foo then $v$ = 1
+-- elseif bar then $v$ = 2
+-- else $v$ = 3
+-- end }, `Id "$v$" }
------------------------------------------------------------
assert (x == 3)
\r
----------------------------------------------------------------------\r
p"match as an expression"\r
-print(match 1 with 1 -> 'ok' | 2 -> 'KO' end)\r
+print(match 1 with 1 -> 'ok' | 2 -> 'KO')\r
\r
----------------------------------------------------------------------\r
p "global match function"\r