blob: 14cfde87b48a952614f52909529d3d116d4105b0 [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 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 gg = require 'metalua.grammar.generator'
local mlp = require 'metalua.compiler.parser'
local mlp_table = require 'metalua.compiler.parser.table'
local function dots_builder (x) return `Dots{ x } end
local function for_builder (x, h)
match x with
| `Comp{ _, acc } -> table.insert (acc, h[1]); return x
| `Pair{ _, _ } -> error "No explicit key in a for list generator"
| _ -> return `Comp{ x, {h[1]} }
end
end
local function if_builder (x, p)
match x with
| `Comp{ _, acc } -> table.insert (acc, `If{ p[1] }); return x
| `Pair{ _, _ } -> error "No explicit key in a list guard"
| _ -> return `Comp{ x, p[1] }
end
end
local function comp_builder(core, list, no_unpack)
-- [ti] = temp var holding table.insert
-- [v] = variable holding the table being built
-- [r] = the core of the list being built
local ti, v, r = mlp.gensym "table_insert", mlp.gensym "table"
-----------------------------------------------------------------------------
-- 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
| `Dots{ x } ->
local w = mlp.gensym()
r = +{stat: for _, -{w} in pairs( -{x} ) do -{ `Call{ ti, v, w } } end }
| `Pair{ k, w } ->
r = `Set{ { `Index{ v, k } }, { w } }
| _ -> r = `Call{ ti, v, 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], {r})
r = list[i]
end
if no_unpack then
return `Stat{ { `Local{ {ti, v}, { +{table.insert}, `Table} }, r }, v }
else
return +{ function()
local -{ti}, -{v} = table.insert, { }
-{r}; return unpack(-{v})
end () }
end
end
local function table_content_builder (list)
match list with
| { `Comp{ y, acc } } -> return comp_builder( y, acc, "no unpack")
| _ ->
local tables = { `Table }
local ctable = tables[1]
local function flush() ctable=`Table; table.insert(tables, ctable) end
for _, x in pairs(list) do
match x with
| `Comp{ y, acc } -> table.insert(ctable, comp_builder(y, acc)); flush()
| `Dots{ y } -> table.insert(ctable, y); flush()
| _ -> table.insert (ctable, x);
end
end
match tables with
| { x } | { x, { } } -> return x
| _ ->
if #tables[#tables]==0 then table.remove(tables) end --suppress empty table
return `Call{ +{table.cat}, unpack(tables) }
end
end
end
mlp_table.field = gg.expr{ name="table cell",
primary = mlp_table.field,
suffix = { name="table cell suffix",
{ "...", builder = dots_builder },
{ "for", mlp.for_header, builder = for_builder },
{ "if", mlp.expr, builder = if_builder } } }
mlp_table.content.builder = table_content_builder
--[[
mlp.stat:add{ "for", gg.expr {
primary = for_header,
suffix = {
{ "for", mlp.for_header, builder = for_builder },
{ "if", mlp.expr, builder = if_builder } } },
"do", mlp.block, "end", builder = for_stat_builder }
--]]
--------------------------------------------------------------------------------
-- 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
--------------------------------------------------------------------------------
-- Improved "[...]" index operator:
-- * support for multi-indexes ("foo[bar, gnat]")
-- * support for ranges ("foo[bar ... gnat]")
--------------------------------------------------------------------------------
mlp.expr.suffix:del '['
mlp.expr.suffix:add{ name="table index/range",
"[", gg.list{
gg.sequence { mlp.expr, gg.onkeyword{ "...", mlp.expr } } ,
separators = { ",", ";" } },
"]", builder = index_builder }