101 lines
3.5 KiB
Plaintext
Executable File
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} }
|