-{ 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} }
