Module:User:Cscott/mlua
Appearance
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)()