Zum Inhalt springen

„Modul:Expr“ – Versionsunterschied

aus Wikipedia, der freien Enzyklopädie
[gesichtete Version][gesichtete Version]
Inhalt gelöscht Inhalt hinzugefügt
K not case sensitive
KKeine Bearbeitungszusammenfassung
Markierung: Manuelle Zurücksetzung
 
(12 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local Expr = { suite = "Expr",
--[=[ 2016-05-26
serial = "2022-09-12",
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
]=]
]==]
local Failsafe = Expr






local messagePrefix = "lua-module-Expr-"
Expr.messagePrefix = "lua-module-Expr-"
local l10nDef = {}
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+20"] = " ",
["U+A0"] = mw.ustring.char( 0xA0 ),
["U+202F"] = mw.ustring.char( 0x202F ),
["%s"] = mw.ustring.char( 91, 0x20,
0xA0,
0x2009,
0x202F, 93 ),
[".%s"] = mw.ustring.char( 91, 0x20,
0x2E,
0xA0,
0x2009,
0x202F, 93 ),
["'%s"] = mw.ustring.char( 91, 0x20,
0x27,
0xA0,
0x2009,
0x202F, 93 ),
["'.%s"] = mw.ustring.char( 91, 0x20,
0x27,
0x2E,
0xA0,
0x2009,
0x202F, 93 )
}
Expr.signMinus = mw.ustring.char( 0x2212 )





Zeile 23: Zeile 72:
-- Retrieve localized message string in content language
-- Retrieve localized message string in content language
-- Precondition:
-- Precondition:
-- say -- string; message ID
-- 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 slang = mw.language.getContentLanguage():getCode()
local m = mw.message.new( messagePrefix .. say )
local msg = mw.message.new( Expr.messagePrefix .. say )
local r = false
local r = false
if m:isBlank() then
if msg:isBlank() then
local l10n = l10nDef[ c ]
local def = Expr.l10nDef[ slang ]
if not l10n then
if not def then
l10n = l10nDef[ "en" ]
def = Expr.l10nDef[ "en" ]
end
end
r = l10n[ say ]
r = def[ say ]
else
else
m:inLanguage( c )
msg:inLanguage( slang )
r = m:plain()
r = msg:plain()
end
end
if not r then
if not r then
Zeile 50: Zeile 99:
end -- factory()
end -- factory()



local function eval( source, frame )

-- Evaluate expression
local function faculty( analyze )
-- Test for boolean interpretation
-- Precondition:
-- Precondition:
-- source -- string; mathematical expression
-- analyze -- string or boolean or nil
-- frame -- object
-- Postcondition:
-- returns boolean
return frame:callParserFunction( "#expr", source )
local s = type( analyze )
end -- eval()
local r
if s == "string" then
r = mw.text.trim( analyze )
if r == "" or r == "0" or r == "-" then
r = false
elseif r == "1" then
r = true
else
r = r:lower()
if r == "y" or
r == "yes" or
r == "true" or
r == "on" then
r = true
elseif r == "n" or
r == "no" or
r == "false" or
r == "off" then
r = false
else
if r == "falsch" or r == "nein" then
r = false
-- error( "faculty@Expr", 0 )
else
r = true
end
end
end
elseif s == "boolean" then
r = analyze
elseif s == "nil" then
r = false
else
r = true
end
return r
end -- faculty()





local function expr( source, frame, show )
local function expr( source, show )
-- Safe evaluation of presumable expression
-- Safe evaluation of presumable expression
-- Precondition:
-- Precondition:
-- source -- string; mathematical expression
-- source -- string, mathematical expression
-- frame -- object
-- show -- string, details about source
-- show -- string; details about source
-- Postcondition:
-- Postcondition:
-- throws error, if expression failed
-- throws error, if expression failed
Zeile 70: Zeile 158:
-- Uses:
-- Uses:
-- factory()
-- factory()
local lucky, r = pcall( eval, source, frame )
local lucky, r = pcall( mw.ext.ParserFunctions.expr, source )
local n = tonumber( r, 10 )
local n = tonumber( r, 10 )
if not lucky or n == nil then
if not ( lucky and n ) then
r = r .. " " .. factory( "ErrorExpr" )
r = r .. " " .. factory( "ErrorExpr" )
.. " ''" .. show .. "'' (" .. source .. ")"
.. " ''" .. show .. "'' (" .. source .. ")"
Zeile 83: Zeile 171:





