Documentation for this module may be created at Module:FormatTemplate/sandbox/doc
---- This module is meant to produce a first pass translation from a template to a module.
---- It scans character by character, one time ONLY, using getletter().
---- It works by recognizing every beginning that the following features ''should'' not be intermingled: [[, {{, {{#, {{{
---- It works through which of these features is present by character-by-character scanning
---- As a result, a new stack[] table entry is created, according to how many levels deep in we are.
---- At each meaningful : or |, separating fields in these features, it accumulates existing text to stack[n].text
---- Within a field, stack.append() is used to attach text to stack[n].out (text to be quoted) or stack[n].code (accumulated with quotes, or statements)
---- For functions that are understood by it (stack.annotate) it translates the features to Lua code.
---- variables get their own little var[] array. At the moment parameters are unique but parser functions may be reduplicated.
---- The beginnings and ends of features are labeled with colored spans, but most text is in nowiki tags.
---- The result is generally any text to output to the new module with a need to wiki (br /'s) should go </nowiki> ... <nowiki>
---- The output should be postprocessed with another function, not yet written, to remove futile concatenations, lines marked for deletion, etc.
---- The output should be formatted with another function, not yet written, to impose proper indentation.
local p={}
-- constants
local MAXPOSN = 100000 -- usually 50000 was 3 seconds .. not right now though ..
local HOLDABLE = {["{"] = true, ["["] = true, ["}"] = true, ["]"] = true}
local ACTABLE = {["{"] = true, ["["] = true, ["}"] = true, ["]"] = true, ["|"] = true, [":"] = true}
local MARKER = {["{{{"] = "|", ["{{"] = "|", ["{{#"] = ":", ["[["] = "|"}
local MATCH = {["{{{"] = "}}}", ["{{#"] = "}}", ["{{"] = "}}", ["[["] = "]]"}
local ENDQUOTE = {['"'] = '"', ["'"] = "'", ["[=["] = "]=]", ["[==["] = "]==]", ["[===["] = "]===]", ["[====["] = "]====]", ["[=====["] = "]=====]", ["[======["] = "]======]", ["[=======["] = "]=======]", ["[========["] = "]========]", ["[=========["] = "]=========]", ["[==========["] = "]==========]", ["[===========["] = "]===========]", ["[============["] = "]============]", ["[=============["] = "]=============]"}
local DUPLICATECASES = 3 -- number of times to duplicate code setting a variable inside "then xxxx" cases rather than calculate it for all cases.
local text = ""
local debuglog = ""
local getletter -- this module is designed around reading ONCE, tracking state; getletter() gets each letter in text once
local flag = false -- true marks the end of the getletter() stream
function getContent(template)
local title -- holds the result of the mw.title.xxx call
if not(template) then
title=mw.title.getCurrentTitle()
if not(title) then return "error: failed to getCurrentTitle()" end
local tdedoc=mw.ustring.match(title.fullText,"Template:(.-)/doc")
if tdedoc then title=mw.title.new("Template:"..tdedoc) end -- SPECIAL CASE: Invoke in the template documentation processes the template instead
else title=mw.title.new(page)
if not (title) then return "error: failed to mw.title.new(" .. template .. ")" end
end -- if not(template)
return title.getContent(title) or ""
end
local function scanabort()
flag = true
return ":" -- an "actable" to prevent looping
end
local function escape(text)
text = string.gsub(text,"%A%D","_")
return text
end
local function quote(text)
text = (not string.match(text,'"')) and ('"' .. text .. '"') or (not string.match(text,"'")) and ("'" .. text .. "'") or (not string.match(text,'%[=%[') and '[=[' .. text .. ']=]') or (not string.match(text,'%[==%[') and '[==[' .. text .. ']==]') or (not string.match(text,'%[===%[') and '[===[' .. text .. ']===]') or (not string.match(text,'%[====%[') and '[====[' .. text .. ']====]')
return text
end
function translateModule(text,importstack,posn,template) -- note template is just for the error message
local letter=""
local output=""
local outtable, vartable = {}, {}
posn=tonumber(posn) or 0
if posn>0 then text=string.sub(text,posn,-1) end --- need to chop off the preceding text so it doesn't gmatch to it
local stopposn = (string.find(text, "[^{}%[%]|:]", MAXPOSN))
if stopposn then text= string.sub(text, 1, stopposn) end
-- vars holds all the parameters
vars = {top = 0, list = {}}
vars.declare = function(id, name)
if vars.list[name] then
return vars.list[name]
else
vars.list[name] = id
return id
end
end
-- pars is like vars, for parser functions. The entire stack entry has to be generated, and on pop, see if, minus its
-- p_nnn name, it exactly matches a previous entry; if so, "drop" the old p_nnn to the last layer and add no new statement at all.
-- this should work because otherwise nothing goes in and redefines p_nnn once it's set.
pars = {list = {}}
pars.declare = function(id, text)
text = string.gsub(text, id, "t_")
local pl = pars.list[text]
if pl then
return pl, false
else
pars.list[text] = id
return id, true
end
end
-- stack is the main structure for holding the data. Each time a feature is found you go up a level.
-- When you go down you create a statement or append text into the stack a level lower (or both).
stack = {top = #importstack, counter = 0}
for i = 0, stack.top do
stack[i] = {}
stack[i].feature = importstack[i]
stack[i].text = {}
stack[i].seg = 1 -- this is NOT ACCURATE, would need to be saved in the transition
end
-- this table defines the code to deal with specific parser functions like {{#ifeq: Also some functions for other features.
stack.annotate = { -- name is the first field of the feature ('if' or the parameter name); id is the counter number
-- text for the THESE STATEMENTS (stack top we're writing to)
-- %1=if %2 then %3 else %4 end
['param1'] = {function(last, text, varid) return '(' .. varid .. ' or ' .. '"{{{' .. string.sub(varid, 3, -1) .. '}}}")' end,
etc = function(last, text) return " .. " .. quote("|" .. text) end, -- shouldn't happen......
last = function(last, text) return text end},
['param2'] = {function(last, text, varid) return "(" .. varid .. (last and "" or " or ") end,
function(last, text) return quote(text) end, -- returned as _code_
etc = function(last) return " .. " .. quote("|" .. text) end, -- shouldn't happen......
last = function(last, text) return text .. ")" end},
['if'] = {function(last) return "if mw.text.trim(" end,
function(last, text, varid)
if text == "" then
text = '""'
end
text = text .. ') ~= "" then</nowiki><br><nowiki> '
if last then -- {{#if:%1}} will return empty string in any case. This is a dumb way to do it, transitional...
text = text .. varid .. ' = ""</nowiki><br>else<br> <nowiki>' .. varid .. ' = ""'
else
text = text .. varid .. ' = '
end
return text
end,
function(last, text, varid)
if text == "" then
text = '""'
end
if last then -- {{#if:%1|%2}} will return empty string if %1 is false
text = text .. '</nowiki><br>else<br> <nowiki>' .. varid .. ' = ""'
else
text = text .. '</nowiki><br>else<br> <nowiki>' .. varid .. ' = '
end
return text
end,
function(last, text, varid)
if text == "" then -- this is %3 in {{#if:%1|%2|%3}}
return '""'
else
return text
end
end, -- this is stupid, but quotes are suppressed on null out string, so...
etc = function(last, text)
return "</nowiki><br><nowiki>-- " .. text .. ': ignored' -- you can have {{#if:%1|%2|%3|%4}}, but %4 isn't displayed at all, even when %3 is
end,
last = function(last, text)
return text .. "</nowiki><br><nowiki>end"
end}, -- weirdly, having function(last) there caused "Not enough memory" error, not nil truncation error
['ifeq'] = {function(last)
return "if mw.text.trim("
end,
function(last, text, varid)
return text .. ') == mw.text.trim('
end,
function(last, text, varid)
if text == "" then
text = '""'
end
text = text .. ') then</nowiki><br><nowiki>'
if last then
text = text .. varid .. ' = ""'
else
text = text .. varid .. ' = '
end
return text -- returned as _code_
end,
function(last, text, varid)
if text == "" then
text = '""'
end
if last then
text = text .. '</nowiki><br><nowiki>else ' .. varid .. ' = ""'
else
text = text .. '</nowiki><br><nowiki>else ' .. varid .. ' = '
end
return text
end,
function(last, text, varid)
if text == "" then
text = '""'
end
return text
end,
etc = function(last, text)
return "</nowiki><br><nowiki>-- " .. text .. ": ignored"
end,
last = function(last, text)
return text .. "</nowiki><br><nowiki>end"
end},
['switch'] = {
function(last, text, varid) return varid .. '_in = mw.text.trim(' end,
function(last, text, varid) return text .. ")</nowiki><br><nowiki>if " .. varid .. "_in == mw.text.trim(" end,
etc = function(last, text, varid)
-- debuglog = debuglog .. "text=" .. text
local text1 = string.match(text, "^([^=]*)")-- I don't know if I'll ever figure out what variable type string.match returns
local text2 = string.match(text, "=(.*)")
if text2 then
local quotetype = string.sub(text1, 1, 1) -- text is received as a quoted string. Need to split at the "=".
if (quotetype == "[") then quotetype = string.match(text1, "%[=*%[") end
text1 = text1 .. ENDQUOTE[quotetype]
text2 = quotetype .. text2
end
if last then
if not text2 then
return "'[TOKEN: DELETE LINE]') then</nowiki><br><nowiki>else " .. varid .. " = " .. text1 .. "</nowiki><br><nowiki>"
elseif (string.match(text1, "#default")) then -- this isn't technically foolproof, but compared to the potential bugs in unquoting, whitespace, unicode, etc...
return "'[TOKEN: DELETE LINE]') then</nowiki><br><nowiki>else " .. varid .. " = " .. text2 .. "</nowiki><br><nowiki>"
else
-- debuglog = debuglog .. "DB" .. mw.text.trim(text1) .. "BD"
return "'[TOKEN: DELETE LINE]') then</nowiki><br><nowiki>elseif " .. varid .. " == " .. text2 .. " then</nowiki><br><nowiki> " .. varid .. " = " .. text1 .. "</nowiki><br><nowiki>"
end
else
if not text2 then
return text1 .. " or "
else
-- debuglog = debuglog .. "text1=" .. text1 .. "text2=" .. text2
return text1 .. ") then</nowiki><br><nowiki>" .. varid .. " = mw.text.trim(" .. text2 .. ((not last) and (")</nowiki><br><nowiki>elseif " .. varid .. "_in == mw.text.trim(") or ")")
end
end
end,
last = function(last, text) return text .. " end" end},
['parser'] = {
function (last, text, varid) return '"{{#" .. ' .. text end, -- '"</nowiki><span style="color:blue;">{{#</span><nowiki>" .. ' .. text end,
function(last, text, varid) return ' .. ":" .. ' .. text end, -- unknown parser functions are {{%1:%2|%3|etc.}}
etc = function(last, text, varid) return ' .. "|" .. ' .. text end,
last = function(last, text, varid) return text .. ' .. "}}"' end}, -- ' .. "</nowiki><span style="color:blue;">}}</span><nowiki>"' end},
['template'] = {
function (last, text, varid) return '"{{" .. ' .. text end, -- '"</nowiki><span style="color:red;">{{</span><nowiki>" .. ' .. text end,
function (last, text, varid)
if varid == ':' then
return ' .. "' .. varid .. '" .. ' .. text
else
return ' .. "|" .. ' .. text
end
end,
etc = function(last, text, varid) return ' .. "|" .. ' .. text end,
last = function(last, text, varid) return text .. ' .. "}}"' end}, -- ' .. "</nowiki><span style="color:red;">}}</span><nowiki>"' end},
['wikilink'] = {
function (last, text, varid) return '"[[" .. ' .. text end, -- '"</nowiki><span style="color:green;">[[</span><nowiki>" .. ' .. text end,
etc = function(last, text, varid) return ' .. "|" .. ' .. text end,
last = function(last, text, varid) return text .. ' .. "]]"' end} } -- ' .. "</nowiki><span style="color:green;">]]</span><nowiki>"' end} }
stack.append = function(text, codeflag) -- code is true if text doesn't need to be quoted
-- stack[stack.top] shall contain two buffers of text, to which text can be added at the end by concatenation: .text, .code
-- stack[stack.top].text is fully archived from previous fields
-- stack[stack.top].code is ready to add to the Lua source, but is in the present field
-- stack[stack.top].out is new text mode additions from the template. It gets quoted and transferred to code whenever code is added.
if text ~= "" then stack[stack.top].dropped = nil end
if codeflag then
if (not stack[stack.top].code) then
stack[stack.top].code = text
stack[stack.top].start = true
return
end
if stack[stack.top].out ~= "" then
if stack[stack.top].code ~= "" then stack[stack.top].code = stack[stack.top].code .. " .. " end
if text ~= "" then text = " .. " .. text end
stack[stack.top].code = stack[stack.top].code .. quote(stack[stack.top].out) .. text
else
stack[stack.top].code = stack[stack.top].code .. text
end
stack[stack.top].out = ""
else
if (not stack[stack.top].out) then
stack[stack.top].out = text
return
end
stack[stack.top].out = stack[stack.top].out .. text
end
end
-- this is done at the end of a field (like at a "|" in a switch statement.
-- fcn is some annotation function chosen per the stack's record of the name of the parser function for that stack level.
stack.codify = function(fcn, id, name, last)
stack.append("", true)
stack[stack.top].dropped = nil
stack[stack.top].code = fcn(last, stack[stack.top].code, id, name) or stack[stack.top].code
end
-- this is done when a new feature is found; move up to the next level.
-- Note stack[n] is a new table each time, so all its fields are nil by default.
stack.push = function(feature)
stack.top = stack.top + 1
stack[stack.top] = {}
if feature == "{{{" then
stack[stack.top].text = {}
else
stack[stack.top].text = {"</nowiki><br /><nowiki>"}
end
stack[stack.top].seg = 1 -- which field we're in (IF 1 = 2 THEN 3 ELSE 4) delimited by | or :
stack[stack.top].feature = feature -- actual text to open the sequence i.e. {{#
end
-- the end of a feature is found; now collapse its contents, or some memo, to the preceding level, or make a new statement about it.
-- Note that in Wikitext the lower stack levels get input data _only_ from higher stack levels, and those are closed first,
-- so in theory a variable should always be set _before_ they're accessed.
stack.pop = function(feature)
stack.field("", true)
local spillover = ""
local pop = stack[stack.top].feature
if (MATCH[pop] ~= feature and feature == "}}}") then
feature = "}}"
spillover = "}"
end
if (MATCH[pop] ~= feature) then
stack.append("<--- error? ")
end
stack.write()
local feature = stack[stack.top].feature
local name = stack[stack.top].name
local id = stack[stack.top].id
local text = table.concat(stack[stack.top].text)
stack[stack.top] = nil
stack.top = stack.top - 1
if feature == "{{" or (feature == "{{#" and name == "parser") then -- wikitext
if stack[stack.top].dropped then text = " .. " .. text end
stack.append(text, true) -- this is not lua code anymore here
stack[stack.top].dropped = true
elseif feature == "{{#" then -- parsed parser function, new line
local novel
id, novel = pars.declare(id, text)
if novel then table.insert(outtable, text) end
if stack[stack.top].dropped then id = " .. " .. id end
stack.append(id, true)
stack[stack.top].dropped = true
elseif feature == "[[" then -- ordinary wikitext for display
if stack[stack.top].dropped then text = " .. " .. text end
stack.append(text, true) -- links are just quoted text, but for some reason "true" mode works and text mode doesn't....
stack[stack.top].dropped = true
elseif feature == "{{{" then
if stack[stack.top].dropped then text = " .. " .. text end
stack.append(text, true) -- this is lua variable name for the parameter
stack[stack.top].dropped = true
end
return spillover
end
-- hit ":" or "|", the former used only in parser functions on the first field, or as a stand-in for the end of data
stack.field = function (letter, last)
local ss = stack[stack.top]
-- first make sure this is really a field boundary
if (last or (ss.seg == 1 and (letter == MARKER[ss.feature] or (ss.feature == "{{" and letter == ":"))) or (ss.feature ~= "[[" and ss.feature ~= "{{" and letter=="|")) then
-- if we don't know, we need to know the name of this segment (unless we're just moving it through as text)
if (ss.seg == 1) then
local name = mw.text.trim(stack[stack.top].out) -- this misses a parser error if "#if :" is called. Sue me.
if (ss.feature == '{{#') then
ss.name = stack.annotate[name] and name or "parser"
ss.id = "p_" .. tostring(stack.counter)
stack.counter = stack.counter + 1
-- now if the feature is {{{ then we register a variable name (
elseif (ss.feature == '{{{') then
ss.name = name
ss.id = vars.declare("t_" .. escape(name), name) -- will be an old id if it exists
if last then ss.name = "param1" else ss.name = "param2" end
elseif (ss.feature == '[[') then
ss.name = "wikilink"
ss.id = "_link_"
else
ss.name = "template"
ss.id = letter -- i.e. in unimplemented function {{urlencode:, make the terminating character the id to distinguish
end
end
stack.append("", true)
-- now we know the name, I think.
local fcn = stack.annotate[(not ss.id and "template") or ss.name]
if fcn[ss.seg] then
stack.codify(fcn[ss.seg],ss.id, ss.name, last)
else
stack.codify(fcn["etc"],ss.id, ss.name, last) -- this is pretty muddy - using last both as a flag for preceding sections and as a function tack-on. Should convert all uses of the second to the first.
end
if last then
stack.codify(fcn["last"],ss.id, ss.name)
end
ss.seg = ss.seg + 1
stack.write()
else -- NOT a new field, so don't do anything
stack.append(letter)
end
end
-- first uses append of "" as code to close out stack[n].code (this field's text contents)
-- then inserts that in stack[n].text (simple concatenation only, stored until the stack is closed)
stack.write = function()
stack.append("", true) -- blank code addition forces quotation of remaining text
table.insert(stack[stack.top].text, stack[stack.top].code)
stack[stack.top].code = ""
end
-- this is the main loop to read character by character from the source text
template=template or ""
getletter = string.gmatch(text,".")
stack[stack.top].out, stack[stack.top].code = "", ""
repeat
local holding = ""
repeat
letter = letter or ""
while not ACTABLE[letter] do
stack.append( letter)
letter = getletter() or scanabort()
end
if HOLDABLE[letter] then
holding = letter
else
stack.field(letter)
end
letter = ""
until holding ~= "" or flag
if #stack[stack.top].out>7500 then
stack.write()
end
letter=getletter() or scanabort()
-- add the letter to the next feature being parsed if possible
if (holding == "[") then -- either [[ or just ignore
-- cases based on the next letter after "["
if (letter == "[") then
stack.push("[[")
letter = ""
else
stack.append( holding) -- single [, treat normally
end
elseif (holding == "{") then
-- cases based on the next letter after "{"
if (letter == "{") then
letter = getletter() or scanabort()
if (letter == "#") then
stack.push("{{#")
letter = ""
elseif (letter == "{") then
stack.push("{{{")
letter = ""
else
stack.push("{{")
end
end
elseif (holding == "]") then
if (letter == "]") then -- we have a ]]
stack.pop("]]")
letter = ""
else stack.append(holding)
end
elseif (holding == "}") then
if (letter == "}") then
letter = getletter()
if letter == "}" then
letter = stack.pop("}}}")
else
stack.pop("}}")
end
else stack.append(holding) -- lone } is nothing
end
end
until flag
-- a colon is used to indicate EOF to avoid checking for "flag" in the scan loop itself; this removes it
stack[stack.top].out = string.sub(stack[stack.top].out, 1, -2)
table.insert(vartable, "</nowiki> ---- beginning of module ----<br />local p = {}<br /><br />getArgs = require('Module:Arguments').getArgs<br /><br />p.main = function(frame)<br />args = getArgs(frame)<br /><br /><nowiki>")
table.insert(vartable, "</nowiki> -- begin variable table<br /><nowiki>")
for k, v in pairs(vars.list) do
if (tonumber(k) == nil) or (tonumber(k) < 1) or (tonumber(k) % 1 ~= 0) then -- quote all non positive integers, others are numbers
table.insert(vartable, "</nowiki>local " .. tostring(v) .. " = args['" .. k .. "'] and mw.text.trim(args['" .. k .. "'])<br /><nowiki>")
else
table.insert(vartable, "</nowiki>local " .. tostring(v) .. " = args[" .. k .. "] and mw.text.trim(args[" .. k .. "])<br /><nowiki>")
end
end
local pdeclare
local pdeclareused
local parssort = {}
for k, v in pairs(pars.list) do
table.insert(parssort, tonumber(string.sub(v, 3, -1)))
pdeclareused = true
end
if parssort[1] then
table.sort(parssort)
pdeclare = "</nowiki>local p_" .. table.concat(parssort, ", p_") .. "<br /><nowiki>"
else
pdeclare = ""
end
table.insert(vartable, pdeclare)
table.insert(vartable, "</nowiki> -- end variable table<br /><br /> -- begin parser function translations<nowiki>")
if stack.top>0 then
stack[stack.top].out = string.sub(stack[stack.top].out, 1, -2) .. "<--- end of run ---><br /></nowiki>'''run incomplete.'''"
stack.write()
-- this code hasn't been updated - it doesn't contain all the information needed to resume a run!
local stackcrypt = ""
for i = stack.top, 1, -1 do
table.insert(outtable, table.concat(stack[i].text))
stackcrypt = stackcrypt .. stack[i].feature
stack[i] = {}
end
stack.top = 0
stackcrypt=string.gsub(stackcrypt,"{","<")
stackcrypt=string.gsub(stackcrypt,"%[","(")
stackcrypt=string.gsub(stackcrypt,"}",">")
stackcrypt=string.gsub(stackcrypt,"%]",")")
if string.len(text) >= MAXPOSN then -- didn't complete the run, making false promises to the user now:
stack[stack.top].out = stack[stack.top].out .. "<br />''Note: due to restrictions on Lua time usage, runs are truncated at MAXPOSN characters''"
stack[stack.top].out = stack[stack.top].out .. "<br />''To continue this run, preview or enter <nowiki>{{#invoke:FormatTemplate|toModule|page="..template.."|stack="..stackcrypt.."|position="..#text.."}}"
else stack[stack.top].out = stack[stack.top].out .. "<br />''If you have an additional segment of template to process, preview or enter <nowiki>{{#invoke:FormatTemplate|toModule|page="..template.."|stack="..stackcrypt.."|position=0}}"
end
end
stack.write()
-- var table is the variable declarations; outtable is the statements (if t_1 == "" then t_1 = "stuff"); stack[0] is the return statement
output=table.concat(vartable) .. table.concat(outtable, "</nowiki><br /><nowiki>") .. "</nowiki><br /> -- end parser function translations<br /><br /><nowiki>return frame:preprocess(" .. table.concat(stack[0].text, "</nowiki><br /><nowiki>") .. ")</nowiki><br />end<br />return p<br /> ---- end of module ----<br /><nowiki>"
return output
end
function p._toModule(stackcrypt, posn)
-- none of this stack/posn stuff actually works any more, it's tremendously out of date!
stackcrypt=mw.ustring.gsub(stackcrypt,"<","{")
stackcrypt=mw.ustring.gsub(stackcrypt,"%(","[")
stackcrypt=mw.ustring.gsub(stackcrypt,">","}")
stackcrypt=mw.ustring.gsub(stackcrypt,"%)","]")
local stack={}
local prowl=mw.ustring.gmatch(stackcrypt,"[^,%s]+")
repeat
local x=prowl()
if x then table.insert(stack,x) end
until not x
local nowikisafehouse={}
local nowikielementnumber=0
local prowl=mw.ustring.gmatch(text,"(<nowiki>.-<%/nowiki>)")
repeat
local nowikimatch=prowl()
if not(nowikimatch) then break end
nowikimatch = mw.ustring.gsub(nowikimatch, "<", "<") -- I want these inactive on display
nowikielementnumber=nowikielementnumber+1
table.insert(nowikisafehouse,nowikimatch)
until false
local nowikicount
text, nowikicount = mw.ustring.gsub(text,"(<nowiki>.-<%/nowiki>)","<Module:FormatTemplate internal nowiki token>")
debuglog = debuglog .. "-- " .. tostring(nowikicount) .. " nowiki segments treated as plain text"
text = mw.ustring.gsub(text, "\n", '\\n') -- trying preserving ALL newlines
-- this is the meat of the formatting
text=translateModule(text,stack,posn,page)
-- unprotect the nowikis from the template itself - but inactivate them on first display!
for nw = 1,nowikielementnumber do
text=mw.ustring.gsub(text,"<Module:FormatTemplate internal nowiki token>", nowikisafehouse[nw], 1)
end
text = "<nowiki>" .. text .. "</nowiki>"
-- returns global changes in text, debuglog
end
function nextquote(posn)
-- starting from a position that we know is not in a string, comment, etc.!
local quotes = {['(")'] = function() return '"' end,
["(')"] = function() return "'" end,
["(%[=+%[)"] = function(q) return "]" .. string.sub(q, 2, -2) .. "]" end}
local startquoteposn = #text
local loc, xxx, startquotetype, endquotetype, endquoteposn
for k,v in pairs(quotes) do
loc, xxx, startquotetype = string.find(text, k, posn)
if (loc and loc < startquoteposn) then
startquoteposn = loc
endquotetype = v(startquotetype)
end
end
if (startquoteposn == #text) then return nil end -- no more quotes!
xxx, endquoteposn = string.find(text, endquotetype, startquoteposn + 1)
return startquoteposn, endquoteposn, endquotetype
end
function p._fixIfs()
-- receives global 'text'
-- if a parser variable will only be used within "a few" if statements, only calculate it there.
-- (in theory this applies to params, but it's nicer to have a full table up front)
text = string.gsub(text, "\n[^\n]-%[TOKEN: DELETE LINE%][^\n]-\n", "\n") -- finally clear up those delete line tokens. Might move to earlier pass...
local pdec = string.match(text, "local (p_.-)\n")
assert(pdec, "failed to find 'local p_nnn...' variable list")
pdec = mw.text.split(pdec, ", ") -- pdec is now a sequence of all the p_nnn variables in the order they are first mentioned in the first pass code
local xxx, parssegstart, parssegend -- xxx = throwaway value. This function only messes with the scope of parser function evaluations.
xxx, parssegstart = string.find(text, "\n%s%-%- begin parser function translations\n")
parssegend = string.find(text, "\n%s%-%- end parser function translations\n", parssegstart)
debuglog = debuglog .. tostring(parssegstart) .. "FFF" .. tostring(parssegend)
local parsseg = string.sub(text, parssegstart, parssegend)
local parsarray = mw.text.split(parsseg, "\n\n")
-- parsarray is now made up of individual popped stack entries from the first pass. #if, #ifeq, #switch, for example.
local parsusages = {} -- which variables appear in one parsseg (dictionary)
local varusages = {} -- which parssegs a variable appears in (sequence)
for i = 1, #pdec do
varusages[pdec[i]] = {}
end
for i = 1, #parsarray do
local getvar = string.gmatch(parsarray[i], "(p_%d*)")
parsusages[i] = {}
repeat
var = getvar()
if not var then break end
debuglog = debuglog .. "-" .. var
parsusages[i][var] = true
table.insert(varusages[var], i)
until false
end
-- Now we go through the variables last to first. If a p_variable is used only in DUPLICATECASES or fewer "then XXX" statements, insert the code setting it after 'then'
for i = #pdec, 1, -1 do
local var = pdec[i]
if #varusages[var] <= DUPLICATECASES then
-- OK, now each variable ought to be assigned only the first time it is mentioned, used all the others
-- first, for now we're not trying to do anything with variables called in the decision making logic (the if, or assignment of p_nnn_in
local predicate = false
for c = 2, #varusages[var] do
local xxx, predend = string.find(parsarray[i], "then\n")
if not predend or string.find(string.sub(parsarray[i], 1, predend), var) then
predicate = true
-- now figure out where/which line to substitute, but don't do it until all pass the test
end
if not predicate then
-- substitute these segments, blank out the entries for the moved segment
end
end
end
end
text = mw.text.nowiki(text) -- just to mark for now
debuglog = "\nDebug data:\n" .. mw.text.nowiki(debuglog)
-- returns global 'text', 'debuglog'
end
function p._fixConcats()
-- receives global 'text'
-- tokenize comments to avoid getting fouled
local outarray = {}
posn = 1
quote1 = {nextquote(1)}
repeat
quote2 = {nextquote(quote1[2]+1)}
if not quote2[1] then break end
-- do something with the concat if it's just a concat
if (mw.text.trim(string.sub(text, quote1[2] + 1, quote2[1] - 1)) == "..") then
if (quote1[3] == quote2[3]) then
table.insert(outarray, string.sub(text, posn, quote1[2] - 1))
posn = quote2[1] + 1
end
end
quote1 = quote2
until false
table.insert(outarray, string.sub(text, posn, -1))
text = "<pre>" .. mw.text.nowiki(table.concat(outarray)) .. "</pre>"
end
-- this function also (mis)handles the pre-processing to get rid of nowikis and comments (only it doesn't do the comments yet, etc...)
function p.main(frame,fcn)
local args=frame.args
local parent=frame.getParent(frame)
if parent then pargs=parent.args else pargs={} end
page=args.page or pargs.page
text = getContent(page)
local stackcrypt=args.stack or pargs.stack or ""
local posn=args.position or pargs.position or 0
-- decide on a function
fcn=fcn or args["function"] or pargs["function"] or ""
fcn=mw.ustring.match(fcn,"%S+")
if (fcn == "toModule") then
p._toModule(stackcrypt, posn) -- passing text, debuglog globally
elseif (fcn == "fixConcats") then
p._fixConcats()
elseif (fcn == "fixIfs") then
p._fixIfs()
end
-- preprocess as nowiki-bounded text
return frame:preprocess(text .. "\n" .. debuglog)
end
function p.toModule(frame)
return p.main(frame,"toModule")
end
return p