mirror of
https://github.com/g012/l65.git
synced 2024-06-11 10:29:27 +00:00
283 lines
11 KiB
Plaintext
283 lines
11 KiB
Plaintext
-------------------------------------------------------------------------------
|
|
-- 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
|