local function base62( value )
local function ellipse( a, epsilon )
-- Convert number from and to base62 encoding
-- Circumference of an ellipse. Approximation by Ramanujan's formula.
-- Precondition:
-- value -- number or string to be converted
-- Returns the approximation and a locical value (true, if the data is well)
epsilon = tonumber(epsilon) or false;
-- number: to base62
a = tonumber(a) or false;
-- string: base62 to number
if not epsilon then return 0, false; end
-- Lua limitation at 10^53; larger numbers are less precise
if not a then return 0, false; end
-- Postcondition:
-- returns string, or number, or false
if epsilon < 0 or epsilon > 1 then return 0, false; end
local r = false
a = math.abs(a);

local state = type( value )
local b = a * math.sqrt (1 - epsilon * epsilon);
if state == "number" then
local k = math.floor( value )
local lambda = (a - b) / (a + b);
local circumference = math.pi * (a + b) * (1 + (3 * lambda * lambda)/(10 + math.sqrt (4 - 3 * lambda * lambda)));
if k == value and value > 0 then
if circumference then
local m
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 = ""
while k > 0 do
break -- for k, v
m = k % 62
else
k = ( k - m ) / 62
r = "1"
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 value == 0 then
r = "0"
end
end
end -- for k, v
elseif state == "string" then
if value:match( "^%w+$" ) then
local n = #value
local k = 1
local c
r = 0
for i = n, 1, -1 do
c = value: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
return r
end
end -- base62()



function logicaland(args)

local r = true;
local k, v, s
local function logicalor( args )
local b
local r = ""
for k, v in pairs(args) do
for k, v in pairs( args ) do
if faculty( v ) then
s = mw.text.trim(v)
b = (s or '') ~= ''
r = "1"
break -- for k, v
r = r and b
end
end
end -- for k, v
return r
return r
end
end


function logicalor(args)
local r = false;
local k, v, s
local b
for k, v in pairs(args) do
s = mw.ustring.lower(mw.text.trim(v) or '');
if s == '' then
b = false;
elseif s=='0' then
b = false;
elseif s=='false' then
b = false;
elseif s=='falsch' then
b = false;
else
b = true;
end
if b then
r = true;
end
end
return r
end



