モジュール:Expr

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  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
end
else
r = n
end
end
end
end -- for k, v
if r then
if luxury and r  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 = 65  and  c = 97  and  c   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  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   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 == "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 = 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  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 Funktion Ramanujan in Modul Expr: ungültige Parameter!'
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