Module:User:Cscott/mlua

return (function()
local builders = {}
local function register(name, f)
  builders[name] = f
end
register('llpeg.lpegrex', function() return require [[Module:User:Cscott/lpegrex]] end)

register('mlua.lua', function(myrequire)
--[[
This grammar is based on Lua 5.4
As seen in https://www.lua.org/manual/5.4/manual.html#9
]]
local Grammar = [==[
chunk         <-- SHEBANG? SKIP Block (!.)^UnexpectedSyntax

Block         <== ( Label / Return / Break / Goto / Do / While / Repeat / If / ForNum / ForIn
                  / FuncDef / FuncDecl / VarDecl / Assign / call / `;`)*
Label         <== `::` @NAME @`::`
Return        <== `return` exprlist?
Break         <== `break`
Goto          <== `goto` @NAME
Do            <== `do` Block p2 @`end`
While         <== `while` @expr p2 @`do` Block p3 @`end`
Repeat        <== `repeat` Block p2 @`until` @expr
If            <== `if` @expr p2 @`then` Block (`elseif` @expr @`then` Block)* (`else` Block)? p3 @`end`
ForNum        <== `for` Id `=` @expr @`,` @expr ((`,` @expr) / $false) p2 (ForWith / $false) p3 @ForBody
ForIn         <== `for` @idlist `in` @exprlist p2 (ForWith / $false) p3 @ForBody
ForWith       <== `with` @Id @`,` @Id
ForBody       <== `do` Block p2 (`then` Block p3 / $false) (`else` Block p4 / $false) @`end`
FuncDef       <== `function` @funcname funcbody
FuncDecl      <== `local` `function` @Id funcbody
VarDecl       <== `local` @iddecllist (p2 `=` @exprlist)?
Assign        <== varlist p2 `=` @exprlist

Number        <== NUMBER->tonumber SKIP
String        <== STRING SKIP
Boolean       <== `false`->tofalse / `true`->totrue
Nil           <== `nil`
Varargs       <== `...`
Id            <== NAME
IdDecl        <== NAME (`<` @NAME @`>`)?
Function      <== `function` $false funcbody
Table         <== `{` (field (fieldsep field)* fieldsep?)? p2 @`}`
Paren         <== `(` @expr p2 @`)`
Pair          <== `[` @expr @`]` p2 @`=` @expr / NAME p2 `=` @expr

Call          <== $false callargs
CallMethod    <== `:` @NAME @callargs
DotIndex      <== `.` @NAME
ColonIndex    <== `:` @NAME
KeyIndex      <== `[` @expr p2 @`]`

indexsuffix   <-- DotIndex / KeyIndex
callsuffix    <-- Call / CallMethod

p2            <-- {:p2:{}:}
p3            <-- {:p3:{}:}
p4            <-- {:p4:{}:}

var           <-- (exprprimary (callsuffix+ indexsuffix / indexsuffix)+)~>rfoldright / Id
call          <-- (exprprimary (indexsuffix+ callsuffix / callsuffix)+)~>rfoldright
exprsuffixed  <-- (exprprimary (indexsuffix / callsuffix)*)~>rfoldright
funcname      <-- (Id DotIndex* ColonIndex?)~>rfoldright

funcbody      <-- @`(` funcargs p2 @`)` Block p3 @`end`
field         <-- Pair / expr
fieldsep      <-- `,` / `;`

callargs      <-| `(` (expr (`,` @expr)*)? p2 @`)` / Table / String
idlist        <-| Id (`,` @Id)*
iddecllist    <-| IdDecl (`,` @IdDecl)*
funcargs      <-| (Id (`,` Id)* (`,` Varargs)? / Varargs)?
exprlist      <-| expr (`,` @expr)*
varlist       <-| var (`,` @var)*

opor     :BinaryOp <== `or`->'or' @exprand
opand    :BinaryOp <== `and`->'and' @exprcmp
opcmp    :BinaryOp <== (`==`->'eq' / `~=`->'ne' / `<=`->'le' / `>=`->'ge' / `<`->'lt' / `>`->'gt') @exprbor
opbor    :BinaryOp <== `|`->'bor' @exprbxor
opbxor   :BinaryOp <== `~`->'bxor' @exprband
opband   :BinaryOp <== `&`->'band' @exprbshift
opbshift :BinaryOp <== (`<<`->'shl' / `>>`->'shr') @exprconcat
opconcat :BinaryOp <== `..`->'concat' @exprconcat
oparit   :BinaryOp <== (`+`->'add' / `-`->'sub') @exprfact
opfact   :BinaryOp <== (`*`->'mul' / `//`->'idiv' / `/`->'div' / `%`->'mod') @exprunary
oppow    :BinaryOp <== `^`->'pow' @exprunary
opunary  :UnaryOp  <== (`not`->'not' / `#`->'len' / `-`->'unm' / `~`->'bnot') @exprunary

expr          <-- expror
expror        <-- (exprand opor*)~>foldleft
exprand       <-- (exprcmp opand*)~>foldleft
exprcmp       <-- (exprbor opcmp*)~>foldleft
exprbor       <-- (exprbxor opbor*)~>foldleft
exprbxor      <-- (exprband opbxor*)~>foldleft
exprband      <-- (exprbshift opband*)~>foldleft
exprbshift    <-- (exprconcat opbshift*)~>foldleft
exprconcat    <-- (exprarit opconcat*)~>foldleft
exprarit      <-- (exprfact oparit*)~>foldleft
exprfact      <-- (exprunary opfact*)~>foldleft
exprunary     <-- opunary / exprpow
exprpow       <-- (exprsimple oppow*)~>foldleft
exprsimple    <-- Nil / Boolean / Number / String / Varargs / Function / Table / exprsuffixed
exprprimary   <-- Id / Paren

STRING        <-- STRING_SHRT / STRING_LONG
STRING_LONG   <-- {:LONG_OPEN {LONG_CONTENT} @LONG_CLOSE:}
STRING_SHRT   <-- {:QUOTE_OPEN {~QUOTE_CONTENT~} @QUOTE_CLOSE:}
QUOTE_OPEN    <-- {:qe: ['"] :} / '«' {:qe: '' -> '»' :} / '‹' {:qe: '' -> '›' :}
QUOTE_CONTENT <-- (ESCAPE_SEQ / !(QUOTE_CLOSE / LINEBREAK) .)*
QUOTE_CLOSE   <-- =qe
ESCAPE_SEQ    <-- '\'->'' @ESCAPE
ESCAPE        <-- [\'"»] /
                  ('n' $10 / 't' $9 / 'r' $13 / 'a' $7 / 'b' $8 / 'v' $11 / 'f' $12)->tochar /
                  ('x' {HEX_DIGIT^2} $16)->tochar /
                  ('u' '{' {HEX_DIGIT^+1} '}' $16)->toutf8char /
                  ('z' SPACE*)->'' /
                  (DEC_DIGIT DEC_DIGIT^-1 !DEC_DIGIT / [012] DEC_DIGIT^2)->tochar /
                  (LINEBREAK $10)->tochar

NUMBER        <-- {HEX_NUMBER / DEC_NUMBER}
HEX_NUMBER    <-- '0' [xX] @HEX_PREFIX ([pP] @EXP_DIGITS)?
DEC_NUMBER    <-- DEC_PREFIX ([eE] @EXP_DIGITS)?
HEX_PREFIX    <-- HEX_DIGIT+ ('.' HEX_DIGIT*)? / '.' HEX_DIGIT+
DEC_PREFIX    <-- DEC_DIGIT+ ('.' DEC_DIGIT*)? / '.' DEC_DIGIT+
EXP_DIGITS    <-- [+-]? DEC_DIGIT+

COMMENT       <-- '--' (COMMENT_LONG / COMMENT_SHRT)
COMMENT_LONG  <-- (LONG_OPEN LONG_CONTENT @LONG_CLOSE)->0
COMMENT_SHRT  <-- (!LINEBREAK .)*

LONG_CONTENT  <-- (!LONG_CLOSE .)*
LONG_OPEN     <-- '[' {:eq: '='*:} '[' LINEBREAK?
LONG_CLOSE    <-- ']' =eq ']'

NAME          <-- !KEYWORD {NAME_PREFIX NAME_SUFFIX?} SKIP
-- We should really accept a proper unicode set here for name characters,
-- but for the time being hack in some ISO-8859-1 characters
NAME_PREFIX   <-- [_a-zA-ZÀ-ÿ]
NAME_SUFFIX   <-- [_a-zA-Z0-9À-ÿ]+

SHEBANG       <-- '#!' (!LINEBREAK .)* LINEBREAK?
SKIP          <-- (SPACE+ / COMMENT)*
LINEBREAK     <-- %cn %cr / %cr %cn / %cn / %cr
SPACE         <-- %sp
HEX_DIGIT     <-- [0-9a-fA-F]
DEC_DIGIT     <-- [0-9]
EXTRA_TOKENS  <-- `[[` `[=` `--` -- unused rule, here just to force defining these tokens
]==]

-- List of syntax errors
local SyntaxErrorLabels = {
  ["Expected_::"]         = "unclosed label, did you forget `::`?",
  ["Expected_)"]          = "unclosed parenthesis, did you forget a `)`?",
  ["Expected_>"]          = "unclosed angle bracket, did you forget a `>`?",
  ["Expected_]"]          = "unclosed square bracket, did you forget a `]`?",
  ["Expected_}"]          = "unclosed curly brace, did you forget a `}`?",
  ["Expected_LONG_CLOSE"] = "unclosed long string or comment, did your forget a ']]'?",
  ["Expected_QUOTE_CLOSE"]= "unclosed short string or comment, did your forget a quote?",
  ["Expected_("]          = "expected parenthesis token `(`",
  ["Expected_,"]          = "expected comma token `,`",
  ["Expected_="]          = "expected equals token `=`",
  ["Expected_callargs"]   = "expected arguments",
  ["Expected_expr"]       = "expected an expression",
  ["Expected_exprand"]    = "expected an expression after operator",
  ["Expected_exprcmp"]    = "expected an expression after operator",
  ["Expected_exprbor"]    = "expected an expression after operator",
  ["Expected_exprbxor"]   = "expected an expression after operator",
  ["Expected_exprband"]   = "expected an expression after operator",
  ["Expected_exprbshift"] = "expected an expression after operator",
  ["Expected_exprconcat"] = "expected an expression after operator",
  ["Expected_exprfact"]   = "expected an expression after operator",
  ["Expected_exprunary"]  = "expected an expression after operator",
  ["Expected_exprlist"]   = "expected expressions",
  ["Expected_funcname"]   = "expected a function name",
  ["Expected_do"]         = "expected `do` keyword to begin a statement block",
  ["Expected_end"]        = "expected `end` keyword to close a statement block",
  ["Expected_then"]       = "expected `then` keyword to begin a statement block",
  ["Expected_until"]      = "expected `until` keyword to close repeat statement",
  ["Expected_ESCAPE"]     = "malformed escape sequence",
  ["Expected_EXP_DIGITS"] = "malformed exponential number",
  ["Expected_HEX_PREFIX"] = "malformed hexadecimal number",
  ["Expected_Id"]         = "expected an identifier name",
  ["Expected_NAME"]       = "expected an identifier name",
  ["Expected_IdDecl"]     = "expected an identifier name declaration",
  ["Expected_iddecllist"] = "expected identifiers names declaration",
  ["Expected_idlist"]     = "expected identifiers names",
  ["Expected_var"]        = "expected a variable",
  ["Expected_ForBody"]    = "expected `with`, `do` or `if` keyword to begin a for-loop body",
  ["UnexpectedSyntax"]    = "unexpected syntax",
}

-- Compile grammar
local lpegrex = myrequire('llpeg.lpegrex')

local function make_parse(defs)
   local patt = lpegrex.compile(Grammar, defs)

   -- Parse Lua source into an AST.
   local function parse(source, name)
      local ast, errlabel, errpos = patt:match(source)
      if not ast then
         name = name or '<source>'
         local lineno, colno, line = lpegrex.calcline(source, errpos)
         local colhelp = string.rep(' ', colno-1)..'^'
         local errmsg = SyntaxErrorLabels[errlabel] or errlabel
         error('syntax error: '..name..':'..lineno..':'..colno..': '..errmsg..
               '\n'..line..'\n'..colhelp)
      end
      return ast
   end
   return parse
end

return {
   make_parse = make_parse,
   --parse = make_parse(),
}

end)

register('mlua.node', function(myrequire)
-- Parent class for expression nodes
local Expr = {}
Expr.__index = Expr
-- Parent class for literal expression nodes
local LiteralExpr = {}
LiteralExpr.__index = LiteralExpr
-- Parent class for statement nodes
local Stmt = {}
Stmt.__index = Stmt
-- Parent class for nodes which encode syntax (not an expression or statement)
local Syntax = {}
Syntax.__index = Syntax

-- Define node types and map to their parent class
local Node = {
  Block = Stmt,
  Label = Stmt,
  Return = Stmt,
  Break = Stmt,
  Goto = Stmt,
  Do = Stmt,
  While = Stmt,
  Repeat = Stmt,
  If = Stmt,
  ForNum = Stmt,
  ForIn = Stmt,
  ForWith = Syntax,
  ForBody = Syntax,
  FuncDef = Stmt,
  FuncDecl = Stmt,
  Function = Expr,
  VarDecl = Stmt,
  Assign = Stmt,
  Number = LiteralExpr,
  String = LiteralExpr,
  Boolean = LiteralExpr,
  Nil = LiteralExpr,
  Varargs = Expr,
  Id = Expr,
  IdDecl = Syntax,
  Table = Expr,
  Paren = Expr,
  Pair = Syntax,
  Call = Expr,
  CallMethod = Expr,
  DotIndex = Expr,
  ColonIndex = Syntax,
  KeyIndex = Expr,
  BinaryOp = Expr,
  UnaryOp = Expr,
}
Node.__index = Node
setmetatable(Syntax, Node)
setmetatable(Stmt, Node)
setmetatable(Expr, Node)
setmetatable(LiteralExpr, Expr)

for tag, parent in pairs(Node) do
  local val = {}
  val.__index = val
  setmetatable(val, parent)
  val.__tostring = function(self)
    local result = { tag, '{ ' }
    for i,v in ipairs(self) do
      if type(v) ~= 'table' or v.tag ~= nil then
        table.insert(result, tostring(v))
      else
        table.insert(result, '{ ')
        for j,vv in ipairs(v) do
          table.insert(result, tostring(vv))
          if v[j+1] ~= nil then
            table.insert(result, ", ")
          end
        end
        table.insert(result, ' }')
      end
      if self[i+1] ~= nil then
        table.insert(result, ", ")
      end
    end
    table.insert(result, ' }')
    return table.concat(result)
  end
  Node[tag] = val
end
Node.__index = Node
Node.LiteralExpr = LiteralExpr
Node.Expr = Expr
Node.Syntax = Syntax
Node.Stmt = Stmt

local function make_node(tag, node)
  setmetatable(node, Node[tag])
  node.tag = tag
  return node
end

return {
  Node = Node,
  make_node = make_node,
}

end)

register('advent.compat', function() return require [[Module:User:Cscott/compat]] end)

register('mlua.define', function(myrequire)
-- Human-friendly names for functions, for easier debugging/tracing.
local M = {}

local function_name_registry = {}

function M.register_fname(name, f)
   assert(type(name) == "string")
   assert(type(f) == "function")
   function_name_registry[f] = name
end

local function report_ferror(f, msg)
   local fname = function_name_registry[f]
   if fname ~= nil then
      msg = fname .. ": " .. msg
   end
   error(msg)
end

-- helper for visitor pattern definitions
function M.define(dispatch, which, f)
   for _,v in pairs(which) do
      assert(v ~= nil) -- catch typos
      dispatch[v] = f
   end
end

function M.define_ast(obj, funcname, tbl)
  for keys,func in pairs(tbl) do
      if type(keys) ~= "table" then
         keys = { keys }
      end
      for _,v in pairs(keys) do
        obj[v][funcname] = func
      end
  end
end

function M.define_ast_visitor(tbl)
   local dispatch = {}
   for keys,func in pairs(tbl) do
      if type(keys) ~= "table" then
         keys = { keys }
      end
      M.define(dispatch, keys, func)
   end
   local visit
   visit = function(node, ...)
      if node == nil then report_ferror(visit, "nil node") end
      local a = dispatch["assert"]
      if a ~= nil then a(node, ...) end -- assert preconditions
      local f = dispatch[node.tag]
      if f ~= nil then return f(node, ...) end
      f = dispatch.default
      if f == nil then
         report_ferror(visit, "no default for " .. node.tag)
      end
      return f(node, ...)
   end
   return visit
end

return M

end)

register('mlua.eval', function(myrequire)
local L = myrequire('mlua.node').Node
local make_node = myrequire('mlua.node').make_node
local compat = myrequire('advent.compat')
local define = myrequire('mlua.define').define
local hasBit32, bit32 = pcall(require, 'bit32')
if not hasBit32 then bit32 = {} end

--[[ debugging options ]]--
-- when true, show char position of each statement executed
local TRACE=false
-- set false when testing to ensure general case of multires args handled
-- properly; the optimizations might otherwise cause the general case not
-- to be reached during testing.
local MULTIRES_OPT=true

local function ripairs(val)
  local i = 1
  while val[i] ~= nil do
    i = i + 1
  end
  local f = function(_, i)
    i = i - 1
    if i == 0 then return nil end
    return i, val[i]
  end
  return f, nil, i
end

local function makePushEnv(cont)
  return function(env) return cont({ parent=env }) end
end

local function makePopEnv(cont)
  return function(env) return cont(env.parent) end
end

local State = {}
State.__index = State
function State:new(parent)
  local s = setmetatable({ labels={}, locals={}, oldLocals={}, localCount = 0, parent = parent }, self)
  if parent == nil then
     s.depth = 0
  else
     s.depth = 1 + parent.depth
  end
  if parent == nil then
    s:defineLocal("_ENV") -- first top-level local is always for _ENV
  elseif parent.breakCont ~= nil then
    s.breakCont = makePopEnv(parent.breakCont)
  end
  return s
end

function State:newBlockState()
  return State:new(self)
end

function State:pushLabel(name, cont)
  self.labels[name] = cont
end

function State:lookupLabel(name)
  local f = nil -- cache
  local labels = self.labels
  return function(env)
    if f == nil then
      -- deferred lookup of label, since it isn't yet defined when the
      -- goto is compiled.  But cache the result to reuse it next time.
      f = labels[name]
      labels = nil -- free up some memory
    end
    return f(env)
  end
end

function State:setBreak(cont)
  local old = self.breakCont
  self.breakCont = cont
end

function State:lookupLocal(name)
  local i = 0
  local s = self
  while s ~= nil do
    local id = s.locals[name]
    if id ~= nil then return i, id end
    i = i + 1
    s = s.parent
  end
  return nil, nil -- not found
end

function State:defineInvisibleLocal(undef)
  if undef == nil then
    self.localCount = self.localCount + 1
  else
    self.localCount = self.localCount - 1
  end
  return self.localCount
end

function State:defineLocal(name, undef)
  if undef == nil then
    self.localCount = self.localCount + 1
    self.oldLocals[self.localCount] = self.locals[name] -- usually nil
    self.locals[name] = self.localCount
  else
    assert(self.locals[name] == self.localCount)
    self.locals[name] = self.oldLocals[self.localCount] -- usually nil
    self.localCount = self.localCount - 1
  end
  return self.localCount
end

local Env = {}
Env.__index = Env
function Env:new()
  return setmetatable({}, self)
end


function L:defineLocals()
  error("missing defineLocals case for "..self.tag)
end
function L.Stmt:defineLocals()
  -- we don't recurse into inner blocks; hence do nothing by default
end
function L.Expr:defineLocals()
  -- expressions don't have declarations
end
function L.ForWith:defineLocals(state, undef)
  if undef == nil then
    for _,v in ipairs(self) do v:defineLocals(state, undef) end
    return state.locals[self[1][1]], state.locals[self[2][1]]
  else
    for _,v in ripairs(self) do v:defineLocals(state, undef) end
  end
end
function L.FuncDecl:defineLocals(state, undef)
  self[1]:defineLocals(state, undef)
end
function L.VarDecl:defineLocals(state, undef)
  if undef == nil then
    for _,v in ipairs(self[1]) do v:defineLocals(state, undef) end
  else
    for _,v in ripairs(self[1]) do v:defineLocals(state, undef) end
  end
end
function L.Id:defineLocals(state, undef)
  state:defineLocal(self[1], undef)
end
function L.IdDecl:defineLocals(state, undef)
  local name,attrib = self[1], self[2]
  -- if attrib ~= nil then error("attributes unimplemented") end
  state:defineLocal(name, undef)
end

function L:compile(state, cont)
  error("Missing compiler for "..self.tag)
end

function L:compileExpr(state)
  error("Missing expression compiler for "..self.tag)
end

function L:compileLhs(state)
  error("Missing left-hand-side compiler for "..self.tag)
end

local withNewBlock = function(f)
  return function(self, state, cont)
    state = state:newBlockState()
    return makePushEnv(f(self, state, makePopEnv(cont)))
  end
end

function L.Block:compileInSameBlock(state, cont)
  -- define all the locals in a forward pass
  for _,node in ipairs(self) do
    node:defineLocals(state)
  end
  -- XXX create continuation that clears/closes all these locals
  -- create continuations in a backward pass
  for _,node in ripairs(self) do
    cont = node:compile(state, cont)
    node:defineLocals(state, 'undef')
    if TRACE then
      local pos = node.pos
      local cont2 = cont
      cont = function(env)
        if _G.mw and _G.mw.log then
          _G.mw.log("Pos "..pos)
        else
          print("Pos", pos)
        end
        return cont2(env)
      end
    end
  end
  return cont
end
L.Block.compile = withNewBlock(L.Block.compileInSameBlock)

function L.Label:compile(state, cont)
  state.pushLabel(self[1], cont)
  return cont
end

function L.Return:compile(state, cont)
  if self[1] == nil or #self[1] == 0 then
    return function() return end -- ignore continuation, finally return
  end
  local t = {}
  for _,v in ipairs(self[1]) do
    table.insert(t, v:compileExpr(state))
  end
  -- special case for tail call
  if #t == 1 then
    -- tail call to expression evaluator, which ought to be a tail call to
    -- the function being called; see Call:compileExpr()
    return t[1]
  elseif #t == 2 and MULTIRES_OPT then
    -- fast path, uses lua itself to handle multires case of t2
    local t1, t2 = t[1], t[2]
    return function(env) return t1(env), t2(env) end
  end
  return function(env)
    local r,n = {}, #t
    for i=1,n-1 do
      r[i] = t[i](env) -- evaluate each expression in order
    end
    -- handle multires expressions (n==0 case already handled above)
    local last = compat.pack(t[n](env))
    n = n - 1 -- we haven't stored the last item yet
    for i=1,last.n do
      n = n + 1
      r[n] = last[i]
    end
    -- we use n instead of #r in case some of the values are null
    return compat.unpack(r, 1, n) -- ignore continuation, return
  end
end

function L.Break:compile(state, cont)
  -- ignore continuation, execute from 'breakCont'
  return state.breakCont
end

function L.Goto:compile(state, cont)
  return state.lookupLabel(self[1]) -- use a different continuation
end

function L.Do:compile(state, cont)
  return self[1]:compile(state, cont)
end

function L.While:compile(state, cont)
  local expr = self[1]:compileExpr(state)
  local body
  local loop = function(env)
    if expr(env) then return body(env) end
    return cont(env)
  end
  local oldBreak = state:setBreak(cont)
  body = self[2]:compile(state, loop)
  state:setBreak(oldBreak)
  return loop
end

function L.Repeat:compile(state, cont)
  -- the test is executed in the same new block as the body

  local condAst = make_node('If', { self[2], make_node("Break", {}) })
  local oldBody = self[1]
  assert(oldBody.tag == 'Block')
  local newBody = make_node('Block', { pos=oldBody.pos, endpos=oldBody.endpos })
  for i,v in ipairs(oldBody) do
    newBody[i] = v
  end
  newBody[#newBody+1] = condAst

  local body
  local loop = function(env) return body(env) end
  local oldBreak = state:setBreak(cont)
  body = newBody:compile(state, loop)
  state:setBreak(oldBreak)
  return body
end

function L.If:compile(state, cont)
  -- 1,2 = if/then
  -- 1,2,3 = if/then/else
  -- 1,2,3,4 = if/then/elseif/then
  -- 1,2,3,4,5 = if/then/elseif/then/else
  local n = 1
  while self[n] ~= nil do
    n = n + 2
  end
  local ifRest = cont
  for i=n-2,1,-2 do
    if self[i+1] == nil then -- final else clause
      ifRest = self[i]:compile(state, cont)
    else
      local test = self[i]:compileExpr(state)
      local thenStmt = self[i+1]:compile(state, cont)
      local rest = ifRest;
      ifRest = function(env)
        if test(env) then return thenStmt(env) else return rest(env) end
      end
    end
  end
  return ifRest
end

-- If only 'then' is present, it executes after any normal completion
-- Otherwise, if 'else' is present, then 'then' only executes after
-- one (or more) successful iterations of the loop body
function makeFinalize(thenAst, elseAst, state, cont, first, idlist, idlistShadow)
  if thenAst == false and elseAst == false then
    return cont
  end
  local thenAssign = make_node("VarDecl", { idlist, idlistShadow })
  local elseAssign = make_node("VarDecl", { idlist }) -- will nil out
  -- only declare the idlist variables in a then block (including a
  -- combined then/else block)
  local thenFunc, elseFunc
  if thenAst ~= false then
    local thenState = state:newBlockState()
    thenAssign:defineLocals(thenState)
    local thenFuncCont = thenAst:compileInSameBlock(thenState, makePopEnv(cont))
    thenFunc = thenAssign:compile(thenState, thenFuncCont)
    -- this else func will be overwritten if actual else clause present
    elseFunc = elseAssign:compile(thenState, thenFuncCont)
  end
  if elseAst ~= false then
    local elseState = state:newBlockState()
    elseFunc = elseAst:compileInSameBlock(elseState, makePopEnv(cont))
  end
  return function(env)
    local nenv = { parent=env }
    if env[first] == true then
      return elseFunc(nenv)
    elseif thenFunc ~= nil then
      return thenFunc(nenv)
    else
      return cont(env)
    end
  end
end

function L.ForNum:compile(state, cont)
  local name,start,stop,optStep,optWith,forBody = self[1],self[2],self[3],self[4],self[5],self[6]
  local bodyAst, thenAst, elseAst = forBody[1], forBody[2], forBody[3]
  local var, varExpr = state:defineInvisibleLocal(), start:compileExpr(state)
  local limit, limitExpr = state:defineInvisibleLocal(), stop:compileExpr(state)
  local step, stepExpr = state:defineInvisibleLocal(), function() return 1 end
  local first = state:defineInvisibleLocal()
  local last = state:defineInvisibleLocal()
  local lastVarAst = make_node("Id", { "0var" }) -- see ForIn below
  local lastVar = state:defineLocal("0var")
  if optStep ~= false then
    stepExpr = optStep:compileExpr(state)
  end
  local nstate = state:newBlockState()
  local id = nstate:defineLocal(name[1])
  local firstId, lastId
  if optWith ~= false then
     firstId, lastId = optWith:defineLocals(nstate)
  end

  local finalize = makeFinalize(thenAst, elseAst, state, cont, first,
                                { name }, { lastVarAst })

  local body
  local loop = function(env)
    if (env[step] > 0 and env[var] <= env[limit]) or (env[step] <= 0 and env[var] >= env[limit]) then
      local nenv = { parent=env }
      nenv[id] = env[var]
      if env[step] > 0 then
        env[last] = env[var] + env[step] > env[limit]
      else
        env[last] = env[var] + env[step] < env[limit]
      end
      if firstId ~= nil then nenv[firstId] = env[first] end
      if lastId ~= nil then nenv[lastId] = env[last] end
      return body(nenv)
    end
    return finalize(env)
  end
  local init = function(env)
    env[var] = tonumber(varExpr(env))
    env[limit] = tonumber(limitExpr(env))
    env[step] = tonumber(stepExpr(env))
    env[first] = true
    if not (env[var] and env[limit] and env[step]) then error() end
    return loop(env)
  end
  local incr = function(env)
    env[lastVar] = env[var]
    env[var] = env[var] + env[step]
    env[first] = false
    return loop(env)
  end
  local oldBreak = nstate:setBreak(makePopEnv(cont))
  body = bodyAst:compileInSameBlock(nstate, makePopEnv(incr))
  nstate:setBreak(oldBreak)
  -- okay, now pop all those local vars off
  if optWith ~= false then
     optWith:defineLocals(nstate, "undef")
  end
  nstate:defineLocal(name[1], "undef")
  state:defineLocal("0var", "undef") -- lastVar
  state:defineInvisibleLocal("undef") -- last
  state:defineInvisibleLocal("undef") -- first
  state:defineInvisibleLocal("undef") -- step
  state:defineInvisibleLocal("undef") -- limit
  state:defineInvisibleLocal("undef") -- var
  return init
end

--[[
  To guide understanding of this method, here is the equivalent code for
  for-in loop execution:

  ** WITHOUT A WITH CLAUSE **
do
  -- init
  local f', s', var_1', cl' = explist -- initAst / initFunc
  first' = true
  -- goto loop
  while true do
    -- loop:
    local var_1, ···, var_n = f'(s', var_1') -- loopStart
    if var_1 == nil then break end
    var_1', ..., var_n' = var_1, ..., var_n -- save for then clause
    block
    -- goto loop
  end
end

  ** WITH A WITH CLAUSE (maintaining 'last') **
do
  -- init
  local f', s', var_1', cl' = explist -- initAst / initFunc
  local var_1', ···, var_n' = f'(s', var_1')
  local first' = true
  local last' = (var_1' == nil)
  -- goto loop
  -- loop:
  while not last' do
    first = first'
    first' = false
    var_1, ..., var_n = var_1', ..., var_n'
    var_1', ···, var_n' = f'(s', var_1')
    last' = (var_1' == nil)
    last = last'
    if last' then
      var_1', ···, var_n' = var_1, ..., var_n -- save for 'then' clause
    end
    block
    -- goto loop
  end
end
]]--

function L.ForIn:compile(state, cont)
  local nstate = state:newBlockState() -- inner block
  local idlist,exprlist,optWith,forBody = self[1],self[2],self[3],self[4]
  local bodyAst, thenAst, elseAst = forBody[1], forBody[2], forBody[3]
  -- this is a "shadow" copy of the idlist, used for 'last' and then clauses
  local idlist2 = {}
  for i,_ in ipairs(idlist) do
    local node = make_node("Id", { "0" .. i })
    table.insert(idlist2, node)
  end
  -- use names starting with a number to ensure they don't conflict w/
  -- any user locals (this is an alternative to :defineInvisibleLocal()
  local fAst = make_node("Id", { "0f" })
  local sAst = make_node("Id", { "0s" })
  local varAst = idlist2[1]
  local clAst = make_node("Id", { "0cl" })
  -- this is used to save iteration variables to their shadows for the then block
  local assignBackAst = make_node("Assign", { idlist2, idlist })

  local loopidlist = idlist
  local assignAst
  if optWith ~= false then
    assignAst = make_node("Assign", { idlist, idlist2 })
    loopidlist = idlist2
  end

  local declareAst = make_node("VarDecl", {
    -- declare all of our shadow variables
    compat.move(idlist2, 1, #idlist2, 4, { fAst, sAst, clAst})
  })
  local innerDeclareAst = make_node("VarDecl", { idlist } )
  local initAst = make_node("Assign", { { fAst, sAst, varAst, clAst }, exprlist })
  local loopStart = make_node("Assign", { loopidlist, { make_node("Call", { false, { sAst, varAst }, fAst }) } })
  -- outer block:
  declareAst:defineLocals(state)
  local _,varId = state:lookupLocal(idlist2[1][1])
  local _, clId = state:lookupLocal("0cl")
  -- inner block:
  innerDeclareAst:defineLocals(nstate)
  local _,var1Id = nstate:lookupLocal(idlist[1][1])
  -- optional 'with'
  local first, last, firstId, lastId
  first = state:defineLocal("0first")
  if optWith ~= false then
    last = state:defineLocal("0last")
    firstId, lastId = optWith:defineLocals(nstate)
  end

  local finalize = makeFinalize(thenAst, elseAst, state, cont, first, idlist, idlist2)

  -- loop starts in outer block and enters inner block
  -- body is executed in inner block
  -- finalize is executed in outer block, so must pop first when breaking from inner
  local body, loop, init, saveAndExecBody
  if optWith == false then
    loop = makePushEnv(loopStart:compile(nstate, function(env)
      if env[var1Id] == nil then return finalize(env.parent) end -- break
      env.parent[first] = false
      return saveAndExecBody(env)
    end))
  else
    local loopMid
    loop = makePushEnv(function(env)
      if env.parent[last] then return finalize(env.parent) end -- break
      env[firstId] = env.parent[first]
      env.parent[first] = false
      return loopMid(env)
    end)
    loopMid = assignAst:compile(nstate, loopStart:compile(nstate, function(env)
      env.parent[last] = (env.parent[varId] == nil)
      env[lastId] = env.parent[last]
      if env[lastId] then
        return saveAndExecBody(env)
      else
        return body(env)
      end
    end))
  end
  saveAndExecBody = assignBackAst:compile(nstate, function(env)
    return body(env)
  end)

  -- init is executed in the outer block
  local function checkClosure(cont)
    return function(env)
      if env[clId] ~= nil then
        error("Closures not yet supported")
      end
      env[first] = true
      return cont(env)
    end
  end
  local init
  if optWith == false then
    init = initAst:compile(
      state, checkClosure(loop)
    )
  else
    init = initAst:compile(
      state, checkClosure(loopStart:compile(state, function(env)
      env[last] = (env[varId] == nil)
      return loop(env)
    end)))
  end

  local oldBreak = nstate:setBreak(makePopEnv(cont))
  body = bodyAst:compileInSameBlock(nstate, makePopEnv(loop))
  nstate:setBreak(oldBreak)
  -- okay, now pop all those local vars off
  if optWith ~= false then
    state:defineLocal("0last", "undef")
    optWith:defineLocals(nstate, "undef")
  end
  state:defineLocal("0first", "undef")
  innerDeclareAst:defineLocals(nstate, "undef")
  declareAst:defineLocals(state, "undef")
  return init
end

function L.FuncDecl:compile(state, cont)
  local level, id = state:lookupLocal(self[1][1])
  local func = L.Function.compileExpr(self, state)
  assert(level == 0)
  return function(env)
    env[id] = func(env)
    return cont(env)
  end
end

function L.FuncDef:compile(state, cont)
  local name, args, body = self[1], self[2], self[3]
  local lhs, func
  local addSelf = false
  if name.tag ~= "ColonIndex" then
    lhs = name:compileLhs(state)
    func = L.Function.compileExpr(self, state)
  else
    -- compile it as a dot index
    lhs = L.DotIndex.compileLhs(name, state)
    -- but add an implicit 'self' argument
    local nargs = { make_node("Id", { "self" } ) }
    for i,v in ipairs(args) do
      nargs[i+1] = v
    end
    func = make_node("Function", { false, nargs, body }):compileExpr(state)
  end
  return function(env)
    lhs(env, func(env))
    return cont(env)
  end
end

function L.VarDecl:compile(state, cont)
  local iddecllist, exprlist = self[1], (self[2] or {})
  local lhs = {}
  for i,v in ipairs(iddecllist) do
    local name,attrib = v[1],v[2]
    local level, id = state:lookupLocal(name)
    assert(level == 0)
    table.insert(lhs, id)
  end
  local rhs = {}
  for i,v in ipairs(exprlist) do
    table.insert(rhs, v:compileExpr(state))
  end
  local n = #exprlist
  if #lhs == 1 and #rhs == 1 then -- fast path
    local l,r = lhs[1], rhs[1]
    return function(env)
      env[l] = r(env)
      return cont(env)
    end
  end
  return function(env)
    -- evaluate all expressions on the right hand side
    local r = {}
    for i=1,#rhs-1 do
      r[i] = rhs[i](env)
    end
    -- "if the list of expressions ends with a function call, then all
    -- values returned by that call enter the list of values" thus:
    -- unpack last argument into remaining values
    if #rhs > 0 then
      local last = compat.pack(rhs[#rhs](env))
      for i=1,last.n do
        r[#rhs+i-1] = last[i]
      end
    end
    -- perform all assignments
    for i=1,#lhs do
      env[lhs[i]] = r[i]
    end
    return cont(env)
  end
end

function L.Assign:compile(state, cont)
  local varlist, exprlist = self[1], self[2]
  local rhs = {}
  for _,e in ipairs(exprlist) do
    table.insert(rhs, e:compileExpr(state))
  end
  local lhs = {}
  for _,v in ipairs(varlist) do
    table.insert(lhs, v:compileLhs(state))
  end
  if #rhs == 1 and #lhs == 1 then -- fast path
    local l,r = lhs[1], rhs[1]
    return function(env)
      l(env, r(env))
      return cont(env)
    end
  end
  return function(env)
    -- evaluate all expressions on the right hand side
    local r = {}
    for i=1,#rhs-1 do
      r[i] = rhs[i](env)
    end
    -- "if the list of expressions ends with a function call, then all
    -- values returned by that call enter the list of values" thus:
    -- unpack last argument into remaining values
    if #rhs > 0 then
      local last = compat.pack(rhs[#rhs](env))
      for i=1,last.n do
        r[#rhs+i-1] = last[i]
      end
    end
    -- perform all assignments
    for i=1,#lhs do
      lhs[i](env, r[i])
    end
    return cont(env)
  end
end

function L.Function:compileExpr(state)
  local args, body = self[2], self[3]
  local nargs = #args
  -- create a new state for this function
  local ns = state:newBlockState()
  -- define new local variables corresponding to the function arguments
  local seenVarargs = false
  for _,v in ipairs(args) do
    if v.tag == 'Id' and not seenVarargs then
      ns:defineLocal(v[1])
    elseif v.tag == 'Varargs' and not seenVarargs then
      seenVarargs = true
      ns:defineLocal("0varargs") -- hidden local
      nargs = nargs - 1
    else
      error("unknown argument type")
    end
  end
  -- compile body in this new state
  local f = body:compileInSameBlock(ns, function() return end)
  -- return a value!
  return function(env)
    return function(...)
      -- create a new environment with arguments appropriately set
      local nenv = { parent = env }
      for i=1,nargs do
        nenv[i] = select(i, ...)
      end
      if seenVarargs then
        nenv[nargs+1] = compat.pack(select(nargs+1, ...))
      end
      return f(nenv)
    end
  end
end

function L.Expr:compile(state, cont)
  -- evaluate expression for side effects, throw value away, invoke continuation
  local f = self:compileExpr(state)
  return function(env)
    f(env) -- throw result away
    return cont(env) -- continue with next statement
  end
end

function L.LiteralExpr:compileExpr(state)
  local n = self[1]
  return function() return n end
end

function L.Nil:compileExpr(state)
  return function() return nil end
end

function L.Varargs:compileExpr(state)
  local getter = make_node("Id", { "0varargs" }):compileExpr(state)
  return function(env)
    local varargs = getter(env)
    return compat.unpack(varargs, 1, varargs.n)
  end
end

function L.Id:compileLhs(state)
  local level, id = state:lookupLocal(self[1])
  if id ~= nil then
    if level == 0 then
      -- bound local variable in this environment
      return function(env, val) env[id] = val end
    else
      return function(env,val)
        local p = env
        for i=1,level do p = p.parent end
        p[id] = val
      end
    end
  end
  -- free name
  local level,_env = state:lookupLocal('_ENV')
  id = self[1]
  return function(env, val)
    local p = env
    for i=1,level do p = p.parent end
    p[_env][id] = val
  end
end

function L.Id:compileExpr(state)
  local level, id = state:lookupLocal(self[1])
  if id ~= nil then
    if level == 0 then
      -- bound local variable in this environment
      return function(env) return env[id] end
    else
      return function(env)
        local p = env
        for i=1,level do p = p.parent end
        return p[id]
      end
    end
  end
  -- free name
  local level,_env = state:lookupLocal('_ENV')
  id = self[1]
  return function(env)
    local p = env
    for i=1,level do p = p.parent end
    return p[_env][id]
  end
end

function L.Table:compileExpr(state)
  local i = 1
  local keys = {}
  for j,v in ipairs(self) do
    keys[j] = i
    if v.tag ~= 'Pair' then
      i = i + 1
    end
  end
  if #self == 0 then
    -- fast path, empty table
    return function() return {} end
  elseif #self == 1 and self[1].tag ~= 'Pair' and MULTIRES_OPT then
    -- fast path, let lua handle multires internally
    local f = self[1]:compileExpr(state)
    return function(env) return { f(env) } end
  end
  local initialize = function(env, t) return t end
  local last = true
  for j,v in ripairs(self) do
    initialize = v:compileTableInit(state, initialize, keys[j], last)
    last = false
  end
  return function(env)
    return initialize(env, {})
  end
end

function L.Pair:compileTableInit(state, initialize)
  local left, right = self[1], self[2]:compileExpr(state)
  if type(left) == 'string' then
    return function(env, t)
      t[left] = right(env)
      return initialize(env, t)
    end
  end
  left = left:compileExpr(state)
  return function(env, t)
    t[left(env)] = right(env)
    return initialize(env, t)
  end
end

function L.Expr:compileTableInit(state, initialize, idx, last)
  local f = self:compileExpr(state)
  if not last then
    return function(env, t)
      t[idx] = f(env)
      return initialize(env, t)
    end
  end
  -- multires expression as final table initializer
  return function(env, t)
    local r = compat.pack(f(env))
    for i=1,r.n do
      t[idx+i-1] = r[i]
    end
    return initialize(env, t)
  end
end

function L.Paren:compileExpr(state)
  -- this is not entirely a no-op, it down-selects multires expressions to
  -- a single result.
  local f = self[1]:compileExpr(state)
  return function(env)
    return (f(env)) -- select just a single value
  end
end

function L.Call:compileExpr(state)
  local _,args,name = self[1],self[2],self[3]
  local f = name:compileExpr(state)
  local t = {}
  for _,v in ipairs(args) do
    table.insert(t, v:compileExpr(state))
  end
  -- fast cases for small # of args; this lets lua handle the multires
  -- argument handling internally
  if #args == 0 then
    return function(env) return f(env)() end
  elseif #args == 1 and MULTIRES_OPT then
    local t1 = t[1]
    return function(env) return f(env)(t1(env)) end
  elseif #args == 2 and MULTIRES_OPT then
    local t1,t2 = t[1],t[2]
    return function(env) return f(env)(t1(env),t2(env)) end
  elseif #args == 3 and MULTIRES_OPT then
    local t1,t2,t3 = t[1],t[2],t[3]
    return function(env) return f(env)(t1(env),t2(env),t3(env)) end
  end
  -- ok, the slightly-slower case for an arbitrary # of arguments
  return function(env)
    local func = f(env)
    local args = {}
    local n = #t
    for i=1,n-1 do
      args[i] = t[i](env) -- evaluate each argument expression in order
    end
    -- handle multires expressions; the n==0 case was already handled above
    local last = compat.pack(t[n](env))
    n = n - 1 -- we haven't stored the last item yet
    for i=1,last.n do
      n = n + 1
      args[n] = last[i]
    end
    -- we use n instead of #r in case some of the values are null
    return func(compat.unpack(args, 1, n))
  end
end

function L.CallMethod:compileExpr(state)
  local method,args,recv = self[1],self[2],self[3]
  local f = recv:compileExpr(state)
  local t = {}
  for _,v in ipairs(args) do
    table.insert(t, v:compileExpr(state))
  end
  if #args == 0 then
    -- fast path for no arguments (except implicit receiver)
    return function(env)
      local recv = f(env)
      return recv[method](recv)
    end
  elseif #args == 1 and MULTIRES_OPT then
    -- fast path: let lua handle multires case
    local t1 = t[1]
    return function(env)
      local recv = f(env)
      return recv[method](recv, t1(env))
    end
  end
  -- slightly slower case to handle arbitrary # arguments
  return function(env)
    local recv = f(env)
    local func = recv[method]
    local args = { recv }
    local n = #t
    for i=1,n-1 do
      args[i + 1] = t[i](env) -- evaluate each argument expression in order
    end
    -- handle multires expressions (n==0 case already handled above)
    local last = compat.pack(t[n](env))
    n = n - 1 -- we haven't stored the last item yet
    for i=1,last.n do
      n = n + 1
      args[n + 1] = last[i]
    end
    -- we use n instead of #r in case some of the values are null
    return func(compat.unpack(args, 1, 1+n))
  end
end

function L.DotIndex:compileLhs(state)
  local left,right = self[2],self[1]
  left = left:compileExpr(state)
  return function(env, val)
    left(env)[right] = val
  end
end

function L.DotIndex:compileExpr(state)
  local left,right = self[2],self[1]
  left = left:compileExpr(state)
  return function(env)
    return left(env)[right]
  end
end

function L.KeyIndex:compileLhs(state)
  local left,right = self[2],self[1]
  left, right = left:compileExpr(state), right:compileExpr(state)
  return function(env, val)
    left(env)[right(env)] = val
  end
end

function L.KeyIndex:compileExpr(state)
  local left,right = self[2],self[1]
  left, right = left:compileExpr(state), right:compileExpr(state)
  return function(env)
    return left(env)[right(env)]
  end
end

local function trymt(name,f)
  return function(l,r)
    -- if both arguments are numbers, don't use a metamethod
    if type(l)=='number' and type(r)=='number' then return f(l,r) end
    local op = nil
    local mt = getmetatable(l)
    if mt ~= nil then op = mt[name] end
    if op == nil then
      mt = getmetatable(r)
      if mt ~= nil then op = mt[name] end
    end
    if op ~= nil then
      local result = op(l,r) -- adjusted to one value
      return result
    end
    return f(l,r)
  end
end

local optable = {
  ['or'] = function(l,r) return l or r end, -- no metamethod
  ['and'] = function(l,r) return l and r end, -- no metamethod
  eq = function(l,r) return l == r end,
  ne = function(l,r) return l ~= r end,
  le = function(l,r) return l <= r end,
  ge = function(l,r) return l >= r end,
  lt = function(l,r) return l < r end,
  gt = function(l,r) return l > r end,
  bor = trymt('__bor', bit32.bor),
  bxor = trymt('__bxor', bit32.bxor),
  band = trymt('__band', bit32.band),
  shl = trymt('__shl', bit32.lshift),
  shr = trymt('__shr', bit32.rshift),
  concat = function(l,r) return l .. r end,
  add = function(l,r) return l + r end,
  sub = function(l,r) return l - r end,
  mul = function(l,r) return l * r end,
  idiv = trymt('__idiv', function(l,r) return math.floor(l / r) end),
  div = function(l,r) return l / r end,
  mod = function(l,r) return l % r end,
  pow = function(l,r) return l ^ r end,
  ['not'] = function(r) return not r end,
  len = function(r)
    -- support for lua 5.4 __len metamethod on tables
    -- (lua 5.1 always used primitive length on tables)
    if type(r) == 'table' then
      local mt = getmetatable(r)
      if mt ~= nil then
        local len = mt.__len
        if len ~= nil then
          local l = len(r) -- adjust to one value
          return l
        end
      end
    end
    return #r
  end,
  unm = function(r) return -r end,
  bnot = trymt('__bnot', bit32.bnot),
}

-- This could be optimized: for example we could specialize for constants,
-- We could fold in the function call that evaluates l and r, etc.
function L.BinaryOp:compileExpr(state)
  local left,op,right = self[1],self[2],self[3]
  left, right = left:compileExpr(state), right:compileExpr(state)
  if op == 'or' then
    -- short cut evaluation
    return function(env)
      return left(env) or right(env)
    end
  elseif op == 'and' then
    -- short cut evaluation
    return function(env)
      return left(env) and right(env)
    end
  else
    op = optable[op]
    return function(env)
      return op(left(env), right(env))
    end
  end
end

function L.UnaryOp:compileExpr(state)
  local op,right = self[1],self[2]
  op, right = optable[op], right:compileExpr(state)
  return function(env)
    return op(right(env))
  end
end

local function compile(ast)
  local state = State:new()
  return ast:compile(state, function() return end)
end

local function eval(ast)
   -- xxx create an appropriate environment
  local GLOBALS = {
    ['assert']=assert, -- XXX might want to customize to give debugging info
    ['error']=error, -- XXX might want to customize to give debugging info
    ['getmetatable']=getmetatable,
    ['ipairs']=ipairs,
    ['math']=math,
    ['next']=next,
    ['pairs']=pairs,
    ['pcall']=pcall,
    ['rawequal']=rawequal,
    ['rawget']=rawget,
    ['rawset']=rawset,
    ['require']=require, -- could add support for sideloading l18n?
    ['select']=select,
    ['setmetatable']=setmetatable,
    ['string']=string, -- might want some compatibility thunks here
    ['table']={
      concat = function(list, sep, i, j)
        if sep == nil then sep = "" end
        if i == nil then i = 1 end
        if j == nil then j = compat.len(list) end
        return table.concat(list, sep, i, j)
      end,
      insert = function(list, pos, val)
        if val == nil then val = pos ; pos = nil end
        if pos == nil then pos = compat.len(list) + 1 end
        return table.insert(list, pos, val)
      end,
      maxn = function() error("not in Lua 5.4") end,
      move = compat.move,  -- not in Lua 5.1
      pack = compat.pack, -- not in Lua 5.1
      remove = function(l,p)
        if p == nil then p = compat.len(l) end
        return table.remove(l, p)
      end,
      sort = table.sort, -- might have issues with table length in Lua 5.1
      unpack = compat.unpack, -- not in Lua 5.1
    },
    ['tonumber']=tonumber,
    ['tostring']=tostring,
    ['type']=type,
    -- add utf8?
    ['_VERSION']="Lua 5.4", -- that's what we support!
    -- add warn? (not present in Lua 5.3, etc)
    ['xpcall']=xpcall,
  }
  GLOBALS._G = GLOBALS
  -- Lua 5.1 support
  if rawget(_G, "rawlen") then GLOBALS.rawlen = _G.rawlen end
  -- Scribunto support
  if rawget(_G, "mw") ~= nil then GLOBALS.mw = _G.mw end

  -- CLI support
  if rawget(_G, 'print') then GLOBALS.print = _G.print end
  if rawget(_G, 'io') then GLOBALS.io = _G.io end

  local env = { GLOBALS }
  local f = compile(ast)
  return f(env)
end

return {
  compile = compile,
  eval = eval,
}

end)

register('mlua.french', function(myrequire)
local lua = myrequire('mlua.lua')
local node = myrequire('mlua.node')

-- Translation table
local french = {
   ['and'] = 'et',
   ['break'] = 'arrêter',
   ['do'] = 'faire',
   ['else'] = 'sinon',
   ['elseif'] = 'sinonsi',
   ['end'] = 'fin',
   ['false'] = 'faux',
   ['for'] = 'pour',
   ['function'] = 'fonction',
   ['goto'] = 'allerà',
   ['if'] = 'si',
   ['in'] = 'de',
   ['local'] = 'locale',
   ['nil'] = 'nulle',
   ['not'] = 'pas',
   ['or'] = 'ou',
   ['repeat'] = 'répéter',
   ['return'] = 'renvoyer',
   ['then'] = 'alors',
   ['true'] = 'vrai',
   ['until'] = "jusqu’à", -- fun
   ['while'] = 'tant que', -- also fun
   -- Bartosz' addition
   ['with'] = 'avec',
}

local function cli(filename, to_or_from)
  local pprint = require "mlua.pprint" -- hide this from make_one_file
  -- Read input file contents
  local file = io.open(filename)
  if not file then
    print('failed to open file: '..filename)
    os.exit(false)
  end
  local source = file:read('*a')
  file:close()

  if to_or_from == nil then
    to_or_from = "to"
  end
  if to_or_from == "to" then
    -- To french:
    local p = lua.make_parse({
        __options = {
          tag = node.make_node,
        }
    })
    local ast = p(source, filename)
    print(pprint.print_ast(source, ast, french))
  else
    -- From french:
    local p = lua.make_parse({
        __options = {
          kw = function(s) return french[s] or s end,
          tag = node.make_node,
        }
    })
    local ast = p(source, filename)
    if to_or_from == 'from' then
       print(pprint.print_ast(source, ast))
    else
       -- execute!
       local eval = myrequire('mlua.eval').eval
       print(eval(ast))
    end
  end
end

--cli(arg[1], arg[2])

return french

end)

register('mlua.wiki', function(myrequire)
--[[
  a Scribunto module to evaluate a given module, but with our interpreter
]]--
local lua = myrequire('mlua.lua')
local make_node = myrequire('mlua.node').make_node
local eval = myrequire('mlua.eval').eval
local french = myrequire('mlua.french')

-- This could include language-specific options.
local parse
local function get_parse()
  if parse == nil then
    parse = lua.make_parse{
      __options = { tag = make_node },
    }
  end
  return parse
end

local parse_fr
local function get_parse_fr()
  if parse_fr == nil then
    parse_fr = lua.make_parse{
      __options = {
        kw = function(s) return french[s] or s end,
        tag = make_node,
      },
    }
  end
  return parse_fr
end

local function get_source(frame, default_to_module)
  local title = frame.args[1]
  if string.find(title, "^Module:") == nil and default_to_module then
    title = mw.title.makeTitle("Module", title)
  else
    title = mw.title.new(title)
  end
  local source = title:getContent()
  if source == nil then
    error("Can't find title " .. tostring(title))
  end
  -- strip syntaxhighlight tags if present
  source = source:gsub("^%s*<syntaxhighlight[^>]*>", "", 1)
  source = source:gsub("</syntaxhighlight[^>]*>%s*$", "", 1)
  return title, source
end

local function invoke_from_ast(frame, ast)
  local M = eval(ast)
  local f = M[frame.args[2]]
  -- now pop the first two arguments off and invoke the function
  -- this is harder than it should be, since frame.args isn't a
  -- real table.
  -- XXX should also hook frame:getArgument, frame.args.__pairs,
  -- frame.args.__ipairs, etc. and also be more careful w/r/t
  -- string/number.
  local args_proxy = function(_,v)
    if type(v) == 'number' then
      return frame.args[v+2]
    else
      return frame.args[v]
    end
  end
  local nargs = setmetatable({}, {__index = args_proxy})
  local nframe = {}
  local frame_proxy = function(_,v)
    if v == 'args' then return nargs end
    local res = frame[v]
    if type(res) == 'function' then
      return function(...)
        if select(1, ...) == nframe then
          -- substitute in the proper receiver
          return res(frame, select(2, ...))
        end
        return res(...)
      end
    end
    return res
  end
  setmetatable(nframe, {__index = frame_proxy})
  return f(nframe)
end

return {
  get_parse = get_parse,
  get_parse_fr = get_parse_fr,
  eval = function(frame, ...)
    if type(frame)=='string' then
      frame = { args = { frame, ... } }
    end
    local source = frame.args[1]
    local lang = frame.args[2] or 'en'
    local parse
    if lang == 'fr' then
      parse = get_parse_fr()
    else
      parse = get_parse()
    end
    local ast = parse(source, "<console>")
    return eval(ast)
  end,

  invoke = function(frame, ...)
    if type(frame)=='string' then
      frame = { args = { frame, ... } }
    end
    local title, source = get_source(frame, true)
    local parse = get_parse()
    local ast = parse(source, title.fullText)
    return invoke_from_ast(frame, ast)
  end,

  invokeUser = function(frame, ...)
    if type(frame)=='string' then
      frame = { args = { frame, ... } }
    end
    -- If we're using Bartosz' for-loop syntax,
    -- we can't get source from module space because this isn't
    -- "syntax-error-free lua"
    local title, source = get_source(frame)
    local parse = get_parse()
    local ast = parse(source, title.fullText)
    return invoke_from_ast(frame, ast)
  end,

  invokeFr = function(frame, ...)
    if type(frame)=='string' then
      frame = { args = { frame, ... } }
    end
    -- we can't get source from module space because this isn't
    -- "syntax-error-free lua"
    local title, source = get_source(frame)
    local parse = get_parse_fr()
    local ast = parse(source, title.fullText)
    return invoke_from_ast(frame, ast)
  end
}

end)

local modules = {}
modules['bit32'] = require('bit32')
modules['string'] = require('string')
modules['strict'] = {}
modules['table'] = require('table')
local function myrequire(name)
  if modules[name] == nil then
    modules[name] = true
    modules[name] = (builders[name])(myrequire)
  end
  return modules[name]
end
return myrequire('mlua.wiki')
end)()

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.