Modul:Expr: Unterschied zwischen den Versionen
(+ Schnüffelkat) |
(2022-08-20) |
||
Zeile 1: | Zeile 1: | ||
--[=[ | local Expr = { suite = "Expr", | ||
serial = "2022-08-20", | |||
item = 54991461 } | |||
--[==[ | |||
Expr | Expr | ||
* average | |||
* base62 | |||
* crossTotal | |||
* decimal2minsec | |||
* figure | |||
* max | * max | ||
* min | * min | ||
* minsec2decimal | |||
* modulo | |||
* percent | |||
* Ramanujan | |||
* random | |||
* sum | |||
* TemplateAverage | |||
* TemplateBooland | |||
* TemplateBoolor | |||
* TemplateMax | * TemplateMax | ||
* TemplateMin | * TemplateMin | ||
* TemplateSum | |||
* booland | * booland | ||
]=] | * boolor | ||
]==] | |||
local Failsafe = Expr | |||
Expr.messagePrefix = "lua-module-Expr-" | |||
Expr.l10nDef = {} | |||
l10nDef[ "en" ] = { | Expr.l10nDef[ "en" ] = { | ||
ErrorExpr = "Error in mathematical expression, function#parameter" | ErrorExpr = "Error in mathematical expression, function#parameter" | ||
} | } | ||
l10nDef[ "de" ] = { | Expr.l10nDef[ "de" ] = { | ||
ErrorExpr = "Fehler in mathematischem Ausdruck, Funktion#Parameter" | ErrorExpr = "Fehler in mathematischem Ausdruck, Funktion#Parameter" | ||
} | } | ||
Expr.breakFigures = { [","] = ",", | |||
["."] = "%.", | |||
["'"] = "'", | |||
["',"] = "[',]", | |||
["'."] = "['%.]", | |||
[" "] = " ", | |||
["U+A0"] = mw.ustring.char( 0xA0 ), | |||
["U+202F"] = mw.ustring.char( 0x202F ), | |||
["%s"] = mw.ustring.char( 91, 0x20, | |||
0xA0, | |||
0x202F, 93 ), | |||
[".%s"] = mw.ustring.char( 91, 0x20, | |||
0x2E, | |||
0xA0, | |||
0x202F, 93 ), | |||
["'%s"] = mw.ustring.char( 91, 0x20, | |||
0x27, | |||
0xA0, | |||
0x202F, 93 ), | |||
["'.%s"] = mw.ustring.char( 91, 0x20, | |||
0x27, | |||
0x2E, | |||
0xA0, | |||
0x202F, 93 ) | |||
} | |||
Zeile 41: | Zeile 69: | ||
-- Retrieve localized message string in content language | -- Retrieve localized message string in content language | ||
-- Precondition: | -- Precondition: | ||
-- say -- string | -- say -- string, message ID | ||
-- Postcondition: | -- Postcondition: | ||
-- Return some message string | -- Return some message string | ||
-- Uses: | -- Uses: | ||
-- > messagePrefix | -- > Expr.messagePrefix | ||
-- > l10nDef | -- > Expr.l10nDef | ||
-- mw.language.getContentLanguage() | -- mw.language.getContentLanguage() | ||
-- mw.message.new() | -- mw.message.new() | ||
local c = mw.language.getContentLanguage():getCode() | local c = mw.language.getContentLanguage():getCode() | ||
local m = mw.message.new( messagePrefix .. say ) | local m = mw.message.new( Expr.messagePrefix .. say ) | ||
local r = false | local r = false | ||
if m:isBlank() then | if m:isBlank() then | ||
local l10n = l10nDef[ c ] | local l10n = Expr.l10nDef[ c ] | ||
if not l10n then | if not l10n then | ||
l10n = l10nDef[ "en" ] | l10n = Expr.l10nDef[ "en" ] | ||
end | end | ||
r = l10n[ say ] | r = l10n[ say ] | ||
Zeile 68: | Zeile 96: | ||
end -- factory() | end -- factory() | ||
local function eval( source | |||
local function eval( source ) | |||
-- Evaluate expression | -- Evaluate expression | ||
-- Precondition: | -- Precondition: | ||
-- source -- string | -- source -- string, mathematical expression | ||
return mw.ext.ParserFunctions.expr( source ) | |||
return | |||
end -- eval() | end -- eval() | ||
Zeile 80: | Zeile 110: | ||
-- Safe evaluation of presumable expression | -- Safe evaluation of presumable expression | ||
-- Precondition: | -- Precondition: | ||
-- source -- string | -- source -- string, mathematical expression | ||
-- frame -- object | -- frame -- object | ||
-- show -- string | -- show -- string, details about source | ||
-- Postcondition: | -- Postcondition: | ||
-- throws error, if expression failed | -- throws error, if expression failed | ||
Zeile 88: | Zeile 118: | ||
-- Uses: | -- Uses: | ||
-- factory() | -- factory() | ||
local lucky, r = pcall( eval, source | local lucky, r = pcall( eval, source ) | ||
local n = tonumber( r, 10 ) | local n = tonumber( r, 10 ) | ||
if not lucky | if not ( lucky and n ) then | ||
r = r .. " " .. factory( "ErrorExpr" ) | r = r .. " " .. factory( "ErrorExpr" ) | ||
.. " ''" .. show .. "'' (" .. source .. ")" | .. " ''" .. show .. "'' (" .. source .. ")" | ||
Zeile 101: | Zeile 131: | ||
local function | |||
-- | local function ellipse( a, epsilon ) | ||
-- | -- Circumference of an ellipse. Approximation by Ramanujan's formula. | ||
-- Returns the approximation and a locical value (true, if the data is well) | |||
epsilon = tonumber(epsilon) or false; | |||
a = tonumber(a) or false; | |||
if not epsilon then return 0, false; end | |||
-- | if not a then return 0, false; end | ||
- | if epsilon < 0 or epsilon > 1 then return 0, false; end | ||
a = math.abs(a); | |||
local b = a * math.sqrt (1 - epsilon * epsilon); | |||
local lambda = (a - b) / (a + b); | |||
if | local circumference = math.pi * (a + b) * (1 + (3 * lambda * lambda)/(10 + math.sqrt (4 - 3 * lambda * lambda))); | ||
if circumference then | |||
return circumference, true; | |||
else | |||
return 0, false; | |||
end | |||
end | |||
local function logicaland( args ) | |||
local r = "" | |||
for k, v in pairs( args ) do | |||
if mw.text.trim( v ) == "" then | |||
r = "" | r = "" | ||
break -- for k, v | |||
else | |||
r = "1" | |||
r = " | |||
end | end | ||
end | end -- for k, v | ||
return r | return r | ||
end | end | ||
function | |||
local function logicalor( args ) | |||
local r = "" | |||
for k, v in pairs( args ) do | |||
v = mw.text.trim( v ) | |||
if v ~= "" and | |||
v ~= "0" then | |||
v = v:lower( v ) | |||
if v ~= "false" and | |||
v ~= "falsch" and | |||
v ~= "nein" then | |||
r = "1" | |||
break -- for k, v | |||
end | |||
end | |||
end -- for k, v | |||
return r | return r | ||
end | end | ||
local function minmax( params, frame, low, lazy ) | local function minmax( params, frame, low, lazy ) | ||
-- Find extremum of unnamed params values | -- Find extremum of unnamed params values | ||
-- Precondition: | -- Precondition: | ||
-- params -- table | -- params -- table, like args | ||
-- .minus | -- .minus | ||
-- .zeroBlank | -- .zeroBlank | ||
-- frame -- object | -- frame -- object | ||
-- low -- true: minimum | -- low -- true: minimum, false: maximum | ||
-- lazy -- true: try numeric result | -- lazy -- true: try numeric result, false: return string | ||
-- Postcondition: | -- Postcondition: | ||
-- throws error, if expression failed | -- throws error, if expression failed | ||
Zeile 279: | Zeile 272: | ||
return r | return r | ||
end -- minmax() | end -- minmax() | ||
Expr.average = function ( array, ask ) | |||
-- Calculate average | |||
-- Precondition: | |||
-- array -- sequence table, with strings and/or numbers | |||
-- ask -- string or not, with figure format | |||
-- Postcondition: | |||
-- returns number, at least 0 | |||
local r, n = Expr.sum( array, ask ) | |||
if n > 1 then | |||
r = r / n | |||
end | |||
return r | |||
end -- Expr.average() | |||
Expr.base62 = function ( adjust ) | |||
-- Convert number from and to base62 encoding | |||
-- Precondition: | |||
-- adjust -- number or ASCII string to be converted | |||
-- number: to base62 | |||
-- string: base62 to number | |||
-- Lua limitation at 10^53; larger numbers are less precise | |||
-- Postcondition: | |||
-- returns string, or number, or false | |||
local r = false | |||
local state = type( adjust ) | |||
if state == "number" then | |||
local k = math.floor( adjust ) | |||
if k == adjust and adjust > 0 then | |||
local m | |||
r = "" | |||
while k > 0 do | |||
m = k % 62 | |||
k = ( k - m ) / 62 | |||
if m >= 36 then | |||
m = m + 61 | |||
elseif m >= 11 then | |||
m = m + 55 | |||
else | |||
m = m + 48 | |||
end | |||
r = string.char( m ) .. r | |||
end | |||
elseif adjust == 0 then | |||
r = "0" | |||
end | |||
elseif state == "string" then | |||
if adjust:match( "^%w+$" ) then | |||
local n = #adjust | |||
local k = 1 | |||
local c | |||
r = 0 | |||
for i = n, 1, -1 do | |||
c = adjust:byte( i, i ) | |||
if c >= 48 and c <= 57 then | |||
c = c - 48 | |||
elseif c >= 65 and c <= 90 then | |||
c = c - 55 | |||
elseif c >= 97 and c <= 122 then | |||
c = c - 61 | |||
else -- How comes? | |||
r = nil | |||
break -- for i | |||
end | |||
r = r + c * k | |||
k = k * 62 | |||
end -- for i | |||
end | |||
end | |||
return r | |||
end -- Expr.base62() | |||
Expr.crossTotal = function ( amount ) | |||
local r = 0 | |||
local s = Expr.figure( amount ) | |||
if s then | |||
if s < 0 then | |||
s = -1 * s | |||
end | |||
s = tostring( math.floor( s ) ) | |||
for i = 1, #s do | |||
r = r + tonumber( s:sub( i, i ) ) | |||
end -- for i | |||
else | |||
r = 0 | |||
end | |||
return r | |||
end -- Expr.crossTotal() | |||
Expr.decimal2minsec = function ( amount, align, ask ) | |||
-- Format coordinate value in degree, minutes, seconds | |||
-- Precondition: | |||
-- amount -- string or number, with decimal coordinate | |||
-- align -- string, number, nil, with number of decimal digits | |||
-- ask -- string or not, with figure format | |||
-- Postcondition: | |||
-- Returns string -- with formatted data, "0" if any problem | |||
local r = Expr.figure( amount, ask ) | |||
if r then | |||
local d = tonumber( align ) | |||
local e = mw.html.create( "span" ) | |||
local kd, km, low | |||
if r < 0 then | |||
low = true | |||
r = -1 * r | |||
end | |||
kd = math.floor( r ) | |||
r = ( r - kd ) * 60 | |||
if kd > 360 then | |||
kd = kd - math.floor( kd / 360 ) * 360 | |||
end | |||
if low then | |||
kd = -1 * kd | |||
end | |||
km = math.floor( r ) | |||
r = ( r - km ) * 60 | |||
if d and d >= 1 and d < 10 then | |||
local s = string.format( "%%.%df", math.floor( d ) ) | |||
local n | |||
r = string.format( s, r ) | |||
n = math.floor( r ) | |||
if r == n then | |||
r = tostring( n ) | |||
else | |||
r = r:gsub( "^(-?%d+%.%d*[1-9])0+$", "%1" ) | |||
end | |||
else | |||
r = tostring( math.floor( r + 0.5 ) ) | |||
end | |||
if not Expr.degminsec then | |||
Expr.degminsec = string.format( "%%d%s %%d%s %%s%s", | |||
mw.ustring.char( 0xB0 ), | |||
mw.ustring.char( 0x2032 ), | |||
mw.ustring.char( 0x2033 ) ) | |||
end | |||
r = string.format( Expr.degminsec, kd, km, r ) | |||
e:css( "white-space", "nowrap" ) | |||
:addClass( "coordinate-deg-min-sec" ) | |||
e:wikitext( r ) | |||
r = tostring( e ) | |||
else | |||
r = "0" | |||
end | |||
return r | |||
end -- Expr.decimal2minsec() | |||
Expr.figure = function ( amount, ask ) | |||
-- Convert number from various formats | |||
-- Precondition: | |||
-- amount -- string (or number), with number | |||
-- ask -- string, with permitted formatting, defaults to "." | |||
-- Postcondition: | |||
-- Returns number, or false | |||
-- 2022-08-08 | |||
local seek = type( amount ) | |||
local r | |||
if seek == "string" then | |||
seek = ask or "." | |||
if type( seek ) == "string" then | |||
local scan = mw.text.trim( amount ) | |||
if scan:find( "[Ee]" ) then | |||
scan = scan:match( "^[+%-]?([%.%d]+)[Ee][+%-]?%d+$" ) | |||
if scan and | |||
( scan:match( "^%.%d+$" ) or | |||
scan:match( "^%d+%.?%d*$" ) ) then | |||
r = tonumber( amount ) | |||
end | |||
else | |||
local low, split | |||
if seek == "" then | |||
seek = "." | |||
end | |||
split = seek:sub( -1 ) | |||
seek = seek:sub( 1, -2 ) | |||
if seek:sub( 1, 1 ) == "-" then | |||
seek = seek:sub( 2 ) | |||
if mw.ustring.sub( scan, 1, 1 ) | |||
== mw.ustring.char( 0x2212 ) then | |||
low = true | |||
scan = mw.ustring.sub( scan, 2 ) | |||
end | |||
end | |||
if not low then | |||
if scan:sub( 1, 1 ) == "-" then | |||
low = true | |||
scan = scan:sub( 2 ) | |||
elseif scan:sub( 1, 1 ) == "+" then | |||
scan = scan:sub( 2 ) | |||
end | |||
end | |||
if ( split == "." or split == "," ) and | |||
not seek:find( split, 1, true ) then | |||
local i = scan:find( split, 1, true ) | |||
if i then | |||
split = scan:sub( i + 1 ) | |||
if split == "" then | |||
split = false | |||
end | |||
if i > 1 then | |||
r = scan:sub( 1, i - 1 ) | |||
elseif split then | |||
r = "" | |||
else | |||
r = false | |||
end | |||
else | |||
split = false | |||
r = scan | |||
end | |||
if r then | |||
seek = Expr.breakFigures[ seek ] | |||
if seek then | |||
local f = function ( a ) | |||
local rf = a | |||
if rf:find( "&.+;" ) then | |||
rf = mw.text.decode( rf, | |||
true ) | |||
end | |||
rf = mw.ustring.gsub( rf, | |||
seek, | |||
"%1%2" ) | |||
return rf | |||
end | |||
seek = "(%d)" .. seek .. "(%d)" | |||
if r ~= "" then | |||
r = f( r ) | |||
end | |||
if split then | |||
split = f( split ) | |||
end | |||
end | |||
if split and | |||
not split:match( "^%d+$" ) then | |||
r = false | |||
end | |||
if r and | |||
not r:match( "^%d+$" ) then | |||
r = false | |||
end | |||
if r and split then | |||
r = string.format( "%s.%s", r, split ) | |||
end | |||
end | |||
end | |||
if r then | |||
r = tonumber( r ) | |||
if low then | |||
r = -1 * r | |||
end | |||
end | |||
end | |||
end | |||
elseif seek == "number" then | |||
r = amount | |||
end | |||
return r or false | |||
end -- Expr.figure() | |||
Expr.minsec2decimal = function ( aDeg, aMin, aSec, alter, ask ) | |||
-- Convert coordinate value from degree, minutes, seconds, letter | |||
-- Precondition: | |||
-- aDeg -- string or number, with degree | |||
-- aMin -- string or number, with minutes | |||
-- aSec -- string or number, with seconds | |||
-- alter -- string or boolean, true|S|W, negative sign | |||
-- ask -- string, with permitted formatting, defaults to "." | |||
local r = Expr.figure( aDeg, ask ) | |||
if r then | |||
local qm = Expr.figure( aMin, ask ) | |||
local qt = Expr.figure( aSec, ask ) | |||
local m = 360 | |||
local less | |||
if not qt or qm then | |||
r = r + qm * 0.0166666666666667 | |||
if qt then | |||
r = r + qt * 0.0002777777777777778 | |||
end | |||
end | |||
if alter then | |||
local s = type( alter ) | |||
if s == "string" then | |||
s = mw.text.trim( alter ):upper() | |||
if s == "S" or s == "W" then | |||
less = true | |||
end | |||
if s == "N" or s == "S" then | |||
m = 180 | |||
end | |||
elseif s == "boolean" then | |||
less = true | |||
end | |||
end | |||
if r < 0 then | |||
r = -1 * r | |||
less = true | |||
end | |||
if r > 0 then | |||
r = r - math.floor( r / m ) * m | |||
end | |||
if less then | |||
r = -1 * r | |||
end | |||
end | |||
return r or 0 | |||
end -- Expr.minsec2decimal() | |||
Expr.modulo = function ( amount, adjust, ask ) | |||
-- Retrieve modulo remainder | |||
-- Precondition: | |||
-- amount -- string or number, with total amount (dividend) | |||
-- adjust -- string or number, with modulo divisor, non-zero | |||
-- ask -- string or not, with figure format | |||
-- Postcondition: | |||
-- Returns number -- with modulo remainder | |||
-- 0 -- if numbers are not available | |||
local qt = Expr.figure( amount, ask ) | |||
local qm = Expr.figure( adjust, ask ) | |||
local r | |||
if qt and qm and qm ~= 0 then | |||
r = qt - math.floor( qt / qm ) * qm | |||
else | |||
r = 0 | |||
end | |||
return r | |||
end -- Expr.modulo() | |||
Expr.percent = function ( amount, all, align, after, ask, allow, frame ) | |||
-- Retrieve percentage | |||
-- Precondition: | |||
-- amount -- string or number, with partial value | |||
-- all -- string or number, with base value (100%) | |||
-- align -- string, number, nil, with number of decimal digits | |||
-- after -- true, if trailing zeroes shall be kept | |||
-- ask -- string or not, with figure format | |||
-- allow -- true, if unformatted | |||
-- frame -- object, if available | |||
-- Postcondition: | |||
-- Returns string -- with formatted percentage, terminated by % | |||
-- 0 -- if numbers are not available | |||
local qp = Expr.figure( amount, ask ) | |||
local qb = Expr.figure( all, ask ) | |||
local r | |||
if qp and qb and qb ~= 0 then | |||
local d = tonumber( align ) | |||
r = qp * 100 / qb | |||
if d and d >= 1 and d < 10 then | |||
local s = string.format( "%%.%df", math.floor( d ) ) | |||
s = string.format( s, r ) | |||
if after then | |||
r = s | |||
else | |||
local n = math.floor( r ) | |||
if tonumber( s ) == n then | |||
r = tostring( n ) | |||
else | |||
r = s:gsub( "^(-?%d+%.%d*[1-9])0+$", "%1" ) | |||
end | |||
end | |||
else | |||
r = tostring( math.floor( r + 0.5 ) ) | |||
end | |||
if not allow then | |||
if not Expr.frame then | |||
Expr.frame = frame or mw.getCurrentFrame() | |||
end | |||
r = Expr.frame:callParserFunction( "formatnum", r ) | |||
end | |||
r = r .. " %" | |||
else | |||
r = 0 | |||
end | |||
return r | |||
end -- Expr.percent() | |||
Expr.sum = function ( array, ask ) | |||
-- Calculate sum | |||
-- Precondition: | |||
-- array -- sequence table, with strings and/or numbers | |||
-- ask -- string or not, with figure format | |||
-- Postcondition: | |||
-- returns -- 1, number, with sum, at least 0 | |||
-- -- 2, number, of summands, at least 0 | |||
local r1 = 0 | |||
local r2 = 0 | |||
if type( array ) == "table" then | |||
for k, v in pairs( array ) do | |||
v = Expr.figure( v, ask ) | |||
if v then | |||
r1 = r1 + v | |||
r2 = r2 + 1 | |||
end | |||
end -- for k, v | |||
end | |||
return r1, r2 | |||
end -- Expr.sum() | |||
Failsafe.failsafe = function ( atleast ) | |||
-- Retrieve versioning and check for compliance | |||
-- Precondition: | |||
-- atleast -- string, with required version | |||
-- or wikidata|item|~|@ or false | |||
-- Postcondition: | |||
-- Returns string -- with queried version/item, also if problem | |||
-- false -- if appropriate | |||
-- 2020-08-17 | |||
local since = atleast | |||
local last = ( since == "~" ) | |||
local linked = ( since == "@" ) | |||
local link = ( since == "item" ) | |||
local r | |||
if last or link or linked or since == "wikidata" then | |||
local item = Failsafe.item | |||
since = false | |||
if type( item ) == "number" and item > 0 then | |||
local suited = string.format( "Q%d", item ) | |||
if link then | |||
r = suited | |||
else | |||
local entity = mw.wikibase.getEntity( suited ) | |||
if type( entity ) == "table" then | |||
local seek = Failsafe.serialProperty or "P348" | |||
local vsn = entity:formatPropertyValues( seek ) | |||
if type( vsn ) == "table" and | |||
type( vsn.value ) == "string" and | |||
vsn.value ~= "" then | |||
if last and vsn.value == Failsafe.serial then | |||
r = false | |||
elseif linked then | |||
if mw.title.getCurrentTitle().prefixedText | |||
== mw.wikibase.getSitelink( suited ) then | |||
r = false | |||
else | |||
r = suited | |||
end | |||
else | |||
r = vsn.value | |||
end | |||
end | |||
end | |||
end | |||
end | |||
end | |||
if type( r ) == "nil" then | |||
if not since or since <= Failsafe.serial then | |||
r = Failsafe.serial | |||
else | |||
r = false | |||
end | |||
end | |||
return r | |||
end -- Failsafe.failsafe() | |||
-- Export | -- Export | ||
local p = {} | local p = {} | ||
function p.average( frame ) | |||
local d = { } | |||
for k, v in pairs( frame.args ) do | |||
k = tostring( k ) | |||
if k:match( "^%d+$" ) then | |||
table.insert( d, v ) | |||
end | |||
end -- for k, v | |||
return Expr.average( d, frame.args.parse ) | |||
end | |||
function p.base62( frame ) | function p.base62( frame ) | ||
Zeile 297: | Zeile 774: | ||
s2 = false | s2 = false | ||
end | end | ||
r = base62( s ) | r = Expr.base62( s ) | ||
if r and not s2 then | if r and not s2 then | ||
r = | r = string.format( "%17d", r ) | ||
end | end | ||
end | end | ||
return r or "" | return r or "" | ||
end | end | ||
function p.crossTotal( frame ) | |||
return Expr.crossTotal( frame.args[ 1 ] ) | |||
end | |||
function p.decimal2minsec( frame ) | |||
return Expr.decimal2minsec( frame.args[ 1 ], | |||
frame.args[ 2 ], | |||
frame.args.parse ) | |||
end | |||
p.figure = function ( frame ) | |||
return Expr.figure( frame.args[ 1 ], frame.args[ 2 ] ) or "" | |||
end -- p.figure | |||
function p.max( frame ) | function p.max( frame ) | ||
Zeile 313: | Zeile 804: | ||
local lucky, r = pcall( minmax, frame.args, frame, true, false ) | local lucky, r = pcall( minmax, frame.args, frame, true, false ) | ||
return r or "" | return r or "" | ||
end | |||
function p.minsec2decimal( frame ) | |||
return Expr.minsec2decimal( frame.args[ 1 ], | |||
frame.args[ 2 ], | |||
frame.args[ 3 ], | |||
frame.args[ 4 ], | |||
frame.args.parse ) | |||
end | |||
function p.modulo( frame ) | |||
return Expr.modulo( frame.args[ 1 ], | |||
frame.args[ 2 ], | |||
frame.args[ 3 ] ) | |||
end | |||
function p.percent( frame ) | |||
local base = frame.args[ 2 ] | |||
local leave, low, pars | |||
if base then | |||
pars = frame.args | |||
else | |||
pars = frame:getParent().args | |||
base = pars[ 2 ] | |||
end | |||
leave = pars[ 4 ] | |||
if leave then | |||
leave = mw.text.trim( leave ) | |||
if leave == "" or leave == "0" then | |||
leave = false | |||
end | |||
end | |||
low = pars.low | |||
if low == "" or low == "0" then | |||
low = false | |||
end | |||
return Expr.percent( pars[ 1 ], base, pars[ 3 ], leave, | |||
pars.parse, low, frame ) | |||
end | |||
function p.Ramanujan( frame ) | |||
local semiaxis = frame.args[1] or 0; | |||
local eps = frame.args[2] or 0; | |||
local value, isOk = ellipse( semiaxis,eps ); | |||
if isOk then | |||
return tostring(value); | |||
else | |||
return '<span class="error">Funktion Ramanujan in Modul Expr: ungültige Parameter!</span>' | |||
end | |||
end | |||
function p.random( frame ) | |||
local n = Expr.figure( frame.args[ 1 ] ) | |||
if n and n >= 2 then | |||
n = math.floor( n ) | |||
else | |||
n = 100 | |||
end | |||
math.randomseed( math.floor( 100000 * os.clock() ) ) | |||
return math.random( 0, n ) | |||
end | |||
function p.sum( frame ) | |||
local d = { } | |||
local r, n | |||
for k, v in pairs( frame.args ) do | |||
k = tostring( k ) | |||
if k:match( "^%d+$" ) then | |||
table.insert( d, v ) | |||
end | |||
end -- for k, v | |||
r, n = Expr.sum( d, frame.args.parse ) | |||
return r | |||
end | |||
function p.TemplateAverage( frame ) | |||
return p.average( frame:getParent() ) | |||
end | |||
function p.TemplateBooland( frame ) | |||
return logicaland( frame:getParent().args ) | |||
end | |||
function p.TemplateBoolor( frame ) | |||
return logicalor( frame:getParent().args ) | |||
end | end | ||
Zeile 322: | Zeile 898: | ||
return p.min( frame:getParent() ) | return p.min( frame:getParent() ) | ||
end | end | ||
function p.TemplateSum( frame ) | |||
function p. | return p.sum( frame:getParent() ) | ||
end | end | ||
function p. | function p.booland( frame ) | ||
return logicaland( frame:getParent().args ) | |||
end | end | ||
function p. | function p.boolor( frame ) | ||
return logicalor( frame:getParent().args ) | |||
end | end | ||
p.failsafe = function ( frame ) | |||
local | -- Versioning interface | ||
if | local s = type( frame ) | ||
local since | |||
if s == "table" then | |||
since = frame.args[ 1 ] | |||
elseif s == "string" then | |||
since = frame | |||
end | |||
if since then | |||
since = mw.text.trim( since ) | |||
if since == "" then | |||
since = false | |||
end | |||
end | end | ||
return | return Failsafe.failsafe( since ) or "" | ||
end -- .Expr() | end -- p.failsafe | ||
p.Expr = function () | |||
return Expr | |||
end -- p.Expr() | |||
setmetatable( p, { __call = function ( func, ... ) | |||
setmetatable( p, nil ); | |||
return Failsafe; | |||
end } ); | |||
return p | return p |
Version vom 24. August 2022, 14:43 Uhr
Die Dokumentation für dieses Modul kann unter Modul:Expr/doc erstellt werden
local Expr = { suite = "Expr", serial = "2022-08-20", item = 54991461 } --[==[ Expr * average * base62 * crossTotal * decimal2minsec * figure * max * min * minsec2decimal * modulo * percent * Ramanujan * random * sum * TemplateAverage * TemplateBooland * TemplateBoolor * TemplateMax * TemplateMin * TemplateSum * booland * boolor ]==] local Failsafe = Expr Expr.messagePrefix = "lua-module-Expr-" Expr.l10nDef = {} Expr.l10nDef[ "en" ] = { ErrorExpr = "Error in mathematical expression, function#parameter" } Expr.l10nDef[ "de" ] = { ErrorExpr = "Fehler in mathematischem Ausdruck, Funktion#Parameter" } Expr.breakFigures = { [","] = ",", ["."] = "%.", ["'"] = "'", ["',"] = "[',]", ["'."] = "['%.]", [" "] = " ", ["U+A0"] = mw.ustring.char( 0xA0 ), ["U+202F"] = mw.ustring.char( 0x202F ), ["%s"] = mw.ustring.char( 91, 0x20, 0xA0, 0x202F, 93 ), [".%s"] = mw.ustring.char( 91, 0x20, 0x2E, 0xA0, 0x202F, 93 ), ["'%s"] = mw.ustring.char( 91, 0x20, 0x27, 0xA0, 0x202F, 93 ), ["'.%s"] = mw.ustring.char( 91, 0x20, 0x27, 0x2E, 0xA0, 0x202F, 93 ) } local function factory( say ) -- Retrieve localized message string in content language -- Precondition: -- say -- string, message ID -- Postcondition: -- Return some message string -- Uses: -- > Expr.messagePrefix -- > Expr.l10nDef -- mw.language.getContentLanguage() -- mw.message.new() local c = mw.language.getContentLanguage():getCode() local m = mw.message.new( Expr.messagePrefix .. say ) local r = false if m:isBlank() then local l10n = Expr.l10nDef[ c ] if not l10n then l10n = Expr.l10nDef[ "en" ] end r = l10n[ say ] else m:inLanguage( c ) r = m:plain() end if not r then r = "(((" .. say .. ")))" end return r end -- factory() local function eval( source ) -- Evaluate expression -- Precondition: -- source -- string, mathematical expression return mw.ext.ParserFunctions.expr( source ) end -- eval() local function expr( source, frame, show ) -- Safe evaluation of presumable expression -- Precondition: -- source -- string, mathematical expression -- frame -- object -- show -- string, details about source -- Postcondition: -- throws error, if expression failed -- returns number with resulting figure -- Uses: -- factory() local lucky, r = pcall( eval, source ) local n = tonumber( r, 10 ) if not ( lucky and n ) then r = r .. " " .. factory( "ErrorExpr" ) .. " ''" .. show .. "'' (" .. source .. ")" error( r, 0 ) else r = n end return r end -- expr() local function ellipse( a, epsilon ) -- Circumference of an ellipse. Approximation by Ramanujan's formula. -- Returns the approximation and a locical value (true, if the data is well) epsilon = tonumber(epsilon) or false; a = tonumber(a) or false; if not epsilon then return 0, false; end if not a then return 0, false; end if epsilon < 0 or epsilon > 1 then return 0, false; end a = math.abs(a); local b = a * math.sqrt (1 - epsilon * epsilon); local lambda = (a - b) / (a + b); local circumference = math.pi * (a + b) * (1 + (3 * lambda * lambda)/(10 + math.sqrt (4 - 3 * lambda * lambda))); if circumference then return circumference, true; else return 0, false; end end local function logicaland( args ) local r = "" for k, v in pairs( args ) do if mw.text.trim( v ) == "" then r = "" break -- for k, v else r = "1" end end -- for k, v return r end local function logicalor( args ) local r = "" for k, v in pairs( args ) do v = mw.text.trim( v ) if v ~= "" and v ~= "0" then v = v:lower( v ) if v ~= "false" and v ~= "falsch" and v ~= "nein" then r = "1" break -- for k, v end end end -- for k, v return r end local function minmax( params, frame, low, lazy ) -- Find extremum of unnamed params values -- Precondition: -- params -- table, like args -- .minus -- .zeroBlank -- frame -- object -- low -- true: minimum, false: maximum -- lazy -- true: try numeric result, false: return string -- Postcondition: -- throws error, if expression failed -- returns number, or -- string if formatting required, or -- false if no data provided -- Uses: -- expr() local k, v, n, scope local light = ( params.minus ~= "-" ) local luxury = ( params.minus and light ) local c = mw.ustring.char( 8722 ) -- minus local scan = "^%s*%-?[0-9]*%.?[0-9]*%s*$" local r = false for k, v in pairs( params ) do if type( k ) == "number" then scope = type( v ) if scope == "string" then if v:match( "^%s*$" ) then n = false else if mw.ustring.match( v, c ) then luxury = light v = mw.ustring.gsub( v, c, "-" ) end if not mw.ustring.match( v, scan ) then if low then scope = "min()#" else scope = "max()#" end scope = scope .. tostring( k ) v = expr( v, frame, scope ) end n = tonumber( v ) end elseif scope == "number" then n = v else n = false end if n then if r then if low then if n < r then r = n end else if n > r then r = n end end else r = n end end end end -- for k, v if r then if luxury and r < 0 then r = c .. tostring( -1 * r ) elseif not lazy then if r == 0 then if params.zeroBlank then r = "" else r = "0" end else r = tostring( r ) end end end return r end -- minmax() Expr.average = function ( array, ask ) -- Calculate average -- Precondition: -- array -- sequence table, with strings and/or numbers -- ask -- string or not, with figure format -- Postcondition: -- returns number, at least 0 local r, n = Expr.sum( array, ask ) if n > 1 then r = r / n end return r end -- Expr.average() Expr.base62 = function ( adjust ) -- Convert number from and to base62 encoding -- Precondition: -- adjust -- number or ASCII string to be converted -- number: to base62 -- string: base62 to number -- Lua limitation at 10^53; larger numbers are less precise -- Postcondition: -- returns string, or number, or false local r = false local state = type( adjust ) if state == "number" then local k = math.floor( adjust ) if k == adjust and adjust > 0 then local m r = "" while k > 0 do m = k % 62 k = ( k - m ) / 62 if m >= 36 then m = m + 61 elseif m >= 11 then m = m + 55 else m = m + 48 end r = string.char( m ) .. r end elseif adjust == 0 then r = "0" end elseif state == "string" then if adjust:match( "^%w+$" ) then local n = #adjust local k = 1 local c r = 0 for i = n, 1, -1 do c = adjust:byte( i, i ) if c >= 48 and c <= 57 then c = c - 48 elseif c >= 65 and c <= 90 then c = c - 55 elseif c >= 97 and c <= 122 then c = c - 61 else -- How comes? r = nil break -- for i end r = r + c * k k = k * 62 end -- for i end end return r end -- Expr.base62() Expr.crossTotal = function ( amount ) local r = 0 local s = Expr.figure( amount ) if s then if s < 0 then s = -1 * s end s = tostring( math.floor( s ) ) for i = 1, #s do r = r + tonumber( s:sub( i, i ) ) end -- for i else r = 0 end return r end -- Expr.crossTotal() Expr.decimal2minsec = function ( amount, align, ask ) -- Format coordinate value in degree, minutes, seconds -- Precondition: -- amount -- string or number, with decimal coordinate -- align -- string, number, nil, with number of decimal digits -- ask -- string or not, with figure format -- Postcondition: -- Returns string -- with formatted data, "0" if any problem local r = Expr.figure( amount, ask ) if r then local d = tonumber( align ) local e = mw.html.create( "span" ) local kd, km, low if r < 0 then low = true r = -1 * r end kd = math.floor( r ) r = ( r - kd ) * 60 if kd > 360 then kd = kd - math.floor( kd / 360 ) * 360 end if low then kd = -1 * kd end km = math.floor( r ) r = ( r - km ) * 60 if d and d >= 1 and d < 10 then local s = string.format( "%%.%df", math.floor( d ) ) local n r = string.format( s, r ) n = math.floor( r ) if r == n then r = tostring( n ) else r = r:gsub( "^(-?%d+%.%d*[1-9])0+$", "%1" ) end else r = tostring( math.floor( r + 0.5 ) ) end if not Expr.degminsec then Expr.degminsec = string.format( "%%d%s %%d%s %%s%s", mw.ustring.char( 0xB0 ), mw.ustring.char( 0x2032 ), mw.ustring.char( 0x2033 ) ) end r = string.format( Expr.degminsec, kd, km, r ) e:css( "white-space", "nowrap" ) :addClass( "coordinate-deg-min-sec" ) e:wikitext( r ) r = tostring( e ) else r = "0" end return r end -- Expr.decimal2minsec() Expr.figure = function ( amount, ask ) -- Convert number from various formats -- Precondition: -- amount -- string (or number), with number -- ask -- string, with permitted formatting, defaults to "." -- Postcondition: -- Returns number, or false -- 2022-08-08 local seek = type( amount ) local r if seek == "string" then seek = ask or "." if type( seek ) == "string" then local scan = mw.text.trim( amount ) if scan:find( "[Ee]" ) then scan = scan:match( "^[+%-]?([%.%d]+)[Ee][+%-]?%d+$" ) if scan and ( scan:match( "^%.%d+$" ) or scan:match( "^%d+%.?%d*$" ) ) then r = tonumber( amount ) end else local low, split if seek == "" then seek = "." end split = seek:sub( -1 ) seek = seek:sub( 1, -2 ) if seek:sub( 1, 1 ) == "-" then seek = seek:sub( 2 ) if mw.ustring.sub( scan, 1, 1 ) == mw.ustring.char( 0x2212 ) then low = true scan = mw.ustring.sub( scan, 2 ) end end if not low then if scan:sub( 1, 1 ) == "-" then low = true scan = scan:sub( 2 ) elseif scan:sub( 1, 1 ) == "+" then scan = scan:sub( 2 ) end end if ( split == "." or split == "," ) and not seek:find( split, 1, true ) then local i = scan:find( split, 1, true ) if i then split = scan:sub( i + 1 ) if split == "" then split = false end if i > 1 then r = scan:sub( 1, i - 1 ) elseif split then r = "" else r = false end else split = false r = scan end if r then seek = Expr.breakFigures[ seek ] if seek then local f = function ( a ) local rf = a if rf:find( "&.+;" ) then rf = mw.text.decode( rf, true ) end rf = mw.ustring.gsub( rf, seek, "%1%2" ) return rf end seek = "(%d)" .. seek .. "(%d)" if r ~= "" then r = f( r ) end if split then split = f( split ) end end if split and not split:match( "^%d+$" ) then r = false end if r and not r:match( "^%d+$" ) then r = false end if r and split then r = string.format( "%s.%s", r, split ) end end end if r then r = tonumber( r ) if low then r = -1 * r end end end end elseif seek == "number" then r = amount end return r or false end -- Expr.figure() Expr.minsec2decimal = function ( aDeg, aMin, aSec, alter, ask ) -- Convert coordinate value from degree, minutes, seconds, letter -- Precondition: -- aDeg -- string or number, with degree -- aMin -- string or number, with minutes -- aSec -- string or number, with seconds -- alter -- string or boolean, true|S|W, negative sign -- ask -- string, with permitted formatting, defaults to "." local r = Expr.figure( aDeg, ask ) if r then local qm = Expr.figure( aMin, ask ) local qt = Expr.figure( aSec, ask ) local m = 360 local less if not qt or qm then r = r + qm * 0.0166666666666667 if qt then r = r + qt * 0.0002777777777777778 end end if alter then local s = type( alter ) if s == "string" then s = mw.text.trim( alter ):upper() if s == "S" or s == "W" then less = true end if s == "N" or s == "S" then m = 180 end elseif s == "boolean" then less = true end end if r < 0 then r = -1 * r less = true end if r > 0 then r = r - math.floor( r / m ) * m end if less then r = -1 * r end end return r or 0 end -- Expr.minsec2decimal() Expr.modulo = function ( amount, adjust, ask ) -- Retrieve modulo remainder -- Precondition: -- amount -- string or number, with total amount (dividend) -- adjust -- string or number, with modulo divisor, non-zero -- ask -- string or not, with figure format -- Postcondition: -- Returns number -- with modulo remainder -- 0 -- if numbers are not available local qt = Expr.figure( amount, ask ) local qm = Expr.figure( adjust, ask ) local r if qt and qm and qm ~= 0 then r = qt - math.floor( qt / qm ) * qm else r = 0 end return r end -- Expr.modulo() Expr.percent = function ( amount, all, align, after, ask, allow, frame ) -- Retrieve percentage -- Precondition: -- amount -- string or number, with partial value -- all -- string or number, with base value (100%) -- align -- string, number, nil, with number of decimal digits -- after -- true, if trailing zeroes shall be kept -- ask -- string or not, with figure format -- allow -- true, if unformatted -- frame -- object, if available -- Postcondition: -- Returns string -- with formatted percentage, terminated by % -- 0 -- if numbers are not available local qp = Expr.figure( amount, ask ) local qb = Expr.figure( all, ask ) local r if qp and qb and qb ~= 0 then local d = tonumber( align ) r = qp * 100 / qb if d and d >= 1 and d < 10 then local s = string.format( "%%.%df", math.floor( d ) ) s = string.format( s, r ) if after then r = s else local n = math.floor( r ) if tonumber( s ) == n then r = tostring( n ) else r = s:gsub( "^(-?%d+%.%d*[1-9])0+$", "%1" ) end end else r = tostring( math.floor( r + 0.5 ) ) end if not allow then if not Expr.frame then Expr.frame = frame or mw.getCurrentFrame() end r = Expr.frame:callParserFunction( "formatnum", r ) end r = r .. " %" else r = 0 end return r end -- Expr.percent() Expr.sum = function ( array, ask ) -- Calculate sum -- Precondition: -- array -- sequence table, with strings and/or numbers -- ask -- string or not, with figure format -- Postcondition: -- returns -- 1, number, with sum, at least 0 -- -- 2, number, of summands, at least 0 local r1 = 0 local r2 = 0 if type( array ) == "table" then for k, v in pairs( array ) do v = Expr.figure( v, ask ) if v then r1 = r1 + v r2 = r2 + 1 end end -- for k, v end return r1, r2 end -- Expr.sum() Failsafe.failsafe = function ( atleast ) -- Retrieve versioning and check for compliance -- Precondition: -- atleast -- string, with required version -- or wikidata|item|~|@ or false -- Postcondition: -- Returns string -- with queried version/item, also if problem -- false -- if appropriate -- 2020-08-17 local since = atleast local last = ( since == "~" ) local linked = ( since == "@" ) local link = ( since == "item" ) local r if last or link or linked or since == "wikidata" then local item = Failsafe.item since = false if type( item ) == "number" and item > 0 then local suited = string.format( "Q%d", item ) if link then r = suited else local entity = mw.wikibase.getEntity( suited ) if type( entity ) == "table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues( seek ) if type( vsn ) == "table" and type( vsn.value ) == "string" and vsn.value ~= "" then if last and vsn.value == Failsafe.serial then r = false elseif linked then if mw.title.getCurrentTitle().prefixedText == mw.wikibase.getSitelink( suited ) then r = false else r = suited end else r = vsn.value end end end end end end if type( r ) == "nil" then if not since or since <= Failsafe.serial then r = Failsafe.serial else r = false end end return r end -- Failsafe.failsafe() -- Export local p = {} function p.average( frame ) local d = { } for k, v in pairs( frame.args ) do k = tostring( k ) if k:match( "^%d+$" ) then table.insert( d, v ) end end -- for k, v return Expr.average( d, frame.args.parse ) end function p.base62( frame ) local r local s = frame.args[ 1 ] if s then local s2 = frame.args[ 2 ] if s2 then s2 = mw.text.trim( s2 ) end if s2 == "D2B" then s = tonumber( s ) else s = mw.text.trim( s ) s2 = false end r = Expr.base62( s ) if r and not s2 then r = string.format( "%17d", r ) end end return r or "" end function p.crossTotal( frame ) return Expr.crossTotal( frame.args[ 1 ] ) end function p.decimal2minsec( frame ) return Expr.decimal2minsec( frame.args[ 1 ], frame.args[ 2 ], frame.args.parse ) end p.figure = function ( frame ) return Expr.figure( frame.args[ 1 ], frame.args[ 2 ] ) or "" end -- p.figure function p.max( frame ) local lucky, r = pcall( minmax, frame.args, frame, false, false ) return r or "" end function p.min( frame ) local lucky, r = pcall( minmax, frame.args, frame, true, false ) return r or "" end function p.minsec2decimal( frame ) return Expr.minsec2decimal( frame.args[ 1 ], frame.args[ 2 ], frame.args[ 3 ], frame.args[ 4 ], frame.args.parse ) end function p.modulo( frame ) return Expr.modulo( frame.args[ 1 ], frame.args[ 2 ], frame.args[ 3 ] ) end function p.percent( frame ) local base = frame.args[ 2 ] local leave, low, pars if base then pars = frame.args else pars = frame:getParent().args base = pars[ 2 ] end leave = pars[ 4 ] if leave then leave = mw.text.trim( leave ) if leave == "" or leave == "0" then leave = false end end low = pars.low if low == "" or low == "0" then low = false end return Expr.percent( pars[ 1 ], base, pars[ 3 ], leave, pars.parse, low, frame ) end function p.Ramanujan( frame ) local semiaxis = frame.args[1] or 0; local eps = frame.args[2] or 0; local value, isOk = ellipse( semiaxis,eps ); if isOk then return tostring(value); else return '<span class="error">Funktion Ramanujan in Modul Expr: ungültige Parameter!</span>' end end function p.random( frame ) local n = Expr.figure( frame.args[ 1 ] ) if n and n >= 2 then n = math.floor( n ) else n = 100 end math.randomseed( math.floor( 100000 * os.clock() ) ) return math.random( 0, n ) end function p.sum( frame ) local d = { } local r, n for k, v in pairs( frame.args ) do k = tostring( k ) if k:match( "^%d+$" ) then table.insert( d, v ) end end -- for k, v r, n = Expr.sum( d, frame.args.parse ) return r end function p.TemplateAverage( frame ) return p.average( frame:getParent() ) end function p.TemplateBooland( frame ) return logicaland( frame:getParent().args ) end function p.TemplateBoolor( frame ) return logicalor( frame:getParent().args ) end function p.TemplateMax( frame ) return p.max( frame:getParent() ) end function p.TemplateMin( frame ) return p.min( frame:getParent() ) end function p.TemplateSum( frame ) return p.sum( frame:getParent() ) end function p.booland( frame ) return logicaland( frame:getParent().args ) end function p.boolor( frame ) return logicalor( frame:getParent().args ) end p.failsafe = function ( frame ) -- Versioning interface local s = type( frame ) local since if s == "table" then since = frame.args[ 1 ] elseif s == "string" then since = frame end if since then since = mw.text.trim( since ) if since == "" then since = false end end return Failsafe.failsafe( since ) or "" end -- p.failsafe p.Expr = function () return Expr end -- p.Expr() setmetatable( p, { __call = function ( func, ... ) setmetatable( p, nil ); return Failsafe; end } ); return p