Open main menu
Home
Random
Donate
Recent changes
Special pages
Community portal
Preferences
About Stockhub
Disclaimers
Search
User menu
Talk
Contributions
Create account
Log in
Editing
Module:Sandbox/Squc/Roman
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
-- Module to convert Roman numerals and reject invalid numerals local p={} local tags = { overline = '<span style="text-decoration:overline;">', doubleov = '<span style="border-top:double 3px">', rn = '<span style="font-family:serif; font-size:118%;">', rnsize = '<span style="font-family:serif; font-size:122%;">', nrnsize = '<span style="font-size:114%;">', errs = '<span class="error">', sspan = '</span>', pipe = '|', } local function atc(cn, rp) local s = " - ''" if rp ~= nil then s = s.."roman numeral "..tostring(rp)..", " end s = s.."char "..tostring(cn).."''; " return s end local function unesc( s ) s = s:gsub("\\p", tags.pipe) s = s:gsub("\\\\", "\\") s = s:gsub("\\=", "\=") return s end local function disperr(err) return tags.errs..err..tags.sspan end local rn_ref = {I=1, V=2, X=3, L=4, C=5, D=6, M=7} local ref_rn = {[1]="I", [2]="V", [3]="X", [4]="L", [5]="C", [6]="D", [7]="M"} 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 == 0 then crn = 10^cex else crn = 5*10^cex end tc = tcc[i] local function rncg(j, ia) if j == 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 == 1 then if rn == 13 and rncr == 7 then rncr, ov, vb = 4, 1, 1 elseif rncr == 7 then rncr = 1 ov = ov + 1 else rncr = rncr + 1 end end rnc = ref_rn[rncr] local rnc_vb = "" if vb == 1 then rnc_vb = "|" end rnc = rnc_vb .. rnc .. string.rep("Μ ", ov) .. rnc_vb return rnc end rnc = rncg() if crn < prn or prn == 0 then num = num + prn*run run = 1 elseif crn == prn then if cfr == 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 then -- e.g. occurs after crn > prn (below) e.g. "XCC" err = err.."Repeat after subtraction - " .. rncg(-2) .. rncg(-1) .. rnc .. atc(tc, i) run = 1 -- In "XCC", assume "XC" is a unit, so the current "C" is counted separately. else run = run + 1 end elseif cfr == 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 == 0.5 then -- e.g. "LC" for 50 err = err..rnc.." cannot follow "..rncg(-1).." (Cannot subtract from " .. tostring(prn) .. ")" ..atc(tc,i) elseif run > 2 then -- e.g. "XXXL" for 20 err = err .. "Number of " .. rncg(-1) .. " before " .. rnc .. " must be at most two" .. atc(tc, i) end num = num - prn*run + crn run = 0 else return -1, ("Unknown error 2") end prn = crn pex = cex pfr = cfr end num = num + prn*run if err ~= "" then err = err:sub(1, -3) end return num, err end 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 -- ovc: position of overline character (U+0305) modified roman numeral pn = pn + 1 -- (or another overline character) if p then p = false else p = true end else local n = 0 local cc = "" local cplen = mw.ustring.len -- codepoint length while n < cplen(carg) do n = n + 1 cc = mw.ustring.sub(carg, n, n) if cc == "<" then local gt = mw.ustring.find(carg, ">", n, true) if gt == nil then tc = tc + 1 err=err.."Unbalanced '<' found"..atc(tc) else tag = mw.ustring.sub(carg, n, gt) local taglen = cplen(tag) n = n + taglen - 1 if tag == tags.sspan then ct = t[#t] -- current t if ct == "ov" then ov = ov - 1 elseif ct == "rn" then rn = rn - 1 elseif ct == "dv" then dv = dv - 1 end if #t == 0 then err=err.."Unbalanced \""..tags.sspan.."\" tag found"..atc(tc) else t[#t] = nil end elseif tag == tags.overline then ov = ov + 1 t[#t + 1] = "ov" if ov > 1 then err=err..ov.." nested overline tags found"..atc(tc) 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.rn or 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.nrnsize then -- Large font size span tag t[#t + 1] = "sz" -- for overlines to show properly 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 tc = tc + 1 -- spaces elseif cc == "&" then local sc = mw.ustring.find(carg, ";", n, true) if sc == nil then tc = tc + 1 err=err.."Extra character '&' found"..atc(tc) else tag = mw.ustring.sub(carg, n, sc) tc = tc + cplen(tag) n = n + cplen(tag) - 1 if tag == "|" or tag == "s" then pn = pn + 1 if p then p = false else p = true end elseif tag == "̅" or tag == "̅" then 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 else err=err.."Unknown tag \""..tag.."\" found"..atc(tc) end end elseif cc == "Μ " 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 == "|" then -- Possible by calling from another module pn = pn + 1 if p then p = false else p = true end else tc = tc + 1 ccu = cc:upper() if rn_ref[ccu] == 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, "Input is empty" elseif #rnseq == 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 end end function p.todecimal( frame ) local fargs = frame.args if fargs.d == "0" then pframe = frame:getParent() args = pframe.args else args = fargs end mode = fargs.mode or "0" disp = fargs.disp or "0" local num, err = todec(args) if mode == "0" then -- Normal mode if num == nil then return disperr("Unknown error 4") end if err == "" then if num ~= -1 then return num else return disperr("Unknown error 3") end else if num == -1 then return disperr(err) else return num.." "..disperr(err) end end elseif mode == "1" then -- Supress errors if num == nil then num = -2 end return num elseif mode == "2" then -- Display all if disp == "0" or 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") end end function p.todecimald( roman ) num, err = todec{ roman } return num, err, os.clock() end -- Decimal to roman numeral -- local function torom1 (dec1) -- For <5000 subunit local function torom2 (dec2, a, b, c) local rom3 = "" if dec2=="1" then rom3 = a elseif dec2=="2" then rom3 = a..a elseif dec2=="3" then rom3 = a..a..a elseif dec2=="4" then rom3 = a..b elseif dec2=="5" then rom3 = b elseif dec2=="6" then rom3 = b..a elseif dec2=="7" then rom3 = b..a..a elseif dec2=="8" then rom3 = b..a..a..a elseif dec2=="9" then rom3 = a..c end return rom3 end dec1 = tostring(dec1) local dec2 = string.rep("0",4-#dec1)..dec1 local a = {[2]="C", [3]="X", [4]="I"} local b = {[2]="D", [3]="L", [4]="V"} local c = {[2]="M", [3]="C", [4]="X"} local rom2 = { ""..string.rep("M", tonumber(dec2:sub(1,1)) ) } for i=2, 4 do rom2[i] = torom2(dec2:sub(i,i), a[i], b[i], c[i]) end local rom1 = table.concat(rom2) return rom1 end 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 -- </span> end local floor = math.floor if type(dec) == "string" then -- sp, ep: start point, end point errsp, errep = mw.ustring.find(dec, tags.errs, 1, true) -- error position (if present) while errsp do endsp, endep = mw.ustring.find(dec, tags.sspan, errep, true) if endsp then ierr = ierr .. ", " .. mw.ustring.sub(dec, errep+1, endsp-1) dec = mw.ustring.sub(dec, 1, errsp-1)..mw.ustring.sub(dec, endep+1) end errsp, errep = mw.ustring.find(dec, tags.errs, 1, true) end local ton = tonumber(dec) if ton == nil then err = err .. "Not a number; " dect = dec:gsub("[^%d]", "") if dect=="" then return -1, "No digits" else err=err.."Extra characters '"..dec:gsub("%d","").."' found; " dec = dec:gsub("[^%d.]", "") dec = tonumber(dec) end else dec = ton end elseif type(dec) ~= "number" then local ton = tonumber(dec) if ton == 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, err end function p.fromdecimal( frame ) fargs = frame.args if fargs.d == "0" then pframe = frame:getParent() args = pframe.args else args = fargs end mode = fargs.mode or "0" disp = fargs.disp or "0" local rn if fargs.rn == "1" then rn = true else rn = false end local rom, err = torom(args[1], rn) if mode == "0" then -- Normal mode if rom == nil then return disperr("Unknown error 6") end if err == "" then if rom ~= -1 then return rom else return disperr("Unknown error 5") end else if rom == -1 then return disperr(err) else return rom.." "..disperr(err) end end elseif mode == "1" then -- No error mode if rom == nil then rom = -2 end return rom elseif mode == "2" then -- Display all if disp == "0" or disp == "" then disp = "[rom]\\r [err]\\e [time]\\t" end tim = os.clock() disp:unesc() disp:gsub("\\r", rom) disp:gsub("\\e", err) disp:gsub("\\t", tim) return disp else return disperr("Unknown mode") end end function p.fromdecimald( dec, rn ) if rn == "1" then rndisp = true else rndisp = false end rom, err = torom(dec, rndisp) return rom, err, os.clock() end return p
Summary:
Please note that all contributions to Stockhub may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
Stockhub:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Templates used on this page:
Template:Mlx
(
edit
)
Module:Sandbox/Squc/Roman
(
edit
)
Module:Sandbox/Squc/Roman/doc
(
edit
)