Pumunta sa nilalaman

Module:relation

Mula Wiksiyonaryo


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