Module:User:Surjection/luasubst

local export = {}

local function isPositiveInteger(v) return type(v) == 'number' and v >= 1 and math.floor(v) == v and v < math.huge end

local function shift_args(args, n)	local newargs = {} for k, v in pairs(args) do		if isPositiveInteger(k) then if k > n then newargs[k - n] = v			end else newargs[k] = v			end end return newargs end

local PseudoframeArgument = {}

function PseudoframeArgument.expand(obj) return obj["_expand"](obj) end

function PseudoframeArgument.new(frame, obj) return setmetatable({ ["_value"] = obj, ["_expand"] = function(obj) return frame:preprocess(obj) end }, PseudoframeArgument) end

PseudoframeArgument.__index = PseudoframeArgument

local Pseudoframe = {} local forwarded_methods = { "callParserFunction", "expandTemplate", "extensionTag", "getTitle", "newChild", "preprocess", "newParserValue", "newTemplateParserValue", } for _, name in ipairs(forwarded_methods) do	Pseudoframe[name] = function (pf, ...) local f = pf["_frame"] return f[name](f, ...) end end

function Pseudoframe.getArgument(pf, arg) if type(arg) == "table" then return pf:getArgument(arg.name) end local arg = pf.args[arg] if arg == nil then return nil end return PseudoframeArgument.new(f, arg) end

function Pseudoframe.argumentPairs(pf) return pairs(pf.args) end

function Pseudoframe.getParent(pf) if pf._parent then return pf._parent else return pf._frame:getParent end end

function Pseudoframe.new(frame, args, parent) return setmetatable({ ["_frame"] = frame, ["args"] = args, ["_parent"] = parent }, Pseudoframe) end

Pseudoframe.__index = Pseudoframe

local function trampoline(module_name, entrypoint, pframe) return mw.text.nowiki(require(module_name)[entrypoint](pframe)) end

local function clean_traceback(msg) local tb = debug.traceback(msg, 3) tb = mw.ustring.gsub(tb, "Module:User:Surjection/luasubst:.+", "") tb = mw.ustring.gsub(tb, "package.lua:.+", "") tb = mw.ustring.gsub(tb, "mw.lua:.+", "") return mw.text.trim(tb) end

local function require_tracked(trackstring, old_require) return function (name) trackstring[1] = trackstring[1] .. clean_traceback("Module imported " .. name) .. "\n\n" return old_require(name) end end

local function trampoline_track_requires(module_name, entrypoint, pframe) local old_require = require local slot = {""} require = require_tracked(slot, old_require) local result = old_require(module_name)[entrypoint](pframe) require = old_require return mw.text.tag("pre", {}, slot[1]) end

local function do_subst_explicit(frame, trampoline) local module_name = "Module:" .. frame.args[1] local entrypoint = frame.args[2] local pframe = Pseudoframe.new(frame, shift_args(frame.args, 2), nil) local result = trampoline(module_name, entrypoint, pframe) return result end

local function do_subst_implicit(frame, trampoline) local parent = frame:getParent local template_name = parent.args[1] local template_args = shift_args(parent.args, 1) local pseudoparent = Pseudoframe.new(parent, template_args, nil) local template_title = mw.title.new(template_name, 10) if template_title.isRedirect then template_title = template_title.redirectTarget end local template_source = template_title:getContent template_source = mw.ustring.gsub(template_source, " .- ", "") template_source = mw.ustring.gsub(template_source, " (.-) ", "%1") template_source = mw.ustring.gsub(template_source, ".- (.-) .*", "%1") local ok, _, invoke_text = mw.ustring.find(template_source, "^(%b{})") if not ok then error("Template does not begin with an invoke or a transclusion!") end local ok, _, invoke_params = mw.ustring.find(invoke_text, "^") if not ok then error("Template does not begin with an invoke!") end local ok, _, module_name, entrypoint, module_params = mw.ustring.find(invoke_params, "^([^|]+)|([^|]+)|(.+)") if not ok then ok, _, module_name, entrypoint = mw.ustring.find(invoke_params, "^([^|]+)|([^|]+)$") if ok then module_params = nil else error("Could not parse invoke!") end end local parsed_args = {} if module_params then local parseTemplate = require("Module:User:Surjection/templateparser").parseTemplate local parsed_ok parsed_ok, parsed_args = parseTemplate("") if not parsed_ok then error("Could not parse module invoke parameters!") end for k, v in pairs(parsed_args) do			if v:find("%[") or v:find("{") then parsed_args[k] = frame:preprocess(v) end end end module_name = "Module:" .. module_name local pframe = Pseudoframe.new(frame, parsed_args, pseudoparent) local result = trampoline(module_name, entrypoint, pframe) return result end

function export.subst(frame) return do_subst_explicit(frame, trampoline) end

function export.subst_universal(frame) return do_subst_implicit(frame, trampoline) end

function export.subst_universal_track_requires(frame) return do_subst_implicit(frame, trampoline_track_requires) end

return export