101 lines
3.5 KiB
Plaintext
Executable File

-{ extension 'match' }
-{ extension 'log' }
require 'metalua.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] } },
{ '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)
-------------------------------------------------------------------
-- 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
| `If{ cond } ->
body = `If{ cond, {body} }
| `Until{ cond } ->
body = +{stat: if -{cond} then -{exit()} else -{body} end }
| `Forin{ ... } | `Fornum{ ... } ->
table.insert (e, {body}); body=e
end
end
-------------------------------------------------------------------
-- Change breaks into gotos that escape all loops at once.
-------------------------------------------------------------------
local cfg = { stat = { }, expr = { } }
function cfg.stat.down(x)
match x with
| `Break -> x <- exit()
| `Forin{ ... } | `Fornum{ ... } | `While{ ... } | `Repeat{ ... } ->
return 'break'
| _ -> -- pass
end
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
----------------------------------------------------------------------
-- Front-end:
----------------------------------------------------------------------
mlp.lexer:add 'unless'
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',
builder = xloop_builder }
mlp.stat:add{
'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} }