local function minmax( params, frame, low, lazy )
local function minmax( params, low, lazy )
-- Find extremum of unnamed params values
-- Find extremum of unnamed params values
-- Precondition:
-- Precondition:
-- params -- table; like args
-- params -- table, like args
-- .minus
-- .minus
-- .zeroBlank
-- .zeroBlank
-- frame -- object
-- low -- true: minimum, false: maximum
-- low -- true: minimum; false: maximum
-- lazy -- true: try numeric result, false: return string
-- lazy -- true: try numeric result; false: return string
-- Postcondition:
-- Postcondition:
-- throws error, if expression failed
-- throws error, if expression failed
Zeile 191: Zeile 236:
-- false if no data provided
-- false if no data provided
-- Uses:
-- Uses:
-- > Expr.signMinus
-- expr()
-- expr()
local k, v, n, scope
local light = ( params.minus ~= "-" )
local light = ( params.minus ~= "-" )
local luxury = ( params.minus and light )
local luxury = ( params.minus and light )
local c = mw.ustring.char( 8722 ) -- minus
local scan = "^%s*%-?[0-9]*%.?[0-9]*%s*$"
local r = false
local r = false
local n, scope
for k, v in pairs( params ) do
for k, v in pairs( params ) do
if type( k ) == "number" then
if type( k ) == "number" then
scope = type( v )
scope = type( v )
if scope == "string" then
if scope == "string" then
if v:match( "^%s*$" ) then
v = mw.text.trim( v )
if v == "" then
n = false
n = false
else
else
if mw.ustring.match( v, c ) then
if mw.ustring.sub( v, 1, 1 ) == Expr.signMinus then
luxury = light
luxury = light
v = mw.ustring.gsub( v, c, "-" )
v = "-" .. mw.ustring.sub( v, 2 )
end
end
if not mw.ustring.match( v, scan ) then
n = Expr.figure( v, ".", true )
if not n then
if low then
if low then
scope = "min()#"
scope = "min()#"
Zeile 216: Zeile 262:
end
end
scope = scope .. tostring( k )
scope = scope .. tostring( k )
v = expr( v, frame, scope )
expr( v, scope )
end
end
n = tonumber( v )
end
end
elseif scope == "number" then
elseif scope == "number" then
Zeile 244: Zeile 289:
if r then
if r then
if luxury and r < 0 then
if luxury and r < 0 then
r = c .. tostring( -1 * r )
r = Expr.signMinus .. tostring( -1 * r )
elseif not lazy then
elseif not lazy then
if r == 0 then
if r == 0 then
Zeile 259: Zeile 304:
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 )
-- Calculate sum of digits in integer number
-- Precondition:
-- amount -- string or number, with integer
-- Postcondition:
-- returns number, at least 0
local r = 0
local s = Expr.figure( amount )
if s then
if s < 0 then
s = -1 * s
end
s = tostring( math.floor( s ) )
if s:match( "^%d+$" ) then
for i = 1, #s do
r = r + tonumber( s:sub( i, i ) )
end -- for i
end
end
return r
end -- Expr.crossTotal()



Expr.decimal2minsec = function ( amount, align, ask, allow, frame )
-- 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
-- allow -- true, if unformatted result
-- frame -- object, if available
-- Postcondition:
-- returns mw.html -- with formatted data, or
-- string -- with "0" if any problem
-- Uses:
-- > Expr.signMinus
local r = Expr.figure( amount, ask )
if r then
local d = tonumber( align )
local e = mw.html.create( "span" )
local kd, km, low, sd
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
sd = tostring( kd )
if low then
sd = Expr.signMinus .. sd
end
km = math.floor( r )
r = ( r - km ) * 60
if d and d >= 1 and d < 10 then
local n = math.floor( r )
if r == n then
r = tostring( n )
else
local s = string.format( "%%.%df", math.floor( d ) )
r = tonumber( string.format( s, r ) )
if allow then
r = tostring( r )
else
if not Expr.frame then
Expr.frame = frame or mw.getCurrentFrame()
end
r = Expr.frame:callParserFunction( "formatnum", r )
end
end
else
r = tostring( math.floor( r + 0.5 ) )
end
if not Expr.degminsec then
Expr.degminsec = string.format( "%%s%s %%d%s %%s%s",
mw.ustring.char( 0xB0 ),
mw.ustring.char( 0x2032 ),
mw.ustring.char( 0x2033 ) )
end
r = string.format( Expr.degminsec, sd, km, r )
e:css( "white-space", "nowrap" )
:addClass( "coordinate-deg-min-sec" )
e:wikitext( r )
r = e
else
r = "0"
end
return r
end -- Expr.decimal2minsec()



Expr.figure = function ( amount, ask, advance, area )
-- Convert number from various formats
-- Precondition:
-- amount -- string (or number), with number
-- ask -- string, with permitted formatting, defaults to "."
-- advance -- true, if expressions permitted
-- area -- string, or not, with permitted set
-- Postcondition:
-- returns number, or false
-- Uses:
-- > Expr.signMinus
-- > Expr.breakFigures
-- 2022-08-08
local seek = type( amount )
local r
if seek == "string" then
local scan = mw.text.trim( amount )
seek = ask or "."
if scan == "" then
seek = false
elseif advance and
not tonumber( scan ) and
scan:find( "[+%-*/)]", 2 ) then
local lucky
lucky, r = pcall( mw.ext.ParserFunctions.expr, scan )
if lucky then
seek = false
r = tonumber( r )
else
r = false
end
end
if type( seek ) == "string" then
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
seek = mw.text.trim( seek )
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 )
== Expr.signMinus 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
if r and type( area ) == "string" then
local set = mw.text.trim( area )
if set == "" then
elseif set == "N" or set == "Z+" then
if r < 0 or
r ~= math.floor( r ) then
r = false
end
elseif set == "Z" then
if r ~= math.floor( r ) then
r = false
end
elseif set == "Z-" then
if r > 0 or
r ~= math.floor( r ) then
r = false
end
elseif set == "R+" then
if r < 0 then
r = false
end
elseif set == "R-" then
if r > 0 then
r = false
end
end
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 qm then
r = r + qm * 0.0166666666666667
if qt then
r = r + qt * 0.0002777777777777778
end
elseif qt then
r = false
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 = alter
end
end
if r then
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
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 result
-- 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, true )
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()




