-- Module to convert Roman numerals and reject invalid numerals
local p=
local tags =
local function atc(cn, rp) local s = " - " if rp ~= nil then s = s.."roman numeral "..tostring(rp)..", " end s = s.."char "..tostring(cn).."; " return send
local function unesc(s) s = s:gsub("\\p", tags.pipe) s = s:gsub("\\\\", "\\") s = s:gsub("\\=", "\=") return send
local function disperr(err) return tags.errs..err..tags.sspanend
local rn_ref = local ref_rn =
local function todec1 (rns, ovl, vbr, tcc)
local err = ""
local cex, cfr, crn, run, num = 0,0,0,0,0,0 -- prn, crn: previous, current roman numeral value local pex, pfr, prn = 0,0,0 -- (current) cex: exponent (10^1, 10^2 etc.), cfr: fractional part, run: amount of character so far local rnc = "" -- roman numeral character
for i = 1, #rns do -- cex = 2, cfr = 0 or 0.5, cex, cfr = math.modf((rns[i]-1)/2) -- crn = 100 or 500 etc. if cfr
nil then j = 0 end rn, ov, vb = rns[i+j], ovl[i+j], vbr[i+j] rncr = rn - ov*6 - vb*4 if ia
13 and rncr
7 then rncr = 1 ov = ov + 1 else rncr = rncr + 1 end end rnc = ref_rn[rncr] local rnc_vb = "" if vb
0 then
num = num + prn*run run = 1
elseif crn
0 then if run > 3 then -- e.g. "XXXXX" for 50, "L" suggested err = err.."More than four "..rnc.." in a row, suggestion: "..rncg(0,1).."?"..atc(tc, i) run = run + 1 elseif run
0.5 then -- e.g. "VV" for 10, "X" suggested err = err..rncg(-1).." cannot be with another "..rnc..", suggestion: "..rncg(0,1).."?"..atc(tc,i) else return -1, ("Unknown error 1") end elseif crn > prn then if crn > prn * 10 then -- e.g. "XM" or "IL" err = err..rnc.." cannot follow "..rncg(-1).." (Subtraction can only be within the same digit)"..atc(tc,i) elseif pfr
local function todec(args) -- pn: number of pipes (vertical bar) found so far, p: in a vertical bar(X100)? local err, tag = "", "" -- err: error message, rnseq: sequence of roman numerals, tag: current html tag local rnseq, t =, -- tc: total character count so far, argn: argument number, cc: current character local ovl, vbr, tcc =,, -- ovl,vbr,tcc: tc, status of overline and t: table of html tags, local argn, tc, pn, ovc = 1, 0, 0, -1 -- vertical bar for each number in rnseq, n: current character number local ov, dv, rn = 0,0,0 -- ov, dv, rn: number of overline, double overline, rn tags nested local p = false -- gt, sc: position of greater than, semicolon character local ierr = "" -- ierr: errors already in input (in error style span tag) local carg = args[argn] -- atc: produces " - Char 123; " for error messages | defined at while carg ~= nil do -- tags: table of html tags | the start if carg
"<" then local gt = mw.ustring.find(carg, ">", n, true) if gt
tags.sspan then ct = t[#t] -- current t if ct
"rn" then rn = rn - 1 elseif ct
0 then err=err.."Unbalanced \""..tags.sspan.."\" tag found"..atc(tc) else t[#t] = nil end elseif tag
tags.doubleov then dv = dv + 1 t[#t + 1] = "dv" if dv > 1 then err=err..dv.." nested double overline tags found"..atc(tc) end elseif tag
tags.rnsize then rn = rn + 1 t[#t + 1] = "rn" if rn > 1 then err=err..rn.." nested rn tags found"..atc(tc) end elseif tag
tags.errs then -- close span tag start, end point local csp, cep = mw.ustring.find(carg, tags.sspan, n, true) ierr = ierr .. ", " .. mw.ustring.sub(carg, n+1, csp-1) n = cep else err=err.."Unknown tag \""..tag.."\" found"..atc(tc) t[#t + 1] = "uk" end end elseif cc
"&" then local sc = mw.ustring.find(carg, ";", n, true) if sc
"|" or tag
"̅" or tag
"̅" then tc = tc + 1 if ovc+1 < tc then err=err.."Overline character is not over a roman numeral"..atc(tc) end rnseq[#rnseq] = rnseq[#rnseq] + 6 ovl[#rnseq] = ovl[#rnseq] + 1 ovc = tc elseif cc
nil then err=err.."Unknown character \""..cc.."\" found"..atc(tc) else -- vb: vertical bar modifier local vb = 0 if p then vb = 1 end rnseq[#rnseq + 1] = rn_ref[ccu] + ov*6 + dv*12 + vb*4 tcc[#rnseq], ovl[#rnseq], vbr[#rnseq] = tc, ov + dv*2, vb ovc = tc -- for error message purposes ^ end end end end argn = argn + 1 carg = args[argn] end if argn
0 then return -1, "No roman numerals found" else num, err1 = todec1(rnseq, ovl, vbr, tcc) if err ~="" then err = "Syntax errors: "..mw.ustring.sub(err, 1, -3).." " end if err1 ~="" then err=err.."Roman numeral usage errors: "..err1.." " end if ierr ~="" then err=err.."Errors already in the input: "..mw.ustring.sub(ierr, 3).." " end if err ~= "" then err = mw.ustring.sub(err, 1, -3) end return num, err endend
function p.todecimal(frame) local fargs = frame.args if fargs.d
"0" then -- Normal mode if num
"" then if num ~= -1 then return num else return disperr("Unknown error 3") end else if num
"1" then -- Supress errors if num
"2" then -- Display all if disp
"" then disp = "[num]\\n [err]\\e [time]\\t" end tim = os.clock disp = unesc(disp) disp = disp:gsub("\\n", num) disp = disp:gsub("\\e", err) disp = disp:gsub("\\t", tim) return disp else return disperr("Unknown mode") endend
function p.todecimald(roman) num, err = todec return num, err, os.clockend
-- Decimal to roman numeral --
local function torom1 (dec1) -- For <5000 subunit local function torom2 (dec2, a, b, c) local rom3 = "" if dec2
"2" then rom3 = a..a elseif dec2
"4" then rom3 = a..b elseif dec2
"6" then rom3 = b..a elseif dec2
"8" then rom3 = b..a..a..a elseif dec2
local function torom (dec, rndisp) local err, ierr = "", "" -- ierr: errors already in the input local rn, rc = "", "" if rndisp then rn = tags.rn rc = tags.sspan -- end local floor = math.floor if type(dec)
nil then err = err .. "Not a number; " dect = dec:gsub("[^%d]", "") if dect
nil then return -1, "Not a number or string" end end if dec < 1 then return -1, "Input ("..dec..") is less than 1" else local dec, frp = math.modf(dec) -- frp: fractional part if frp ~= 0 then err=err.."Input has fractional part "..frp..", ignoring...; " end local romt = local rdec = dec -- rdec: remaining dec local od = tags.doubleov local ov = tags.overline local cl = tags.sspan -- close local vb = tags.pipe -- vertical bar if dec >= 5e9 then err = err .. "Input is 5,000,000,000 (5e9) or greater; " local ov = floor(math.log10(dec/5)/3) local cdec = 0 -- ov: number of overlines for i = ov, 3, -1 do cdec = floor(rdec/10^(i*3)) rdec = rdec - cdec*10^(i*3) local romt2 = torom1(cdec) local romt1 = for j=1, #romt2 do romt1[j] = romt2:sub(j, j) end romt1[#romt1+1] = "" if rndisp then size = tags.rnsize else size = tags.nrnsize end romt[#romt+1] = size..table.concat(romt1, string.rep("̅",i))..cl end end if dec >= 5e8 then cdec = floor(rdec /1e6) rdec = rdec - cdec*1e6 romt[#romt+1] = od..rn..torom1(cdec)..rc..cl end if dec >= 5e6 then cdec = floor(rdec /1e5) rdec = rdec - cdec*1e5 romt[#romt+1] = vb..ov..rn..torom1(cdec)..rc..cl..vb end if dec >= 5e3 then cdec = floor(rdec /1e3) rdec = rdec - cdec*1e3 romt[#romt+1] = ov..rn..torom1(cdec)..rc..cl end cdec = rdec romt[#romt+1] = rn..torom1(cdec)..rc rom = table.concat(romt, " ") end if err ~= "" then err = mw.ustring.sub(err, 1, -3).." " end if ierr ~="" then err = err.."Errors already in the input: "..mw.ustring.sub(ierr, 3).." " end if err ~= "" then err = mw.ustring.sub(err, 1, -3) end return rom, errend
function p.fromdecimal(frame) fargs = frame.args if fargs.d
"1" then rn = true else rn = false end local rom, err = torom(args[1], rn) if mode
nil then return disperr("Unknown error 6") end if err
-1 then return disperr(err) else return rom.." "..disperr(err) end end elseif mode
nil then rom = -2 end return rom elseif mode
"0" or disp
function p.fromdecimald(dec, rn) if rn
return p