mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
This is an important refactor of the way documentation generation works
* Installs luarocks WITH it's executable (easy to install other rocks if necessary) * Use Lua supplied with luarocks * Create Utils/luadocumentor.bat, which works with RELATIVE PATH ! -> Everybody can generate the doc * Updated launch files accordingly
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- 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 extension implements list comprehensions, similar to Haskell and
|
||||
-- Python syntax, to easily describe lists.
|
||||
--
|
||||
-- * x[a ... b] is the list { x[a], x[a+1], ..., x[b] }
|
||||
-- * { f()..., b } contains all the elements returned by f(), then b
|
||||
-- (allows to expand list fields other than the last one)
|
||||
-- * list comprehensions a la python, with "for" and "if" suffixes:
|
||||
-- {i+10*j for i=1,3 for j=1,3 if i~=j} is { 21, 31, 12, 32, 13, 23 }
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-{ extension ("match", ...) }
|
||||
|
||||
local SUPPORT_IMPROVED_LOOPS = true
|
||||
local SUPPORT_IMPROVED_INDEXES = false -- depends on deprecated table.isub
|
||||
local SUPPORT_CONTINUE = true
|
||||
local SUPPORT_COMP_LISTS = true
|
||||
|
||||
assert (SUPPORT_IMPROVED_LOOPS or not SUPPORT_CONTINUE,
|
||||
"Can't support 'continue' without improved loop headers")
|
||||
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local Q = require 'metalua.treequery'
|
||||
|
||||
local function dots_list_suffix_builder (x) return `DotsSuffix{ x } end
|
||||
|
||||
local function for_list_suffix_builder (list_element, suffix)
|
||||
local new_header = suffix[1]
|
||||
match list_element with
|
||||
| `Comp{ _, acc } -> table.insert (acc, new_header); return list_element
|
||||
| _ -> return `Comp{ list_element, { new_header } }
|
||||
end
|
||||
end
|
||||
|
||||
local function if_list_suffix_builder (list_element, suffix)
|
||||
local new_header = `If{ suffix[1] }
|
||||
match list_element with
|
||||
| `Comp{ _, acc } -> table.insert (acc, new_header); return list_element
|
||||
| _ -> return `Comp{ list_element, { new_header } }
|
||||
end
|
||||
end
|
||||
|
||||
-- Builds a statement from a table element, which adds this element to
|
||||
-- a table `t`, potentially thanks to an alias `tinsert` to
|
||||
-- `table.insert`.
|
||||
-- @param core the part around which the loops are built.
|
||||
-- either `DotsSuffix{expr}, `Pair{ expr } or a plain expression
|
||||
-- @param list comprehension suffixes, in the order in which they appear
|
||||
-- either `Forin{ ... } or `Fornum{ ...} or `If{ ... }. In each case,
|
||||
-- it misses a last child node as its body.
|
||||
-- @param t a variable containing the table to fill
|
||||
-- @param tinsert a variable containing `table.insert`.
|
||||
--
|
||||
-- @return fill a statement which fills empty table `t` with the denoted element
|
||||
local function comp_list_builder(core, list, t, tinsert)
|
||||
local filler
|
||||
-- 1 - Build the loop's core: if it has suffix "...", every elements of the
|
||||
-- multi-return must be inserted, hence the extra [for] loop.
|
||||
match core with
|
||||
| `DotsSuffix{ element } ->
|
||||
local x = gg.gensym()
|
||||
filler = +{stat: for _, -{x} in pairs{ -{element} } do (-{tinsert})(-{t}, -{x}) end }
|
||||
| `Pair{ key, value } ->
|
||||
--filler = +{ -{t}[-{key}] = -{value} }
|
||||
filler = `Set{ { `Index{ t, key } }, { value } }
|
||||
| _ -> filler = +{ (-{tinsert})(-{t}, -{core}) }
|
||||
end
|
||||
|
||||
-- 2 - Stack the `if` and `for` control structures, from outside to inside.
|
||||
-- This is done in a destructive way for the elements of [list].
|
||||
for i = #list, 1, -1 do
|
||||
table.insert (list[i], {filler})
|
||||
filler = list[i]
|
||||
end
|
||||
|
||||
return filler
|
||||
end
|
||||
|
||||
local function table_content_builder (list)
|
||||
local special = false -- Does the table need a special builder?
|
||||
for _, element in ipairs(list) do
|
||||
local etag = element.tag
|
||||
if etag=='Comp' or etag=='DotsSuffix' then special=true; break end
|
||||
end
|
||||
if not special then list.tag='Table'; return list end
|
||||
|
||||
local t, tinsert = gg.gensym 'table', gg.gensym 'table_insert'
|
||||
local filler_block = { +{stat: local -{t}, -{tinsert} = { }, table.insert } }
|
||||
for _, element in ipairs(list) do
|
||||
local filler
|
||||
match element with
|
||||
| `Comp{ core, comp } -> filler = comp_list_builder(core, comp, t, tinsert)
|
||||
| _ -> filler = comp_list_builder(element, { }, t, tinsert)
|
||||
end
|
||||
table.insert(filler_block, filler)
|
||||
end
|
||||
return `Stat{ filler_block, t }
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Back-end for improved index operator.
|
||||
local function index_builder(a, suffix)
|
||||
match suffix[1] with
|
||||
-- Single index, no range: keep the native semantics
|
||||
| { { e, false } } -> return `Index{ a, e }
|
||||
-- Either a range, or multiple indexes, or both
|
||||
| ranges ->
|
||||
local r = `Call{ +{table.isub}, a }
|
||||
local function acc (x,y) table.insert (r,x); table.insert (r,y) end
|
||||
for _, seq in ipairs (ranges) do
|
||||
match seq with
|
||||
| { e, false } -> acc(e,e)
|
||||
| { e, f } -> acc(e,f)
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Find continue statements in a loop body, change them into goto
|
||||
-- end-of-body.
|
||||
local function transform_continue_statements(body)
|
||||
local continue_statements = Q(body)
|
||||
:if_unknown() -- tolerate unknown 'Continue' statements
|
||||
:not_under ('Forin', 'Fornum', 'While', 'Repeat')
|
||||
:filter ('Continue')
|
||||
:list()
|
||||
if next(continue_statements) then
|
||||
local continue_label = gg.gensym 'continue' [1]
|
||||
table.insert(body, `Label{ continue_label })
|
||||
for _, statement in ipairs(continue_statements) do
|
||||
statement.tag = 'Goto'
|
||||
statement[1] = continue_label
|
||||
end
|
||||
return true
|
||||
else return false end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Back-end for loops with a multi-element header
|
||||
local function loop_builder(x)
|
||||
local first, elements, body = unpack(x)
|
||||
|
||||
-- Change continue statements into gotos.
|
||||
if SUPPORT_CONTINUE then transform_continue_statements(body) end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- 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)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Change breaks into gotos that escape all loops at once.
|
||||
local exit_label = nil
|
||||
local function break_to_goto(break_node)
|
||||
if not exit_label then exit_label = gg.gensym 'break' [1] end
|
||||
break_node = break_node or { }
|
||||
break_node.tag = 'Goto'
|
||||
break_node[1] = exit_label
|
||||
return break_node
|
||||
end
|
||||
Q(body)
|
||||
:not_under('Function', 'Forin', 'Fornum', 'While', 'Repeat')
|
||||
:filter('Break')
|
||||
:foreach (break_to_goto)
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- Compile all headers elements, from last to first.
|
||||
-- invariant: `body` is a block (not a statement)
|
||||
local result = body
|
||||
for i = #elements, 1, -1 do
|
||||
local e = elements[i]
|
||||
match e with
|
||||
| `If{ cond } ->
|
||||
result = { `If{ cond, result } }
|
||||
| `Until{ cond } ->
|
||||
result = +{block: if -{cond} then -{break_to_goto()} else -{result} end }
|
||||
| `While{ cond } ->
|
||||
if i==1 then result = { `While{ cond, result } } -- top-level while
|
||||
else result = +{block: if -{cond} then -{result} else -{break_to_goto()} end } end
|
||||
| `Forin{ ... } | `Fornum{ ... } ->
|
||||
table.insert (e, result); result={e}
|
||||
| _-> require'metalua.pprint'.printf("Bad loop header element %s", e)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------
|
||||
-- If some breaks had to be changed into gotos, insert the label
|
||||
if exit_label then result = { result, `Label{ exit_label } } end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Improved "[...]" index operator:
|
||||
-- * support for multi-indexes ("foo[bar, gnat]")
|
||||
-- * support for ranges ("foo[bar ... gnat]")
|
||||
--------------------------------------------------------------------------------
|
||||
local function extend(M)
|
||||
|
||||
local _M = gg.future(M)
|
||||
|
||||
if SUPPORT_COMP_LISTS then
|
||||
-- support for "for" / "if" comprehension suffixes in literal tables
|
||||
local original_table_element = M.table.element
|
||||
M.table.element = gg.expr{ name="table cell",
|
||||
primary = original_table_element,
|
||||
suffix = { name="table cell suffix",
|
||||
{ "...", builder = dots_list_suffix_builder },
|
||||
{ "for", _M.for_header, builder = for_list_suffix_builder },
|
||||
{ "if", _M.expr, builder = if_list_suffix_builder } } }
|
||||
M.table.content.builder = table_content_builder
|
||||
end
|
||||
|
||||
if SUPPORT_IMPROVED_INDEXES then
|
||||
-- Support for ranges and multiple indices in bracket suffixes
|
||||
M.expr.suffix:del '['
|
||||
M.expr.suffix:add{ name="table index/range",
|
||||
"[", gg.list{
|
||||
gg.sequence { _M.expr, gg.onkeyword{ "...", _M.expr } } ,
|
||||
separators = { ",", ";" } },
|
||||
"]", builder = index_builder }
|
||||
end
|
||||
|
||||
if SUPPORT_IMPROVED_LOOPS then
|
||||
local original_for_header = M.for_header
|
||||
M.stat :del 'for'
|
||||
M.stat :del 'while'
|
||||
|
||||
M.loop_suffix = gg.multisequence{
|
||||
{ 'while', _M.expr, builder = |x| `Until{ `Op{ 'not', x[1] } } },
|
||||
{ 'until', _M.expr, builder = |x| `Until{ x[1] } },
|
||||
{ 'if', _M.expr, builder = |x| `If{ x[1] } },
|
||||
{ 'for', original_for_header, builder = |x| x[1] } }
|
||||
|
||||
M.loop_suffix_list = gg.list{ _M.loop_suffix, terminators='do' }
|
||||
|
||||
M.stat :add{
|
||||
'for', original_for_header, _M.loop_suffix_list, 'do', _M.block, 'end',
|
||||
builder = loop_builder }
|
||||
|
||||
M.stat :add{
|
||||
'while', _M.expr, _M.loop_suffix_list, 'do', _M.block, 'end',
|
||||
builder = |x| loop_builder{ `While{x[1]}, x[2], x[3] } }
|
||||
end
|
||||
|
||||
if SUPPORT_CONTINUE then
|
||||
M.lexer :add 'continue'
|
||||
M.stat :add{ 'continue', builder='Continue' }
|
||||
end
|
||||
end
|
||||
|
||||
return extend
|
||||
@@ -0,0 +1,400 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- 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
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
--
|
||||
-- Glossary:
|
||||
--
|
||||
-- * term_seq: the tested stuff, a sequence of terms
|
||||
-- * pattern_element: might match one term of a term seq. Represented
|
||||
-- as expression ASTs.
|
||||
-- * pattern_seq: might match a term_seq
|
||||
-- * pattern_group: several pattern seqs, one of them might match
|
||||
-- the term seq.
|
||||
-- * case: pattern_group * guard option * block
|
||||
-- * match_statement: tested term_seq * case list
|
||||
--
|
||||
-- Hence a complete match statement is a:
|
||||
--
|
||||
-- { list(expr), list{ list(list(expr)), expr or false, block } }
|
||||
--
|
||||
-- Implementation hints
|
||||
-- ====================
|
||||
--
|
||||
-- The implementation is made as modular as possible, so that parts
|
||||
-- can be reused in other extensions. The priviledged way to share
|
||||
-- contextual information across functions is through the 'cfg' table
|
||||
-- argument. Its fields include:
|
||||
--
|
||||
-- * code: code generated from pattern. A pattern_(element|seq|group)
|
||||
-- is compiled as a sequence of instructions which will jump to
|
||||
-- label [cfg.on_failure] if the tested term doesn't match.
|
||||
--
|
||||
-- * on_failure: name of the label where the code will jump if the
|
||||
-- pattern doesn't match
|
||||
--
|
||||
-- * locals: names of local variables used by the pattern. This
|
||||
-- includes bound variables, and temporary variables used to
|
||||
-- destructure tables. Names are stored as keys of the table,
|
||||
-- values are meaningless.
|
||||
--
|
||||
-- * after_success: label where the code must jump after a pattern
|
||||
-- succeeded to capture a term, and the guard suceeded if there is
|
||||
-- any, and the conditional block has run.
|
||||
--
|
||||
-- * ntmp: number of temporary variables used to destructurate table
|
||||
-- in the current case.
|
||||
--
|
||||
-- Code generation is performed by acc_xxx() functions, which accumulate
|
||||
-- code in cfg.code:
|
||||
--
|
||||
-- * acc_test(test, cfg) will generate a jump to cfg.on_failure
|
||||
-- *when the test returns TRUE*
|
||||
--
|
||||
-- * acc_stat accumulates a statement
|
||||
--
|
||||
-- * acc_assign accumulate an assignment statement, and makes sure that
|
||||
-- the LHS variable the registered as local in cfg.locals.
|
||||
--
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- TODO: hygiene wrt type()
|
||||
-- TODO: cfg.ntmp isn't reset as often as it could. I'm not even sure
|
||||
-- the corresponding locals are declared.
|
||||
|
||||
local checks = require 'checks'
|
||||
local gg = require 'metalua.grammar.generator'
|
||||
local pp = require 'metalua.pprint'
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- This would have been best done through library 'metalua.walk',
|
||||
-- but walk depends on match, so we have to break the dependency.
|
||||
-- It replaces all instances of `...' in `ast' with `term', unless
|
||||
-- it appears in a function.
|
||||
----------------------------------------------------------------------
|
||||
local function replace_dots (ast, term)
|
||||
local function rec (node)
|
||||
for i, child in ipairs(node) do
|
||||
if type(child)~="table" then -- pass
|
||||
elseif child.tag=='Dots' then
|
||||
if term=='ambiguous' then
|
||||
error ("You can't use `...' on the right of a match case when it appears "..
|
||||
"more than once on the left")
|
||||
else node[i] = term end
|
||||
elseif child.tag=='Function' then return nil
|
||||
else rec(child) end
|
||||
end
|
||||
end
|
||||
return rec(ast)
|
||||
end
|
||||
|
||||
local tmpvar_base = gg.gensym 'submatch.' [1]
|
||||
|
||||
local function next_tmpvar(cfg)
|
||||
assert (cfg.ntmp, "No cfg.ntmp imbrication level in the match compiler")
|
||||
cfg.ntmp = cfg.ntmp+1
|
||||
return `Id{ tmpvar_base .. cfg.ntmp }
|
||||
end
|
||||
|
||||
-- Code accumulators
|
||||
local acc_stat = |x,cfg| table.insert (cfg.code, x)
|
||||
local acc_test = |x,cfg| acc_stat(+{stat: if -{x} then -{`Goto{cfg.on_failure}} end}, cfg)
|
||||
-- lhs :: `Id{ string }
|
||||
-- rhs :: expr
|
||||
local function acc_assign (lhs, rhs, cfg)
|
||||
assert(lhs.tag=='Id')
|
||||
cfg.locals[lhs[1]] = true
|
||||
acc_stat (`Set{ {lhs}, {rhs} }, cfg)
|
||||
end
|
||||
|
||||
local literal_tags = { String=1, Number=1, True=1, False=1, Nil=1 }
|
||||
|
||||
-- pattern :: `Id{ string }
|
||||
-- term :: expr
|
||||
local function id_pattern_element_builder (pattern, term, cfg)
|
||||
assert (pattern.tag == "Id")
|
||||
if pattern[1] == "_" then
|
||||
-- "_" is used as a dummy var ==> no assignment, no == checking
|
||||
cfg.locals._ = true
|
||||
elseif cfg.locals[pattern[1]] then
|
||||
-- This var is already bound ==> test for equality
|
||||
acc_test (+{ -{term} ~= -{pattern} }, cfg)
|
||||
else
|
||||
-- Free var ==> bind it, and remember it for latter linearity checking
|
||||
acc_assign (pattern, term, cfg)
|
||||
cfg.locals[pattern[1]] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- mutually recursive with table_pattern_element_builder
|
||||
local pattern_element_builder
|
||||
|
||||
-- pattern :: pattern and `Table{ }
|
||||
-- term :: expr
|
||||
local function table_pattern_element_builder (pattern, term, cfg)
|
||||
local seen_dots, len = false, 0
|
||||
acc_test (+{ type( -{term} ) ~= "table" }, cfg)
|
||||
for i = 1, #pattern do
|
||||
local key, sub_pattern
|
||||
if pattern[i].tag=="Pair" then -- Explicit key/value pair
|
||||
key, sub_pattern = unpack (pattern[i])
|
||||
assert (literal_tags[key.tag], "Invalid key")
|
||||
else -- Implicit key
|
||||
len, key, sub_pattern = len+1, `Number{ len+1 }, pattern[i]
|
||||
end
|
||||
|
||||
-- '...' can only appear in final position
|
||||
-- Could be fixed actually...
|
||||
assert (not seen_dots, "Wrongly placed `...' ")
|
||||
|
||||
if sub_pattern.tag == "Id" then
|
||||
-- Optimization: save a useless [ v(n+1)=v(n).key ]
|
||||
id_pattern_element_builder (sub_pattern, `Index{ term, key }, cfg)
|
||||
if sub_pattern[1] ~= "_" then
|
||||
acc_test (+{ -{sub_pattern} == nil }, cfg)
|
||||
end
|
||||
elseif sub_pattern.tag == "Dots" then
|
||||
-- Remember where the capture is, and thatt arity checking shouldn't occur
|
||||
seen_dots = true
|
||||
else
|
||||
-- Business as usual:
|
||||
local v2 = next_tmpvar(cfg)
|
||||
acc_assign (v2, `Index{ term, key }, cfg)
|
||||
pattern_element_builder (sub_pattern, v2, cfg)
|
||||
-- TODO: restore ntmp?
|
||||
end
|
||||
end
|
||||
if seen_dots then -- remember how to retrieve `...'
|
||||
-- FIXME: check, but there might be cases where the variable -{term}
|
||||
-- will be overridden in contrieved tables.
|
||||
-- ==> save it now, and clean the setting statement if unused
|
||||
if cfg.dots_replacement then cfg.dots_replacement = 'ambiguous'
|
||||
else cfg.dots_replacement = +{ select (-{`Number{len}}, unpack(-{term})) } end
|
||||
else -- Check arity
|
||||
acc_test (+{ #-{term} ~= -{`Number{len}} }, cfg)
|
||||
end
|
||||
end
|
||||
|
||||
-- mutually recursive with pattern_element_builder
|
||||
local eq_pattern_element_builder, regexp_pattern_element_builder
|
||||
|
||||
-- Concatenate code in [cfg.code], that will jump to label
|
||||
-- [cfg.on_failure] if [pattern] doesn't match [term]. [pattern]
|
||||
-- should be an identifier, or at least cheap to compute and
|
||||
-- side-effects free.
|
||||
--
|
||||
-- pattern :: pattern_element
|
||||
-- term :: expr
|
||||
function pattern_element_builder (pattern, term, cfg)
|
||||
if literal_tags[pattern.tag] then
|
||||
acc_test (+{ -{term} ~= -{pattern} }, cfg)
|
||||
elseif "Id" == pattern.tag then
|
||||
id_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Op" == pattern.tag and "div" == pattern[1] then
|
||||
regexp_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Op" == pattern.tag and "eq" == pattern[1] then
|
||||
eq_pattern_element_builder (pattern, term, cfg)
|
||||
elseif "Table" == pattern.tag then
|
||||
table_pattern_element_builder (pattern, term, cfg)
|
||||
else
|
||||
error ("Invalid pattern at "..
|
||||
tostring(pattern.lineinfo)..
|
||||
": "..pp.tostring(pattern, {hide_hash=true}))
|
||||
end
|
||||
end
|
||||
|
||||
function eq_pattern_element_builder (pattern, term, cfg)
|
||||
local _, pat1, pat2 = unpack (pattern)
|
||||
local ntmp_save = cfg.ntmp
|
||||
pattern_element_builder (pat1, term, cfg)
|
||||
cfg.ntmp = ntmp_save
|
||||
pattern_element_builder (pat2, term, cfg)
|
||||
end
|
||||
|
||||
-- pattern :: `Op{ 'div', string, list{`Id string} or `Id{ string }}
|
||||
-- term :: expr
|
||||
local function regexp_pattern_element_builder (pattern, term, cfg)
|
||||
local op, regexp, sub_pattern = unpack(pattern)
|
||||
|
||||
-- Sanity checks --
|
||||
assert (op=='div', "Don't know what to do with that op in a pattern")
|
||||
assert (regexp.tag=="String",
|
||||
"Left hand side operand for '/' in a pattern must be "..
|
||||
"a literal string representing a regular expression")
|
||||
if sub_pattern.tag=="Table" then
|
||||
for _, x in ipairs(sub_pattern) do
|
||||
assert (x.tag=="Id" or x.tag=='Dots',
|
||||
"Right hand side operand for '/' in a pattern must be "..
|
||||
"a list of identifiers")
|
||||
end
|
||||
else
|
||||
assert (sub_pattern.tag=="Id",
|
||||
"Right hand side operand for '/' in a pattern must be "..
|
||||
"an identifier or a list of identifiers")
|
||||
end
|
||||
|
||||
-- Regexp patterns can only match strings
|
||||
acc_test (+{ type(-{term}) ~= 'string' }, cfg)
|
||||
-- put all captures in a list
|
||||
local capt_list = +{ { string.strmatch(-{term}, -{regexp}) } }
|
||||
-- save them in a var_n for recursive decomposition
|
||||
local v2 = next_tmpvar(cfg)
|
||||
acc_stat (+{stat: local -{v2} = -{capt_list} }, cfg)
|
||||
-- was capture successful?
|
||||
acc_test (+{ not next (-{v2}) }, cfg)
|
||||
pattern_element_builder (sub_pattern, v2, cfg)
|
||||
end
|
||||
|
||||
|
||||
-- Jumps to [cfg.on_faliure] if pattern_seq doesn't match
|
||||
-- term_seq.
|
||||
local function pattern_seq_builder (pattern_seq, term_seq, cfg)
|
||||
if #pattern_seq ~= #term_seq then error ("Bad seq arity") end
|
||||
cfg.locals = { } -- reset bound variables between alternatives
|
||||
for i=1, #pattern_seq do
|
||||
cfg.ntmp = 1 -- reset the tmp var generator
|
||||
pattern_element_builder(pattern_seq[i], term_seq[i], cfg)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------
|
||||
-- for each case i:
|
||||
-- pattern_seq_builder_i:
|
||||
-- * on failure, go to on_failure_i
|
||||
-- * on success, go to on_success
|
||||
-- label on_success:
|
||||
-- block
|
||||
-- goto after_success
|
||||
-- label on_failure_i
|
||||
--------------------------------------------------
|
||||
local function case_builder (case, term_seq, cfg)
|
||||
local patterns_group, guard, block = unpack(case)
|
||||
local on_success = gg.gensym 'on_success' [1]
|
||||
for i = 1, #patterns_group do
|
||||
local pattern_seq = patterns_group[i]
|
||||
cfg.on_failure = gg.gensym 'match_fail' [1]
|
||||
cfg.dots_replacement = false
|
||||
pattern_seq_builder (pattern_seq, term_seq, cfg)
|
||||
if i<#patterns_group then
|
||||
acc_stat (`Goto{on_success}, cfg)
|
||||
acc_stat (`Label{cfg.on_failure}, cfg)
|
||||
end
|
||||
end
|
||||
acc_stat (`Label{on_success}, cfg)
|
||||
if guard then acc_test (+{not -{guard}}, cfg) end
|
||||
if cfg.dots_replacement then
|
||||
replace_dots (block, cfg.dots_replacement)
|
||||
end
|
||||
block.tag = 'Do'
|
||||
acc_stat (block, cfg)
|
||||
acc_stat (`Goto{cfg.after_success}, cfg)
|
||||
acc_stat (`Label{cfg.on_failure}, cfg)
|
||||
end
|
||||
|
||||
local function match_builder (x)
|
||||
local term_seq, cases = unpack(x)
|
||||
local cfg = {
|
||||
code = `Do{ },
|
||||
after_success = gg.gensym "_after_success" }
|
||||
|
||||
|
||||
-- Some sharing issues occur when modifying term_seq,
|
||||
-- so it's replaced by a copy new_term_seq.
|
||||
-- TODO: clean that up, and re-suppress the useless copies
|
||||
-- (cf. remarks about capture bug below).
|
||||
local new_term_seq = { }
|
||||
|
||||
local match_locals
|
||||
|
||||
-- Make sure that all tested terms are variables or literals
|
||||
for i=1, #term_seq do
|
||||
local t = term_seq[i]
|
||||
-- Capture problem: the following would compile wrongly:
|
||||
-- `match x with x -> end'
|
||||
-- Temporary workaround: suppress the condition, so that
|
||||
-- all external variables are copied into unique names.
|
||||
--if t.tag ~= 'Id' and not literal_tags[t.tag] then
|
||||
local v = gg.gensym 'v'
|
||||
if not match_locals then match_locals = `Local{ {v}, {t} } else
|
||||
table.insert(match_locals[1], v)
|
||||
table.insert(match_locals[2], t)
|
||||
end
|
||||
new_term_seq[i] = v
|
||||
--end
|
||||
end
|
||||
term_seq = new_term_seq
|
||||
|
||||
if match_locals then acc_stat(match_locals, cfg) end
|
||||
|
||||
for i=1, #cases do
|
||||
local case_cfg = {
|
||||
after_success = cfg.after_success,
|
||||
code = `Do{ }
|
||||
-- locals = { } -- unnecessary, done by pattern_seq_builder
|
||||
}
|
||||
case_builder (cases[i], term_seq, case_cfg)
|
||||
if next (case_cfg.locals) then
|
||||
local case_locals = { }
|
||||
table.insert (case_cfg.code, 1, `Local{ case_locals, { } })
|
||||
for v, _ in pairs (case_cfg.locals) do
|
||||
table.insert (case_locals, `Id{ v })
|
||||
end
|
||||
end
|
||||
acc_stat(case_cfg.code, cfg)
|
||||
end
|
||||
local li = `String{tostring(cases.lineinfo)}
|
||||
acc_stat(+{error('mismatch at '..-{li})}, cfg)
|
||||
acc_stat(`Label{cfg.after_success}, cfg)
|
||||
return cfg.code
|
||||
end
|
||||
|
||||
----------------------------------------------------------------------
|
||||
-- Syntactical front-end
|
||||
----------------------------------------------------------------------
|
||||
|
||||
local function extend(M)
|
||||
|
||||
local _M = gg.future(M)
|
||||
|
||||
checks('metalua.compiler.parser')
|
||||
M.lexer:add{ "match", "with", "->" }
|
||||
M.block.terminators:add "|"
|
||||
|
||||
local match_cases_list_parser = gg.list{ name = "match cases list",
|
||||
gg.sequence{ name = "match case",
|
||||
gg.list{ name = "match case patterns list",
|
||||
primary = _M.expr_list,
|
||||
separators = "|",
|
||||
terminators = { "->", "if" } },
|
||||
gg.onkeyword{ "if", _M.expr, consume = true },
|
||||
"->",
|
||||
_M.block },
|
||||
separators = "|",
|
||||
terminators = "end" }
|
||||
|
||||
M.stat:add{ name = "match statement",
|
||||
"match",
|
||||
_M.expr_list,
|
||||
"with", gg.optkeyword "|",
|
||||
match_cases_list_parser,
|
||||
"end",
|
||||
builder = |x| match_builder{ x[1], x[3] } }
|
||||
end
|
||||
|
||||
return extend
|
||||
Reference in New Issue
Block a user