Zeile 264: Zeile 848:
-- 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 279: Zeile 874:
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 = string.format( "%17d", 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 tostring( Expr.decimal2minsec( frame.args[ 1 ],
frame.args[ 2 ],
frame.args.parse,
faculty( frame.args.low ),
frame ) )
end

p.figure = function ( frame )
local r = Expr.figure( frame.args[ 1 ],
frame.args.parse,
faculty( frame.args.expr ),
frame.args.set )
if r then
r = tostring( r )
else
r = ""
end
return r
end -- p.figure


function p.max( frame )
function p.max( frame )
local lucky, r = pcall( minmax, frame.args, frame, false, false )
local lucky, r = pcall( minmax, frame.args, false, false )
return r or ""
return r or ""
end
end


function p.min( frame )
function p.min( frame )
local lucky, r = pcall( minmax, frame.args, frame, true, false )
local lucky, r = pcall( minmax, frame.args, true, false )
return r or ""
return r or ""
end
end


function p.TemplateMax( frame )
function p.minsec2decimal( frame )
return p.max( frame:getParent() )
return Expr.minsec2decimal( frame.args[ 1 ],
frame.args[ 2 ],
frame.args[ 3 ],
frame.args[ 4 ],
frame.args.parse )
end
end


function p.TemplateMin( frame )
function p.modulo( frame )
return p.min( frame:getParent() )
return Expr.modulo( frame.args[ 1 ],
frame.args[ 2 ],
frame.args[ 3 ] )
end
end


function p.booland(frame)
function p.percent( frame )
local fr=frame:getParent()
local base = frame.args[ 2 ]
local pars
return logicaland(fr.args)
if base then
pars = frame.args
else
pars = frame:getParent().args
base = pars[ 2 ]
end
return Expr.percent( pars[ 1 ],
base,
pars[ 3 ],
faculty( pars[ 4 ] ),
pars.parse,
faculty( pars.low ),
frame )
end
end


function p.boolor(frame)
function p.Ramanujan( frame )
local fr=frame:getParent()
local semiaxis = frame.args[1] or 0;
local eps = frame.args[2] or 0;
return logicalor(fr.args)
local value, isOk = ellipse( semiaxis,eps );
if isOk then
return tostring(value);
else
return -1 -- An error code
end
end
end


function p.Expr( f, a )
function p.random( frame )
local r = false
local n = Expr.figure( frame.args[ 1 ] )
if f == "min" or f == "max" then
if n and n >= 2 then
local frame = mw.getCurrentFrame()
n = math.floor( n )
else
local low = ( f == "min" )
local lucky
n = 100
lucky, r = pcall( minmax, a, frame, low, true )
elseif f == "base62" then
r = base62( a )
end
end
math.randomseed( math.floor( 100000 * os.clock() ) )
return math.random( 0, n - 1 )
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
return r
end -- .Expr()
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

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 -- Expr
return p

Aktuelle Version vom 21. September 2024, 02:22 Uhr

Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2022-09-12

PARKEN IM MODUL-NAMENSRAUM AUS SICHTUNGSGRÜNDEN Ein optionaler Parameter erlaubt unterschiedliche Interpretation eines Eingabewerts.

  • Vorgabe ist . mit der Bedeutung „Computer-Format“.
  • „Computer-Format“ bedeutet: . als Dezimaltrennzeichen, Minuszeichen in ASCII, keine Zifferngruppierung („Tausender“). Alternativ kommt die Exponential-Notation mit E in Frage.

Das erlaubte Eingabeformat, falls angegeben, besteht aus ein bis drei Codes mgd mit den Komponenten:

  • d – Pflichtangabe wenn verwendet: Dezimaltrennzeichen. Zulässige Werte: . oder , (nur an letzter Stelle).
  • m – Minuszeichen ist „typografisch“ (Unicode U+2212) erlaubt bei - (nur an erster Stelle).
  • g – Zifferngruppierung („Tausender“, auch Nachkommastellen) erlaubt gemäß nachfolgender Tabelle (optional).
Codes für Ziffergruppierung
g Bedeutung
, Komma, passend zum angloamerikanischen „Computer-Format“.
. Punkt; Standardformatierung in der deutschsprachigen Wikipedia
' ASCII-Hochkomma/Apostroph; üblich mit Bezug zur Schweiz
', ASCII-Hochkomma/Apostroph oder Komma
'. ASCII-Hochkomma/Apostroph oder Punkt
U+20 ASCII-Leerzeichen
Nicht als Zahlenformat geeignet, aber zur Analyse und für qualifizierte Fehlermeldungen
U+A0 Geschütztes Leerzeichen
U+202F Schmales geschütztes Leerzeichen
%s Einfaches oder (auch schmales) geschütztes Leerzeichen
.%s Punkt oder einfaches oder (auch schmales) geschütztes Leerzeichen
'%s ASCII-Hochkomma/Apostroph oder einfaches oder (auch schmales) geschütztes Leerzeichen
'.%s Punkt oder ASCII-Hochkomma/Apostroph oder einfaches oder (auch schmales) geschütztes Leerzeichen

Bei der Eingabe können für die Zifferngruppierung alternativ auch HTML-Entities benutzt werden.

  • Wissenschaftliche oder Exponential-Darstellung erfordert immer das „Computer-Format“; e oder E, Exponent ganzzahlig, + kann der Mantisse und dem Exponent vorangestellt werden.

Beispiele:

  • -., – Format für deutschsprachige Wikis, entsprechend lokalem Wert der Parserfunktion formatnum – Komma als Dezimaltrennzeichen, Punkt als Tausendertrennzeichen, typografisches Minuszeichen erlaubt
  • ,. – klassisches US-Format
  • -'.%s, – breiteste Akzeptanz deutschsprachiger Formate, typografisches Minuszeichen möglich

local Expr = { suite  = "Expr",
               serial = "2022-09-12",
               item   = 54991461 }
--[==[
Expr
* average
* base62
* crossTotal
* decimal2minsec
* figure
* max
* min
* minsec2decimal
* modulo
* percent
* Ramanujan
* random
* sum
* TemplateAverage
* TemplateBooland
* TemplateBoolor
* TemplateMax
* TemplateMin
* TemplateSum
]==]
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+20"]   = " ",
                      ["U+A0"]   = mw.ustring.char( 0xA0 ),
                      ["U+202F"] = mw.ustring.char( 0x202F ),
                      ["%s"]     = mw.ustring.char( 91, 0x20,
                                                        0xA0,
                                                        0x2009,
                                                        0x202F, 93 ),
                      [".%s"]    = mw.ustring.char( 91, 0x20,
                                                        0x2E,
                                                        0xA0,
                                                        0x2009,
                                                        0x202F, 93 ),
                      ["'%s"]    = mw.ustring.char( 91, 0x20,
                                                        0x27,
                                                        0xA0,
                                                        0x2009,
                                                        0x202F, 93 ),
                      ["'.%s"]   = mw.ustring.char( 91, 0x20,
                                                        0x27,
                                                        0x2E,
                                                        0xA0,
                                                        0x2009,
                                                        0x202F, 93 )
                    }
