bce65b08261ce0bfdbbe176beb6b59c7b8180c95
[metalua.git] / src / lib / extension / xloop.mlua
blobbce65b08261ce0bfdbbe176beb6b59c7b8180c95
1 -{ extension 'match' }
2 -{ extension 'log' }
4 require 'walk'
6 ----------------------------------------------------------------------
7 -- Back-end:
8 ----------------------------------------------------------------------
10 -- Parse additional elements in a loop
11 loop_element = gg.multisequence{
12    { 'while',  mlp.expr, builder = |x| `Until{ `Op{ 'not', x[1] } } },
13    { 'until',  mlp.expr, builder = |x| `Until{ x[1] } },
14    { 'if',     mlp.expr, builder = |x| `If{ x[1] } },
15    { 'unless', mlp.expr, builder = |x| `If{ `Op{ 'not', x[1] } } },
16    { 'for',    mlp.for_header, builder = |x| x[1] } }
18 -- Recompose the loop
19 function xloop_builder(x)
20    local first, elements, body = unpack(x)
22    -------------------------------------------------------------------
23    -- If it's a regular loop, don't bloat the code
24    -------------------------------------------------------------------
25    if not next(elements) then
26       table.insert(first, body)
27       return first
28    end
30    -------------------------------------------------------------------
31    -- There's no reason to treat the first element in a special way
32    -------------------------------------------------------------------
33    table.insert(elements, 1, first)
35    -------------------------------------------------------------------
36    -- if a header or a break must be able to exit the loops, ti will
37    -- set exit_label and use it (a regular break wouldn't be enough,
38    -- as it couldn't escape several nested loops.)
39    -------------------------------------------------------------------
40    local exit_label
41    local function exit()
42       if not exit_label then exit_label = mlp.gensym 'break' [1] end
43       return `Goto{ exit_label }
44    end
46    -------------------------------------------------------------------
47    -- Compile all headers elements, from last to first
48    -------------------------------------------------------------------
49    for i = #elements, 1, -1 do
50       local e = elements[i]
51       match e with
52       | `If{ cond }    ->
53          body = `If{ cond, {body} }
54       | `Until{ cond } -> 
55          body = +{stat: if -{cond} then -{exit()} else -{body} end }
56       | `Forin{ ... } | `Fornum{ ... } ->
57          table.insert (e, {body}); body=e
58       end
59    end
61    -------------------------------------------------------------------
62    -- Change breaks into gotos that escape all loops at once.
63    -------------------------------------------------------------------
64    local cfg = { stat = { }, expr = { } }
65    function cfg.stat.down(x)
66       match x with
67       | `Break -> x <- exit()
68       | `Forin{ ... } | `Fornum{ ... } | `While{ ... } | `Repeat{ ... } -> 
69          return 'break'
70       | _ -> -- pass
71       end
72    end
73    function cfg.expr.down(x) if x.tag=='Function' then return 'break' end end
74    walk.stat(cfg, body)
76    if exit_label then body = { body, `Label{ exit_label } } end
77    return body
78 end
80 ----------------------------------------------------------------------
81 -- Front-end:
82 ----------------------------------------------------------------------
84 mlp.lexer:add 'unless'
85 mlp.stat:del  'for'
86 mlp.stat:del  'while'
88 loop_element_list = gg.list{ loop_element, terminators='do' }
90 mlp.stat:add{ 
91    'for', mlp.for_header, loop_element_list, 'do', mlp.block, 'end', 
92    builder = xloop_builder }
94 mlp.stat:add{ 
95    'while', mlp.expr, loop_element_list, 'do', mlp.block, 'end', 
96    builder = |x| xloop_builder{ `While{x[1]}, x[2], x[3] } }
98 mlp.stat:add{
99    'unless', mlp.expr, 'then', mlp.block, 'end', 
100    builder = |x| +{stat: if not -{x[1]} then -{x[2]} end} }