Module:User:Cscott/mlua

From Wikipedia, the free encyclopedia
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)()