Expr.signMinus = mw.ustring.char( 0x2212 )



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 slang = mw.language.getContentLanguage():getCode()
    local msg = mw.message.new( Expr.messagePrefix .. say )
    local r = false
    if msg:isBlank() then
        local def = Expr.l10nDef[ slang ]
        if not def then
            def = Expr.l10nDef[ "en" ]
        end
        r = def[ say ]
    else
        msg:inLanguage( slang )
        r = msg:plain()
    end
    if not r then
        r = "(((" .. say .. ")))"
    end
    return r
end -- factory()



local function faculty( analyze )
    -- Test for boolean interpretation
    -- Precondition:
    --     analyze  -- string or boolean or nil
    -- Postcondition:
    --     returns boolean
    local s = type( analyze )
    local r
    if s == "string" then
        r = mw.text.trim( analyze )
        if r == ""  or  r == "0"  or  r == "-" then
            r = false
        elseif r == "1" then
            r = true
        else
            r = r:lower()
            if r == "y"  or
               r == "yes"  or
               r == "true"  or
               r == "on" then
                r = true
            elseif r == "n"  or
                   r == "no"  or
                   r == "false"  or
                   r == "off" then
                r = false
            else
                if r == "falsch"  or  r == "nein" then
                    r = false
                    --    error( "faculty@Expr", 0 )
                else
                    r = true
                end
            end
        end
    elseif s == "boolean" then
        r = analyze
    elseif s == "nil" then
        r = false
    else
        r = true
    end
    return r
