return (functionlocal builders = local function register(name, f) builders[name] = fendregister('llpeg.lpegrex', function return require end)
register('mlua.lua', function(myrequire)--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 <
`:` @NAME @callargsDotIndex <
`:` @NAMEKeyIndex <
indexsuffix <-- DotIndex / KeyIndexcallsuffix <-- Call / CallMethod
p2 <-- p3 <-- p4 <--
var <-- (exprprimary (callsuffix+ indexsuffix / indexsuffix)+)~>rfoldright / Idcall <-- (exprprimary (indexsuffix+ callsuffix / callsuffix)+)~>rfoldrightexprsuffixed <-- (exprprimary (indexsuffix / callsuffix)*)~>rfoldrightfuncname <-- (Id DotIndex* ColonIndex?)~>rfoldright
funcbody <-- @`(` funcargs p2 @`)` Block p3 @`end`field <-- Pair / exprfieldsep <-- `,` / `;`
callargs <-| `(` (expr (`,` @expr)*)? p2 @`)` / Table / Stringidlist <-| Id (`,` @Id)*iddecllist <-| IdDecl (`,` @IdDecl)*funcargs <-| (Id (`,` Id)* (`,` Varargs)? / Varargs)?exprlist <-| expr (`,` @expr)*varlist <-| var (`,` @var)*
opor :BinaryOp <
`and`->'and' @exprcmpopcmp :BinaryOp <
`->'eq' / `~=`->'ne' / `<=`->'le' / `>=`->'ge' / `<`->'lt' / `>`->'gt') @exprboropbor :BinaryOp <
`~`->'bxor' @exprbandopband :BinaryOp <
(`<<`->'shl' / `>>`->'shr') @exprconcatopconcat :BinaryOp <
(`+`->'add' / `-`->'sub') @exprfactopfact :BinaryOp <
`^`->'pow' @exprunaryopunary :UnaryOp <
expr <-- exprorexpror <-- (exprand opor*)~>foldleftexprand <-- (exprcmp opand*)~>foldleftexprcmp <-- (exprbor opcmp*)~>foldleftexprbor <-- (exprbxor opbor*)~>foldleftexprbxor <-- (exprband opbxor*)~>foldleftexprband <-- (exprbshift opband*)~>foldleftexprbshift <-- (exprconcat opbshift*)~>foldleftexprconcat <-- (exprarit opconcat*)~>foldleftexprarit <-- (exprfact oparit*)~>foldleftexprfact <-- (exprunary opfact*)~>foldleftexprunary <-- opunary / exprpowexprpow <-- (exprsimple oppow*)~>foldleftexprsimple <-- Nil / Boolean / Number / String / Varargs / Function / Table / exprsuffixedexprprimary <-- Id / Paren
STRING <-- STRING_SHRT / STRING_LONGSTRING_LONG <-- STRING_SHRT <-- QUOTE_OPEN <-- / '«' / '‹' QUOTE_CONTENT <-- (ESCAPE_SEQ / !(QUOTE_CLOSE / LINEBREAK) .)*QUOTE_CLOSE <-- =qeESCAPE_SEQ <-- '\'-> @ESCAPEESCAPE <-- [\'"»] / ('n' $10 / 't' $9 / 'r' $13 / 'a' $7 / 'b' $8 / 'v' $11 / 'f' $12)->tochar / ('x' $16)->tochar / ('u' '' $16)->toutf8char / ('z' SPACE*)-> / (DEC_DIGIT DEC_DIGIT^-1 !DEC_DIGIT / [012] DEC_DIGIT^2)->tochar / (LINEBREAK $10)->tochar
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)->0COMMENT_SHRT <-- (!LINEBREAK .)*
LONG_CONTENT <-- (!LONG_CLOSE .)*LONG_OPEN <-- '[' {:eq: '='*:} '[' LINEBREAK? LONG_CLOSE <-- ']' =eq ']'
NAME <-- !KEYWORD SKIP-- We should really accept a proper unicode set here for name characters,-- but for the time being hack in some ISO-8859-1 charactersNAME_PREFIX <-- [_a-zA-ZÀ-ÿ]NAME_SUFFIX <-- [_a-zA-Z0-9À-ÿ]+
SHEBANG <-- '#!' (!LINEBREAK .)* LINEBREAK?SKIP <-- (SPACE+ / COMMENT)*LINEBREAK <-- %cn %cr / %cr %cn / %cn / %crSPACE <-- %spHEX_DIGIT <-- [0-9a-fA-F]DEC_DIGIT <-- [0-9]EXTRA_TOKENS <-- `` `[=` `--` -- unused rule, here just to force defining these tokens ]
-- List of syntax errorslocal SyntaxErrorLabels = `?", ["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 grammarlocal 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 '
' 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)