--- /dev/null
+-{ extension 'match' }
+-{ extension 'log' }
+require 'walk'
+
+-- 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] } },
+ { '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)
+
+ -- Breaks might have to escape several loops, so we'll rather use
+ -- a goto
+ local exit_label
+ local function exit()
+ if not exit_label then exit_label = mlp.gensym 'break' [1] end
+ return `Goto{ exit_label }
+ end
+
+ 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'
+ | _ -> _
+ 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
+
+loop_element_list = gg.list{ loop_element, terminators='do' }
+
+mlp.stat:del 'for'
+mlp.stat:del 'while'
+
+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] } }
+