end -- faculty()



local function expr( source, show )
    -- Safe evaluation of presumable expression
    -- Precondition:
    --     source  -- string, mathematical expression
    --     show    -- string, details about source
    -- Postcondition:
    --     throws error, if expression failed
    --     returns number with resulting figure
    -- Uses:
    --     factory()
    local lucky, r = pcall( mw.ext.ParserFunctions.expr, 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
        if faculty( v ) then
            r = "1"
            break    -- for k, v
        end
    end    -- for k, v
    return r
end



local function minmax( params, low, lazy  )
    -- Find extremum of unnamed params values
    -- Precondition:
    --     params  -- table, like args
    --                       .minus
    --                       .zeroBlank
    --     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.signMinus
    --     expr()
    local light  = ( params.minus ~= "-" )
    local luxury = ( params.minus and light )
    local r      = false
    local n, scope
    for k, v in pairs( params ) do
        if type( k ) == "number" then
            scope = type( v )
            if scope == "string" then
                v = mw.text.trim( v )
                if v == "" then
                    n = false
                else
                    if mw.ustring.sub( v, 1, 1 ) == Expr.signMinus then
                        luxury = light
                        v      = "-" .. mw.ustring.sub( v, 2 )
                    end
                    n = Expr.figure( v, ".", true )
                    if not n then
                        if low then
                            scope = "min()#"
                        else
                            scope = "max()#"
                        end
                        scope = scope .. tostring( k )
                        expr( v, scope )
                    end
                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 = Expr.signMinus .. 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 )
    -- Calculate sum of digits in integer number
    -- Precondition:
    --     amount  -- string or number, with integer
    -- Postcondition:
    --     returns number, at least 0
    local r = 0
    local s = Expr.figure( amount )
    if s then
        if s < 0 then
            s = -1 * s
        end
        s = tostring( math.floor( s ) )
        if s:match( "^%d+$" ) then
            for i = 1, #s do
                r = r  +  tonumber( s:sub( i, i ) )
            end -- for i
        end
    end
    return r
end -- Expr.crossTotal()



