
require 'metalua.extension.match'

module ('spmatch', package.seeall)

require 'metalua.walk.id'

-{extension 'log'}

----------------------------------------------------------------------
-- Back-end for statements
-- "match function ..." and "local match function...".
-- Tag must be either "Localrec" or "Set".
----------------------------------------------------------------------
named_match_function_builder = |tag| function (x)
   local func_name, _, cases = unpack(x)
   local arity = #cases[1][1][1]
   if arity==0 then
      error "There must be at least 1 case in match function"
   end
   local args = { }
   for i=1, arity do args[i] = mlp.gensym("arg."..i) end
   local body = match_builder{args, cases}
   return { tag=tag, {func_name}, { `Function{ args, {body} } } }
end

-- Get rid of the former parser, it will be blended in a multiseq:
mlp.stat:del 'match'

----------------------------------------------------------------------
-- "match function", "match ... with"
----------------------------------------------------------------------
mlp.stat:add{ 'match',
   gg.multisequence{

      ----------------------------------------------------------------
      -- Shortcut for declaration of functions containing only a match:
      -- "function f($1) match $1 with $2 end end" can be written:
      -- "match function f $2 end"
      ----------------------------------------------------------------
      { 'function', mlp.expr, gg.optkeyword '|',
         match_cases_list_parser, 'end',
         builder = named_match_function_builder 'Set' },

      ----------------------------------------------------------------
      -- Reintroduce the original match statement:
      ----------------------------------------------------------------
      default = gg.sequence{
         mlp.expr_list, 'with', gg.optkeyword '|',
         match_cases_list_parser, 'end',
         builder = |x| match_builder{ x[1], x[3] } } } }

----------------------------------------------------------------------
-- Shortcut: "local match function f $cases end" translates to:
-- "local function f($args) match $args with $cases end end"
----------------------------------------------------------------------
mlp.stat:get'local'[2]:add{
   'match', 'function', mlp.expr, gg.optkeyword '|',
   match_cases_list_parser, 'end',
   builder = named_match_function_builder 'Localrec' }

----------------------------------------------------------------------
-- "match...with" expressions and "match function..."
----------------------------------------------------------------------
mlp.expr:add{ 'match', builder = |x| x[1], gg.multisequence{

      ----------------------------------------------------------------
      -- Anonymous match functions:
      -- "function ($1) match $1 with $2 end end" can be written:
      -- "match function $2 end"
      ----------------------------------------------------------------
      { 'function', gg.optkeyword '|',
         match_cases_list_parser,
         'end',
         builder = function(x)
            local _, cases = unpack(x)
            local v        = mlp.gensym()
            local body     = match_builder{v, cases}
            return `Function{ {v}, {body} }
         end },

      ----------------------------------------------------------------
      -- match expressions: you can put a match where an expression
      -- is expected. The case bodies are then expected to be
      -- expressions, not blocks.
      ----------------------------------------------------------------
      default = gg.sequence{
         mlp.expr_list, 'with', gg.optkeyword '|',
         gg.list{  name = "match cases list",
            gg.sequence{ name = "match expr case",
               gg.list{ name  = "match expr case patterns list",
                  primary     = mlp.expr_list,
                  separators  = "|",
                  terminators = { "->", "if" } },
               gg.onkeyword{ "if", mlp.expr, consume = true },
               "->",
               mlp.expr }, -- Notice: expression, not block!
            separators  = "|" },
         -- Notice: no "end" keyword!
         builder = function (x)
            local tested_term_seq, _, cases = unpack(x)
            local v = mlp.gensym 'match_expr'
            -- Replace expressions with blocks
            for case in ivalues (cases) do
               local body = case[3]
               case[3] = { `Set{ {v}, {body} } }
            end
            local m = match_builder { tested_term_seq, cases }
            return `Stat{ { `Local{{v}}; m }, v }
         end } } }

function bind (x)
   local patterns, values = unpack(x)

   -------------------------------------------------------------------
   -- Generate pattern code: "bind vars = vals" translates to:
   -- do
   --   pattern matching code, goto 'fail' on mismatch
   --   goto 'success'
   --   label 'fail': error "..."
   --   label success
   -- end
   -- vars is the set of variables used by the pattern
   -------------------------------------------------------------------
   local code, vars do
      local match_cfg = {
         on_failure = mlp.gensym 'mismatch' [1],
         locals = { },
         code = { } }
      pattern_seq_builder(patterns, values, match_cfg)
      local on_success = mlp.gensym 'on_success' [1]
      code = {
         match_cfg.code;
         `Goto{ on_success };
         `Label{ match_cfg.on_failure };
         +{error "bind error"};
         `Label{ on_success } }
      vars = match_cfg.locals
   end

   -------------------------------------------------------------------
   -- variables that actually appear in the pattern:
   -------------------------------------------------------------------
   local vars_in_pattern do
      vars_in_pattern = { }
      local walk_cfg = { id = { } }
      function walk_cfg.id.free(v) vars_in_pattern[v[1]]=true end
      walk_id.expr_list(walk_cfg, patterns)
   end

   -------------------------------------------------------------------
   -- temp variables that are generated for destructuring,
   -- but aren't explicitly typed by the user. These must be made
   -- local.
   -------------------------------------------------------------------
   local vars_not_in_pattern do
      vars_not_in_pattern = { }
      for k in keys(vars) do
         if not vars_in_pattern[k] then
            vars_not_in_pattern[k] = true
         end
      end
   end

   -------------------------------------------------------------------
   -- Declare the temp variables as local to the statement.
   -------------------------------------------------------------------
   if next(vars_not_in_pattern) then
      local loc = { }
      for k in keys (vars_not_in_pattern) do
         table.insert (loc, `Id{k})
      end
      table.insert (code, 1, `Local{ loc, { } })
   end

   -------------------------------------------------------------------
   -- Transform the set of pattern variable names into a list of `Id{}
   -------------------------------------------------------------------
   local decl_list do
      decl_list = { }
      for k in keys (vars_in_pattern) do
         table.insert (decl_list, `Id{k})
      end
   end

   return code, decl_list
end

function local_bind(x)
   local code, vars = bind (x)
   return { `Local{ vars, { } }; code }
end

function non_local_bind(x)
   local code, _ = bind (x)
   code.tag = 'Do'
   return code
end

----------------------------------------------------------------------
-- Syntax front-end
----------------------------------------------------------------------
mlp.lexer:add 'bind'

----------------------------------------------------------------------
-- bind patterns = vars
----------------------------------------------------------------------
mlp.stat:add{ 'bind', mlp.expr_list, '=', mlp.expr_list,
   builder = non_local_bind }

----------------------------------------------------------------------
-- local bind patterns = vars
-- Some monkey-patching of "local ..." must take place
----------------------------------------------------------------------
mlp.stat:get'local'[2]:add{ 'bind', mlp.expr_list, '=', mlp.expr_list,
   builder = local_bind }
