Module:relation
Itsura
- This module lacks a documentation subpage. Please create it.
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local math_module = "Module:math"
local byte = string.byte
local compare_number -- defined below
local compare_string -- defined below
local match = string.match
local pcall = pcall
local rawequal = rawequal
local select = select
local sub = string.sub
local type = type
local function sign(...)
sign = require(math_module).sign
return sign(...)
end
--[==[
The equality operator {==} as a function.]==]
function export.equal(a, b)
return a == b
end
export.eq = export.equal
--[==[
The inequality operator {~=} as a function.]==]
function export.not_equal(a, b)
return a ~= b
end
export.ne = export.not_equal
--[==[
Returns {true} if two variables refer to the same object; roughly equivalent to the {{code|py|is}} keyword in Python.
This differs from {==} or {rawequal()}, in that it accounts for strange objects like [[w:NaN|NaN]], which always return false for equality checks. As such, it should only be used when it is absolutely necessary to distinguish objects by identity rather than value (e.g. during memoization), and is not a drop-in replacement for {==} or {rawequal()}.]==]
function export.is(a, b)
-- rawequal() will correctly filter almost everything.
if rawequal(a, b) then
-- If equal, return true unless one is +0 and the other -0, as these
-- behave differently under certain conditions: e.g. 1/(+0) is infinity,
-- but 1/(-0) is -infinity.
return a ~= 0 or 1 / a == 1 / b
end
-- If not equal, return false unless a and b are both NaN and have the same
-- sign. NaN will return false when compared with itself, so compare with
-- ~= (since there's no need for rawequal(), as Lua never invokes an __eq
-- metamethod when comparing an object with itself).
return a ~= a and b ~= b and sign(a) == sign(b)
end
--[==[
The less-than operator {<} as a function, incorporating the fixes for string comparison implemented in {export.compare_string()}.]==]
function export.less_than(a, b)
-- Generate the default value, which will throw any type errors.
local default = a < b
if type(a) == "string" then
return compare_string(a, b)
end
return default
end
export.lt = export.less_than
--[==[
The less-than-or-equal operator {<=} as a function, incorporating the fixes for string comparison implemented in {export.compare_string()}.]==]
function export.less_than_or_equal(a, b)
if a == b then
return true
end
local default = a <= b
if type(a) == "string" then
return compare_string(a, b)
end
return default
end
export.le = export.less_than_or_equal
--[==[
The greater-than operator {>} as a function, incorporating the fixes for string comparison implemented in {export.compare_string()}.]==]
function export.greater_than(a, b)
local default = a > b
if type(a) == "string" then
return compare_string(b, a)
end
return default
end
export.gt = export.greater_than
--[==[
The greater-than-or-equal operator {>=} as a function, incorporating the fixes for string comparison implemented in {export.compare_string()}.]==]
function export.greater_than_or_equal(a, b)
if a == b then
return true
end
local default = a >= b
if type(a) == "string" then
return compare_string(b, a)
end
return default
end
export.ge = export.greater_than_or_equal
--[==[
A comparison function for numbers; it can be used as the sort function with {table.sort}.
In accordance with {{w|IEEE 754}}:
* {{w|NaN}} is sorted as though it has a larger absolute value than infinity ({-NaN < -Inf}; {+Inf < +NaN}).
* {{w|Signed zero}} is acknowledged, with {-0 < +0}.]==]
function export.compare_number(a, b)
-- If < or > return true, neither can be NaN, so return the result.
if a < b then
return true
elseif a > b then
return false
-- If equal, distinguish -0 from +0.
elseif a == b then
return a == 0 and b == 0 and 1 / a < 1 / b or false
-- If <, > and == all return false, one or both must be NaN. If only one is,
-- the result is determined by the sign of the NaN.
elseif a == a then
return sign(b) == 1
elseif b == b then
return sign(a) == -1
end
-- If both are NaN, compare the signs.
return sign(a) < sign(b)
end
compare_number = export.compare_number
--[==[
A comparison function for strings; it can be used as the sort function with {table.sort}.
Note that this uses byte-order for strings, which fixes the issues with {<} raised in [[phab:T193096#4161287]] and [[phab:T49137#9167559]]:
* {<} is supposed to compare UTF-8 codepoints in the two strings, but when a codepoint that is U+10000 or above is encountered in the left-hand string, {<} always returns {false}. This even occurs when the left-hand codepoint is lower than the right, when both are outside of the BMP, resulting in incorrect results.
* Unassigned codepoints and bytes which constitute invalid UTF-8 are considered less than almost all assigned codepoints.]==]
function export.compare_string(a, b)
-- Equality check.
if a == b then
return false
end
-- Byte comparison is slow, so only do it when it's really needed:
-- iterate over both strings, grabbing a set of ASCII bytes followed by
-- a set of non-ASCII bytes from each (either of which could be empty),
-- and compare them with ==. If the ASCII substrings are unequal, just
-- use <, since the bug won't affect it. Otherwise, compare bytes in the
-- non-ASCII substrings.
local loc, ascii_a, nonascii_a, ascii_b, nonascii_b = 1
repeat
ascii_a, nonascii_a = match(a, "^([^\128-\255]*)([\128-\255]*)", loc)
ascii_b, nonascii_b, loc = match(b, "^([^\128-\255]*)([\128-\255]*)()", loc) -- update `loc` on the second call
-- When comparing ASCII sets, use <. The lower substring will be
-- from the lower string *except* when it comprises the start of the
-- other substring and is followed by a non-ASCII character. For
-- instance, if `ascii_a` is "pqrs":
-- If `ascii_b` is "abc", `b` is lower, since "abc" < "pqrs".
-- If `ascii_b` is "pqr" and followed by non-ASCII "ž", `a` is
-- lower, since "pqrs" < "pqrž".
-- If `ascii_b` is "pqr" and at the end of `b`, `b` is lower, since
-- "pqr" < "pqrs".
if ascii_a ~= ascii_b then
if ascii_a < ascii_b then
return nonascii_a == "" or ascii_a ~= sub(ascii_b, 1, #ascii_a)
end
return not (nonascii_b == "" or ascii_b ~= sub(ascii_a, 1, #ascii_b))
end
-- If the non-ASCII parts are not equal, terminate the loop.
until nonascii_a ~= nonascii_b
-- If either one is the empty string, then the end of that string has
-- been reached, making it the lower string.
if nonascii_a == "" then
return true
elseif nonascii_b == "" then
return false
end
loc = 1
while true do
-- 4 bytes at a time is a balance between minimizing the number of
-- byte() calls without grabbing unnecessary extra bytes after the
-- difference.
local b_a1, b_a2, b_a3, b_a4 = byte(nonascii_a, loc, loc + 3)
if b_a1 == nil then
return true
end
local b_b1, b_b2, b_b3, b_b4 = byte(nonascii_b, loc, loc + 3)
if b_a1 ~= b_b1 then
return b_b1 and b_a1 < b_b1
elseif b_a2 ~= b_b2 then
return b_a2 == nil or b_b2 and b_a2 < b_b2
elseif b_a3 ~= b_b3 then
return b_a3 == nil or b_b3 and b_a3 < b_b3
elseif b_a4 ~= b_b4 then
return b_a4 == nil or b_b4 and b_a4 < b_b4
end
loc = loc + 4
end
end
compare_string = export.compare_string
do
local types
local function get_types()
types, get_types = {
["boolean"] = 1,
["number"] = 2,
["string"] = 3,
["table"] = 4,
["function"] = 5,
["thread"] = 6,
["userdata"] = 7,
}, nil
return types
end
-- Called with pcall().
local function _lt(a, b)
return a < b
end
--[==[
A general compare function, which is able to compare two inputs of any type; it can be used as the sort function with {table.sort}.]==]
function export.compare(a, b)
-- Order nil first.
if a == nil then
return b ~= nil
elseif b == nil then
return false
end
local type_a, type_b = type(a), type(b)
if type_a ~= type_b then
return (types or get_types())[type_a] < types[type_b]
-- Strings and number comparison can't be overridden by metamethods.
elseif type_a == "string" then
return compare_string(a, b)
elseif type_a == "number" then
return compare_number(a, b)
end
-- Otherwise, try using a metamethod.
local success, result = pcall(_lt, a, b)
if success then
return result
-- Order true before false by default.
elseif type_a == "boolean" then
return a == true and b == false
end
-- Not comparable.
return false
end
end
--[==[
Logical XOR (exclusive OR): true only if exactly one input argument is true. Considers {false} and {nil} as logically false (falsy), and anything else as logically true (truthy).
Note: for consistency with the in-built {and} and {or} operators, the precise return value depends on the input arguments:
* If one argument is logically true and the other logically false, returns the truthy argument.
* If both are logically true, returns {false}.
* If both are logically false, returns the (falsy) second argument.]==]
function export.xor(a, b)
if not a then
return b
elseif not b then
return a
end
return false
end
--[==[
Given relative index {i} and length {n}, returns the equivalent absolute index.
With relative indices, a positive index counts forward from the start (treating the first item as 1), and a negative index counts backwards from the end (treating the last item as -1). For example:
* {relative_index(2, 5)} returns {2}.
* {relative_index(-2, 5)} returns {4}.
If the {bound} flag is set, then the absolute index is bounded between {0} and {n}. For example:
* {relative_index(-10, 5)} returns {-4}, but {relative_index(-10, 5, true)} returns {0}.
* {relative_index(10, 5)} returns {10}, but {relative_index(10, 5, true)} returns {5}.]==]
function export.relative_index(i, n, bound)
if i < 0 then
i = n + i + 1
end
return not bound and i or
i < 0 and 0 or
i > n and n
end
--[==[
Given an arbitrary number of input arguments, returns the first value which is not {nil}. Roughly equivalent to a chain of `??` operators in various languages.]==]
function export.null_coalesce(v, ...)
if v ~= nil then
return v
end
v = ...
if v ~= nil then
return v
end
for i = 2, select("#", ...) do
local v = select(i, ...)
if v ~= nil then
return v
end
end
end
return export