Expr.decimal2minsec = function ( amount, align, ask, allow, frame )
    -- 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
    --     allow   -- true, if unformatted result
    --     frame   -- object, if available
    -- Postcondition:
    --     returns  mw.html  -- with formatted data, or
    --              string   -- with "0" if any problem
    -- Uses:
    --     >  Expr.signMinus
    local r = Expr.figure( amount, ask )
    if r then
        local d = tonumber( align )
        local e = mw.html.create( "span" )
        local kd, km, low, sd
        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
        sd = tostring( kd )
        if low then
            sd = Expr.signMinus .. sd
        end
        km = math.floor( r )
        r  = ( r - km )  *  60
        if d  and  d >= 1  and  d < 10 then
            local n = math.floor( r )
            if r == n then
                r = tostring( n )
            else
                local s = string.format( "%%.%df", math.floor( d ) )
                r = tonumber( string.format( s, r ) )
                if allow then
                    r = tostring( r )
                else
                    if not Expr.frame then
                        Expr.frame = frame or mw.getCurrentFrame()
                    end
                    r = Expr.frame:callParserFunction( "formatnum", r )
                end
            end
        else
            r = tostring( math.floor( r + 0.5 ) )
        end
        if not Expr.degminsec then
            Expr.degminsec = string.format( "%%s%s %%d%s %%s%s",
                                            mw.ustring.char( 0xB0 ),
                                            mw.ustring.char( 0x2032 ),
                                            mw.ustring.char( 0x2033 ) )
        end
        r = string.format( Expr.degminsec, sd, km, r )
        e:css( "white-space", "nowrap" )
         :addClass( "coordinate-deg-min-sec" )
        e:wikitext( r )
        r = e
    else
        r = "0"
    end
    return r
end -- Expr.decimal2minsec()



Expr.figure = function ( amount, ask, advance, area )
    -- Convert number from various formats
    -- Precondition:
    --     amount   -- string (or number), with number
    --     ask      -- string, with permitted formatting, defaults to "."
    --     advance  -- true, if expressions permitted
    --     area     -- string, or not, with permitted set
    -- Postcondition:
    --     returns  number, or false
    -- Uses:
    --     >  Expr.signMinus
    --     >  Expr.breakFigures
    -- 2022-08-08
    local seek = type( amount )
    local r
    if seek == "string" then
        local scan = mw.text.trim( amount )
        seek = ask or "."
        if scan == "" then
            seek = false
        elseif advance  and
               not tonumber( scan ) and
               scan:find( "[+%-*/)]", 2 ) then
            local lucky
            lucky, r = pcall( mw.ext.ParserFunctions.expr, scan )
            if lucky then
                seek = false
                r    = tonumber( r )
            else
                r = false
            end
        end
        if type( seek ) == "string" then
            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
                seek = mw.text.trim( seek )
                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 )
                       == Expr.signMinus 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
    if r  and  type( area ) == "string" then
        local set = mw.text.trim( area )
        if set == "" then
        elseif set == "N"  or  set == "Z+" then
            if r < 0  or
               r ~= math.floor( r ) then
                r = false
            end
        elseif set == "Z" then
            if r ~= math.floor( r ) then
                r = false
            end
        elseif set == "Z-" then
            if r > 0  or
               r ~= math.floor( r ) then
                r = false
            end
        elseif set == "R+" then
            if r < 0 then
                r = false
            end
        elseif set == "R-" then
            if r > 0 then
                r = false
            end
        end
    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 qm then
            r = r  +  qm * 0.0166666666666667
            if qt then
                r = r  +  qt * 0.0002777777777777778
            end
        elseif qt then
            r = false
        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 = alter
            end
        end
        if r then
            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
    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 result
    --     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, true )
            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  tostring( Expr.decimal2minsec( frame.args[ 1 ],
                                           frame.args[ 2 ],
                                           frame.args.parse,
                                           faculty( frame.args.low ),
                                           frame ) )
end

p.figure = function ( frame )
    local r = Expr.figure( frame.args[ 1 ],
                           frame.args.parse,
                           faculty( frame.args.expr ),
                           frame.args.set )
    if r then
        r = tostring( r )
    else
        r = ""
    end
    return r
end -- p.figure

function p.max( frame )
    local lucky, r = pcall( minmax, frame.args, false, false )
    return r or ""
end

function p.min( frame )
    local lucky, r = pcall( minmax, frame.args, 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 pars
    if base then
        pars = frame.args
    else
        pars = frame:getParent().args
        base = pars[ 2 ]
    end
    return Expr.percent( pars[ 1 ],
                         base,
                         pars[ 3 ],
                         faculty( pars[ 4 ] ),
                         pars.parse,
                         faculty( pars.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 -1 -- An error code
    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 - 1 )
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

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