Compare commits
No commits in common. "3239d6ac95a4bd95a511b66e421d451d92c60135" and "2d43c457e16b6855e298b2dc5d5d204a901701bf" have entirely different histories.
3239d6ac95
...
2d43c457e1
@ -1,732 +0,0 @@
|
|||||||
-- ======================================================================
|
|
||||||
-- ENHANCED CONSTANTS (higher precision)
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.pi = 3.14159265358979323846 -- Replace with higher precision
|
|
||||||
math.tau = 6.28318530717958647693 -- 2*pi, useful for full rotations
|
|
||||||
math.e = 2.71828182845904523536
|
|
||||||
math.phi = 1.61803398874989484820 -- Golden ratio (1 + sqrt(5)) / 2
|
|
||||||
math.sqrt2 = 1.41421356237309504880
|
|
||||||
math.sqrt3 = 1.73205080756887729353
|
|
||||||
math.ln2 = 0.69314718055994530942 -- Natural log of 2
|
|
||||||
math.ln10 = 2.30258509299404568402 -- Natural log of 10
|
|
||||||
math.infinity = 1/0
|
|
||||||
math.nan = 0/0
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- EXTENDED FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
-- Cube root that handles negative numbers correctly
|
|
||||||
function math.cbrt(x)
|
|
||||||
return x < 0 and -(-x)^(1/3) or x^(1/3)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Euclidean distance - more accurate than sqrt(x*x + y*y)
|
|
||||||
function math.hypot(x, y)
|
|
||||||
return math.sqrt(x * x + y * y)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- IEEE 754 NaN check
|
|
||||||
function math.isnan(x)
|
|
||||||
return x ~= x
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if number is finite
|
|
||||||
function math.isfinite(x)
|
|
||||||
return x > -math.infinity and x < math.infinity
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Mathematical sign function
|
|
||||||
function math.sign(x)
|
|
||||||
return x > 0 and 1 or (x < 0 and -1 or 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Constrain value to range
|
|
||||||
function math.clamp(x, min, max)
|
|
||||||
return x < min and min or (x > max and max or x)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Linear interpolation
|
|
||||||
function math.lerp(a, b, t)
|
|
||||||
return a + (b - a) * t
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Smooth interpolation using Hermite polynomial
|
|
||||||
function math.smoothstep(a, b, t)
|
|
||||||
t = math.clamp((t - a) / (b - a), 0, 1)
|
|
||||||
return t * t * (3 - 2 * t)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Map value from input range to output range
|
|
||||||
function math.map(x, in_min, in_max, out_min, out_max)
|
|
||||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Round to nearest integer
|
|
||||||
function math.round(x)
|
|
||||||
return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Round to specified decimal places
|
|
||||||
function math.roundto(x, decimals)
|
|
||||||
local mult = 10 ^ (decimals or 0)
|
|
||||||
return math.floor(x * mult + 0.5) / mult
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Normalize angle to [-π, π] range
|
|
||||||
function math.normalize_angle(angle)
|
|
||||||
return angle - 2 * math.pi * math.floor((angle + math.pi) / (2 * math.pi))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 2D Euclidean distance
|
|
||||||
function math.distance(x1, y1, x2, y2)
|
|
||||||
local dx, dy = x2 - x1, y2 - y1
|
|
||||||
return math.sqrt(dx * dx + dy * dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Factorial with bounds checking
|
|
||||||
function math.factorial(n)
|
|
||||||
if n < 0 or n ~= math.floor(n) or n > 170 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local result = 1
|
|
||||||
for i = 2, n do
|
|
||||||
result = result * i
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Greatest common divisor using Euclidean algorithm
|
|
||||||
function math.gcd(a, b)
|
|
||||||
a, b = math.floor(math.abs(a)), math.floor(math.abs(b))
|
|
||||||
while b ~= 0 do
|
|
||||||
a, b = b, a % b
|
|
||||||
end
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Least common multiple
|
|
||||||
function math.lcm(a, b)
|
|
||||||
if a == 0 or b == 0 then return 0 end
|
|
||||||
return math.abs(a * b) / math.gcd(a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- ENHANCED RANDOM FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
-- Random float in range
|
|
||||||
function math.randomf(min, max)
|
|
||||||
if not min and not max then
|
|
||||||
return math.random()
|
|
||||||
elseif not max then
|
|
||||||
max = min
|
|
||||||
min = 0
|
|
||||||
end
|
|
||||||
return min + math.random() * (max - min)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Random integer in range
|
|
||||||
function math.randint(min, max)
|
|
||||||
if not max then
|
|
||||||
max = min
|
|
||||||
min = 1
|
|
||||||
end
|
|
||||||
return math.floor(math.random() * (max - min + 1) + min)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Random boolean with probability
|
|
||||||
function math.randboolean(p)
|
|
||||||
p = p or 0.5
|
|
||||||
return math.random() < p
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- STATISTICS FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
function math.sum(t)
|
|
||||||
if type(t) ~= "table" then return 0 end
|
|
||||||
local sum = 0
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
sum = sum + t[i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return sum
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.mean(t)
|
|
||||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
|
||||||
local sum = 0
|
|
||||||
local count = 0
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
sum = sum + t[i]
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return count > 0 and sum / count or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.median(t)
|
|
||||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
|
||||||
local nums = {}
|
|
||||||
local count = 0
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
count = count + 1
|
|
||||||
nums[count] = t[i]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if count == 0 then return 0 end
|
|
||||||
table.sort(nums)
|
|
||||||
if count % 2 == 0 then
|
|
||||||
return (nums[count/2] + nums[count/2 + 1]) / 2
|
|
||||||
else
|
|
||||||
return nums[math.ceil(count/2)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.variance(t)
|
|
||||||
if type(t) ~= "table" then return 0 end
|
|
||||||
local count = 0
|
|
||||||
local m = math.mean(t)
|
|
||||||
local sum = 0
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
local dev = t[i] - m
|
|
||||||
sum = sum + dev * dev
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return count > 1 and sum / count or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.stdev(t)
|
|
||||||
return math.sqrt(math.variance(t))
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.pvariance(t)
|
|
||||||
if type(t) ~= "table" then return 0 end
|
|
||||||
local count = 0
|
|
||||||
local m = math.mean(t)
|
|
||||||
local sum = 0
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
local dev = t[i] - m
|
|
||||||
sum = sum + dev * dev
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return count > 0 and sum / count or 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.pstdev(t)
|
|
||||||
return math.sqrt(math.pvariance(t))
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.mode(t)
|
|
||||||
if type(t) ~= "table" or #t == 0 then return nil end
|
|
||||||
local counts = {}
|
|
||||||
local most_frequent = nil
|
|
||||||
local max_count = 0
|
|
||||||
for i=1, #t do
|
|
||||||
local v = t[i]
|
|
||||||
counts[v] = (counts[v] or 0) + 1
|
|
||||||
if counts[v] > max_count then
|
|
||||||
max_count = counts[v]
|
|
||||||
most_frequent = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return most_frequent
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.minmax(t)
|
|
||||||
if type(t) ~= "table" or #t == 0 then return nil, nil end
|
|
||||||
local min, max
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
min = t[i]
|
|
||||||
max = t[i]
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if min == nil then return nil, nil end
|
|
||||||
for i=1, #t do
|
|
||||||
if type(t[i]) == "number" then
|
|
||||||
if t[i] < min then min = t[i] end
|
|
||||||
if t[i] > max then max = t[i] end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return min, max
|
|
||||||
end
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- 2D VECTOR OPERATIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.vec2 = {
|
|
||||||
new = function(x, y)
|
|
||||||
return {x = x or 0, y = y or 0}
|
|
||||||
end,
|
|
||||||
|
|
||||||
copy = function(v)
|
|
||||||
return {x = v.x, y = v.y}
|
|
||||||
end,
|
|
||||||
|
|
||||||
add = function(a, b)
|
|
||||||
return {x = a.x + b.x, y = a.y + b.y}
|
|
||||||
end,
|
|
||||||
|
|
||||||
sub = function(a, b)
|
|
||||||
return {x = a.x - b.x, y = a.y - b.y}
|
|
||||||
end,
|
|
||||||
|
|
||||||
mul = function(a, b)
|
|
||||||
if type(b) == "number" then
|
|
||||||
return {x = a.x * b, y = a.y * b}
|
|
||||||
end
|
|
||||||
return {x = a.x * b.x, y = a.y * b.y}
|
|
||||||
end,
|
|
||||||
|
|
||||||
div = function(a, b)
|
|
||||||
if type(b) == "number" then
|
|
||||||
local inv = 1 / b
|
|
||||||
return {x = a.x * inv, y = a.y * inv}
|
|
||||||
end
|
|
||||||
return {x = a.x / b.x, y = a.y / b.y}
|
|
||||||
end,
|
|
||||||
|
|
||||||
dot = function(a, b)
|
|
||||||
return a.x * b.x + a.y * b.y
|
|
||||||
end,
|
|
||||||
|
|
||||||
length = function(v)
|
|
||||||
return math.sqrt(v.x * v.x + v.y * v.y)
|
|
||||||
end,
|
|
||||||
|
|
||||||
length_squared = function(v)
|
|
||||||
return v.x * v.x + v.y * v.y
|
|
||||||
end,
|
|
||||||
|
|
||||||
distance = function(a, b)
|
|
||||||
local dx, dy = b.x - a.x, b.y - a.y
|
|
||||||
return math.sqrt(dx * dx + dy * dy)
|
|
||||||
end,
|
|
||||||
|
|
||||||
distance_squared = function(a, b)
|
|
||||||
local dx, dy = b.x - a.x, b.y - a.y
|
|
||||||
return dx * dx + dy * dy
|
|
||||||
end,
|
|
||||||
|
|
||||||
normalize = function(v)
|
|
||||||
local len = math.sqrt(v.x * v.x + v.y * v.y)
|
|
||||||
if len > 1e-10 then
|
|
||||||
local inv_len = 1 / len
|
|
||||||
return {x = v.x * inv_len, y = v.y * inv_len}
|
|
||||||
end
|
|
||||||
return {x = 0, y = 0}
|
|
||||||
end,
|
|
||||||
|
|
||||||
rotate = function(v, angle)
|
|
||||||
local c, s = math.cos(angle), math.sin(angle)
|
|
||||||
return {
|
|
||||||
x = v.x * c - v.y * s,
|
|
||||||
y = v.x * s + v.y * c
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
angle = function(v)
|
|
||||||
return math.atan2(v.y, v.x)
|
|
||||||
end,
|
|
||||||
|
|
||||||
lerp = function(a, b, t)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
return {
|
|
||||||
x = a.x + (b.x - a.x) * t,
|
|
||||||
y = a.y + (b.y - a.y) * t
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
reflect = function(v, normal)
|
|
||||||
local dot = v.x * normal.x + v.y * normal.y
|
|
||||||
return {
|
|
||||||
x = v.x - 2 * dot * normal.x,
|
|
||||||
y = v.y - 2 * dot * normal.y
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- 3D VECTOR OPERATIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.vec3 = {
|
|
||||||
new = function(x, y, z)
|
|
||||||
return {x = x or 0, y = y or 0, z = z or 0}
|
|
||||||
end,
|
|
||||||
|
|
||||||
copy = function(v)
|
|
||||||
return {x = v.x, y = v.y, z = v.z}
|
|
||||||
end,
|
|
||||||
|
|
||||||
add = function(a, b)
|
|
||||||
return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z}
|
|
||||||
end,
|
|
||||||
|
|
||||||
sub = function(a, b)
|
|
||||||
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
|
|
||||||
end,
|
|
||||||
|
|
||||||
mul = function(a, b)
|
|
||||||
if type(b) == "number" then
|
|
||||||
return {x = a.x * b, y = a.y * b, z = a.z * b}
|
|
||||||
end
|
|
||||||
return {x = a.x * b.x, y = a.y * b.y, z = a.z * b.z}
|
|
||||||
end,
|
|
||||||
|
|
||||||
div = function(a, b)
|
|
||||||
if type(b) == "number" then
|
|
||||||
local inv = 1 / b
|
|
||||||
return {x = a.x * inv, y = a.y * inv, z = a.z * inv}
|
|
||||||
end
|
|
||||||
return {x = a.x / b.x, y = a.y / b.y, z = a.z / b.z}
|
|
||||||
end,
|
|
||||||
|
|
||||||
dot = function(a, b)
|
|
||||||
return a.x * b.x + a.y * b.y + a.z * b.z
|
|
||||||
end,
|
|
||||||
|
|
||||||
cross = function(a, b)
|
|
||||||
return {
|
|
||||||
x = a.y * b.z - a.z * b.y,
|
|
||||||
y = a.z * b.x - a.x * b.z,
|
|
||||||
z = a.x * b.y - a.y * b.x
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
length = function(v)
|
|
||||||
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
|
||||||
end,
|
|
||||||
|
|
||||||
length_squared = function(v)
|
|
||||||
return v.x * v.x + v.y * v.y + v.z * v.z
|
|
||||||
end,
|
|
||||||
|
|
||||||
distance = function(a, b)
|
|
||||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
|
||||||
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
||||||
end,
|
|
||||||
|
|
||||||
distance_squared = function(a, b)
|
|
||||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
|
||||||
return dx * dx + dy * dy + dz * dz
|
|
||||||
end,
|
|
||||||
|
|
||||||
normalize = function(v)
|
|
||||||
local len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
|
||||||
if len > 1e-10 then
|
|
||||||
local inv_len = 1 / len
|
|
||||||
return {x = v.x * inv_len, y = v.y * inv_len, z = v.z * inv_len}
|
|
||||||
end
|
|
||||||
return {x = 0, y = 0, z = 0}
|
|
||||||
end,
|
|
||||||
|
|
||||||
lerp = function(a, b, t)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
return {
|
|
||||||
x = a.x + (b.x - a.x) * t,
|
|
||||||
y = a.y + (b.y - a.y) * t,
|
|
||||||
z = a.z + (b.z - a.z) * t
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
reflect = function(v, normal)
|
|
||||||
local dot = v.x * normal.x + v.y * normal.y + v.z * normal.z
|
|
||||||
return {
|
|
||||||
x = v.x - 2 * dot * normal.x,
|
|
||||||
y = v.y - 2 * dot * normal.y,
|
|
||||||
z = v.z - 2 * dot * normal.z
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- MATRIX OPERATIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.mat2 = {
|
|
||||||
new = function(a, b, c, d)
|
|
||||||
return {
|
|
||||||
{a or 1, b or 0},
|
|
||||||
{c or 0, d or 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
identity = function()
|
|
||||||
return {{1, 0}, {0, 1}}
|
|
||||||
end,
|
|
||||||
|
|
||||||
mul = function(a, b)
|
|
||||||
return {
|
|
||||||
{
|
|
||||||
a[1][1] * b[1][1] + a[1][2] * b[2][1],
|
|
||||||
a[1][1] * b[1][2] + a[1][2] * b[2][2]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a[2][1] * b[1][1] + a[2][2] * b[2][1],
|
|
||||||
a[2][1] * b[1][2] + a[2][2] * b[2][2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
det = function(m)
|
|
||||||
return m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
|
||||||
end,
|
|
||||||
|
|
||||||
inverse = function(m)
|
|
||||||
local det = m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
|
||||||
if math.abs(det) < 1e-10 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local inv_det = 1 / det
|
|
||||||
return {
|
|
||||||
{m[2][2] * inv_det, -m[1][2] * inv_det},
|
|
||||||
{-m[2][1] * inv_det, m[1][1] * inv_det}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
rotation = function(angle)
|
|
||||||
local cos, sin = math.cos(angle), math.sin(angle)
|
|
||||||
return {
|
|
||||||
{cos, -sin},
|
|
||||||
{sin, cos}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
transform = function(m, v)
|
|
||||||
return {
|
|
||||||
x = m[1][1] * v.x + m[1][2] * v.y,
|
|
||||||
y = m[2][1] * v.x + m[2][2] * v.y
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
scale = function(sx, sy)
|
|
||||||
sy = sy or sx
|
|
||||||
return {
|
|
||||||
{sx, 0},
|
|
||||||
{0, sy}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
math.mat3 = {
|
|
||||||
identity = function()
|
|
||||||
return {
|
|
||||||
{1, 0, 0},
|
|
||||||
{0, 1, 0},
|
|
||||||
{0, 0, 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
transform = function(x, y, angle, sx, sy)
|
|
||||||
sx = sx or 1
|
|
||||||
sy = sy or sx
|
|
||||||
local cos, sin = math.cos(angle), math.sin(angle)
|
|
||||||
return {
|
|
||||||
{cos * sx, -sin * sy, x},
|
|
||||||
{sin * sx, cos * sy, y},
|
|
||||||
{0, 0, 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
mul = function(a, b)
|
|
||||||
local result = {
|
|
||||||
{0, 0, 0},
|
|
||||||
{0, 0, 0},
|
|
||||||
{0, 0, 0}
|
|
||||||
}
|
|
||||||
for i = 1, 3 do
|
|
||||||
for j = 1, 3 do
|
|
||||||
for k = 1, 3 do
|
|
||||||
result[i][j] = result[i][j] + a[i][k] * b[k][j]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end,
|
|
||||||
|
|
||||||
transform_point = function(m, v)
|
|
||||||
local x = m[1][1] * v.x + m[1][2] * v.y + m[1][3]
|
|
||||||
local y = m[2][1] * v.x + m[2][2] * v.y + m[2][3]
|
|
||||||
local w = m[3][1] * v.x + m[3][2] * v.y + m[3][3]
|
|
||||||
if math.abs(w) < 1e-10 then
|
|
||||||
return {x = 0, y = 0}
|
|
||||||
end
|
|
||||||
return {x = x / w, y = y / w}
|
|
||||||
end,
|
|
||||||
|
|
||||||
translation = function(x, y)
|
|
||||||
return {
|
|
||||||
{1, 0, x},
|
|
||||||
{0, 1, y},
|
|
||||||
{0, 0, 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
rotation = function(angle)
|
|
||||||
local cos, sin = math.cos(angle), math.sin(angle)
|
|
||||||
return {
|
|
||||||
{cos, -sin, 0},
|
|
||||||
{sin, cos, 0},
|
|
||||||
{0, 0, 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
scale = function(sx, sy)
|
|
||||||
sy = sy or sx
|
|
||||||
return {
|
|
||||||
{sx, 0, 0},
|
|
||||||
{0, sy, 0},
|
|
||||||
{0, 0, 1}
|
|
||||||
}
|
|
||||||
end,
|
|
||||||
|
|
||||||
det = function(m)
|
|
||||||
return m[1][1] * (m[2][2] * m[3][3] - m[2][3] * m[3][2]) -
|
|
||||||
m[1][2] * (m[2][1] * m[3][3] - m[2][3] * m[3][1]) +
|
|
||||||
m[1][3] * (m[2][1] * m[3][2] - m[2][2] * m[3][1])
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- GEOMETRY FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.geometry = {
|
|
||||||
point_line_distance = function(px, py, x1, y1, x2, y2)
|
|
||||||
local dx, dy = x2 - x1, y2 - y1
|
|
||||||
local len_sq = dx * dx + dy * dy
|
|
||||||
if len_sq < 1e-10 then
|
|
||||||
return math.distance(px, py, x1, y1)
|
|
||||||
end
|
|
||||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
local nearestX = x1 + t * dx
|
|
||||||
local nearestY = y1 + t * dy
|
|
||||||
return math.distance(px, py, nearestX, nearestY)
|
|
||||||
end,
|
|
||||||
|
|
||||||
point_in_polygon = function(px, py, vertices)
|
|
||||||
local inside = false
|
|
||||||
local n = #vertices / 2
|
|
||||||
for i = 1, n do
|
|
||||||
local x1, y1 = vertices[i*2-1], vertices[i*2]
|
|
||||||
local x2, y2
|
|
||||||
if i == n then
|
|
||||||
x2, y2 = vertices[1], vertices[2]
|
|
||||||
else
|
|
||||||
x2, y2 = vertices[i*2+1], vertices[i*2+2]
|
|
||||||
end
|
|
||||||
if ((y1 > py) ~= (y2 > py)) and
|
|
||||||
(px < (x2 - x1) * (py - y1) / (y2 - y1) + x1) then
|
|
||||||
inside = not inside
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return inside
|
|
||||||
end,
|
|
||||||
|
|
||||||
triangle_area = function(x1, y1, x2, y2, x3, y3)
|
|
||||||
return math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2)
|
|
||||||
end,
|
|
||||||
|
|
||||||
point_in_triangle = function(px, py, x1, y1, x2, y2, x3, y3)
|
|
||||||
local area = math.geometry.triangle_area(x1, y1, x2, y2, x3, y3)
|
|
||||||
local area1 = math.geometry.triangle_area(px, py, x2, y2, x3, y3)
|
|
||||||
local area2 = math.geometry.triangle_area(x1, y1, px, py, x3, y3)
|
|
||||||
local area3 = math.geometry.triangle_area(x1, y1, x2, y2, px, py)
|
|
||||||
return math.abs(area - (area1 + area2 + area3)) < 1e-10
|
|
||||||
end,
|
|
||||||
|
|
||||||
line_intersect = function(x1, y1, x2, y2, x3, y3, x4, y4)
|
|
||||||
local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
|
||||||
if math.abs(d) < 1e-10 then
|
|
||||||
return false, nil, nil
|
|
||||||
end
|
|
||||||
local ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d
|
|
||||||
local ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
|
|
||||||
if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
|
|
||||||
local x = x1 + ua * (x2 - x1)
|
|
||||||
local y = y1 + ua * (y2 - y1)
|
|
||||||
return true, x, y
|
|
||||||
end
|
|
||||||
return false, nil, nil
|
|
||||||
end,
|
|
||||||
|
|
||||||
closest_point_on_segment = function(px, py, x1, y1, x2, y2)
|
|
||||||
local dx, dy = x2 - x1, y2 - y1
|
|
||||||
local len_sq = dx * dx + dy * dy
|
|
||||||
if len_sq < 1e-10 then
|
|
||||||
return x1, y1
|
|
||||||
end
|
|
||||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
return x1 + t * dx, y1 + t * dy
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
-- ======================================================================
|
|
||||||
-- INTERPOLATION FUNCTIONS
|
|
||||||
-- ======================================================================
|
|
||||||
|
|
||||||
math.interpolation = {
|
|
||||||
bezier = function(t, p0, p1, p2, p3)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
local t2 = t * t
|
|
||||||
local t3 = t2 * t
|
|
||||||
local mt = 1 - t
|
|
||||||
local mt2 = mt * mt
|
|
||||||
local mt3 = mt2 * mt
|
|
||||||
return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3
|
|
||||||
end,
|
|
||||||
|
|
||||||
catmull_rom = function(t, p0, p1, p2, p3)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
local t2 = t * t
|
|
||||||
local t3 = t2 * t
|
|
||||||
return 0.5 * (
|
|
||||||
(2 * p1) +
|
|
||||||
(-p0 + p2) * t +
|
|
||||||
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
|
|
||||||
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
|
|
||||||
hermite = function(t, p0, p1, m0, m1)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
local t2 = t * t
|
|
||||||
local t3 = t2 * t
|
|
||||||
local h00 = 2 * t3 - 3 * t2 + 1
|
|
||||||
local h10 = t3 - 2 * t2 + t
|
|
||||||
local h01 = -2 * t3 + 3 * t2
|
|
||||||
local h11 = t3 - t2
|
|
||||||
return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1
|
|
||||||
end,
|
|
||||||
|
|
||||||
quadratic_bezier = function(t, p0, p1, p2)
|
|
||||||
t = math.clamp(t, 0, 1)
|
|
||||||
local mt = 1 - t
|
|
||||||
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
|
|
||||||
end,
|
|
||||||
|
|
||||||
step = function(t, edge, x)
|
|
||||||
return t < edge and 0 or x
|
|
||||||
end,
|
|
||||||
|
|
||||||
smootherstep = function(edge0, edge1, x)
|
|
||||||
local t = math.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
|
||||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
|
||||||
end
|
|
||||||
}
|
|
||||||
57
modules/math/math.go
Normal file
57
modules/math/math.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package math
|
||||||
|
|
||||||
|
import luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||||
|
|
||||||
|
func GetFunctionList() map[string]luajit.GoFunction {
|
||||||
|
return map[string]luajit.GoFunction{
|
||||||
|
"math_factorial": math_factorial,
|
||||||
|
"math_gcd": math_gcd,
|
||||||
|
"math_lcm": math_lcm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func math_factorial(s *luajit.State) int {
|
||||||
|
n := s.ToNumber(1)
|
||||||
|
if n < 0 || n != float64(int(n)) || n > 170 {
|
||||||
|
s.PushNil()
|
||||||
|
s.PushString("invalid argument")
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
result := 1.0
|
||||||
|
for i := 2; i <= int(n); i++ {
|
||||||
|
result *= float64(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushNumber(result)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func math_gcd(s *luajit.State) int {
|
||||||
|
a := int(s.ToNumber(1))
|
||||||
|
b := int(s.ToNumber(2))
|
||||||
|
|
||||||
|
for b != 0 {
|
||||||
|
a, b = b, a%b
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushNumber(float64(a))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func math_lcm(s *luajit.State) int {
|
||||||
|
a := int(s.ToNumber(1))
|
||||||
|
b := int(s.ToNumber(2))
|
||||||
|
|
||||||
|
// Calculate GCD
|
||||||
|
gcd := func(x, y int) int {
|
||||||
|
for y != 0 {
|
||||||
|
x, y = y, x%y
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
result := a * b / gcd(a, b)
|
||||||
|
s.PushNumber(float64(result))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
817
modules/math/math.lua
Normal file
817
modules/math/math.lua
Normal file
@ -0,0 +1,817 @@
|
|||||||
|
-- math.lua - Extended math library with advanced functions and utilities
|
||||||
|
|
||||||
|
local math_ext = {}
|
||||||
|
|
||||||
|
-- Import standard math functions to maintain compatibility
|
||||||
|
for name, func in pairs(_G.math) do
|
||||||
|
math_ext[name] = func
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- CONSTANTS (higher precision than standard library)
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
math_ext.pi = 3.14159265358979323846
|
||||||
|
math_ext.tau = 6.28318530717958647693 -- 2*pi, useful for full rotations
|
||||||
|
math_ext.e = 2.71828182845904523536
|
||||||
|
math_ext.phi = 1.61803398874989484820 -- Golden ratio (1 + sqrt(5)) / 2
|
||||||
|
math_ext.sqrt2 = 1.41421356237309504880
|
||||||
|
math_ext.sqrt3 = 1.73205080756887729353
|
||||||
|
math_ext.ln2 = 0.69314718055994530942 -- Natural log of 2
|
||||||
|
math_ext.ln10 = 2.30258509299404568402 -- Natural log of 10
|
||||||
|
math_ext.infinity = 1/0
|
||||||
|
math_ext.nan = 0/0
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- EXTENDED FUNCTIONS
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
-- Cube root that handles negative numbers correctly (unlike x^(1/3))
|
||||||
|
function math_ext.cbrt(x)
|
||||||
|
return x < 0 and -(-x)^(1/3) or x^(1/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Euclidean distance (hypotenuse) - more accurate than sqrt(x*x + y*y) for edge cases
|
||||||
|
function math_ext.hypot(x, y)
|
||||||
|
return math.sqrt(x * x + y * y)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- IEEE 754 NaN check - only reliable way to test for NaN
|
||||||
|
function math_ext.isnan(x)
|
||||||
|
return x ~= x
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if number is finite (not infinity or NaN)
|
||||||
|
function math_ext.isfinite(x)
|
||||||
|
return x > -math_ext.infinity and x < math_ext.infinity
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mathematical sign function returning -1, 0, or 1
|
||||||
|
function math_ext.sign(x)
|
||||||
|
return x > 0 and 1 or (x < 0 and -1 or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Constrain value to range [min, max] - essential for safe calculations
|
||||||
|
function math_ext.clamp(x, min, max)
|
||||||
|
return x < min and min or (x > max and max or x)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Linear interpolation - fundamental for animation and gradients
|
||||||
|
function math_ext.lerp(a, b, t)
|
||||||
|
return a + (b - a) * t
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Smooth interpolation using Hermite polynomial - gives eased motion
|
||||||
|
function math_ext.smoothstep(a, b, t)
|
||||||
|
t = math_ext.clamp((t - a) / (b - a), 0, 1)
|
||||||
|
return t * t * (3 - 2 * t)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Map value from input range to output range - useful for scaling
|
||||||
|
function math_ext.map(x, in_min, in_max, out_min, out_max)
|
||||||
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round to nearest integer (more predictable than math.floor(x + 0.5))
|
||||||
|
function math_ext.round(x)
|
||||||
|
return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Round to specified decimal places using multiplication/division
|
||||||
|
function math_ext.roundto(x, decimals)
|
||||||
|
local mult = 10 ^ (decimals or 0)
|
||||||
|
return math.floor(x * mult + 0.5) / mult
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Normalize angle to [-π, π] range for consistent angle calculations
|
||||||
|
function math_ext.normalize_angle(angle)
|
||||||
|
return angle - 2 * math_ext.pi * math.floor((angle + math_ext.pi) / (2 * math_ext.pi))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 2D Euclidean distance between two points
|
||||||
|
function math_ext.distance(x1, y1, x2, y2)
|
||||||
|
local dx, dy = x2 - x1, y2 - y1
|
||||||
|
return math.sqrt(dx * dx + dy * dy)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- RANDOM NUMBER FUNCTIONS - Enhanced random utilities
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
-- Random float in specified range [min, max) - more flexible than math.random()
|
||||||
|
function math_ext.randomf(min, max)
|
||||||
|
if not min and not max then
|
||||||
|
return math.random()
|
||||||
|
elseif not max then
|
||||||
|
max = min
|
||||||
|
min = 0
|
||||||
|
end
|
||||||
|
return min + math.random() * (max - min)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Random integer in range [min, max] inclusive - handles single argument case
|
||||||
|
function math_ext.randint(min, max)
|
||||||
|
if not max then
|
||||||
|
max = min
|
||||||
|
min = 1
|
||||||
|
end
|
||||||
|
return math.floor(math.random() * (max - min + 1) + min)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Random boolean with configurable probability - useful for procedural generation
|
||||||
|
function math_ext.randboolean(p)
|
||||||
|
p = p or 0.5
|
||||||
|
return math.random() < p
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- STATISTICS FUNCTIONS - Robust statistical calculations
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
-- Sum of numeric values in table - filters out non-numbers automatically
|
||||||
|
function math_ext.sum(t)
|
||||||
|
if type(t) ~= "table" then return 0 end
|
||||||
|
|
||||||
|
local sum = 0
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
sum = sum + t[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sum
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Arithmetic mean (average) - handles mixed-type arrays safely
|
||||||
|
function math_ext.mean(t)
|
||||||
|
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||||
|
|
||||||
|
local sum = 0
|
||||||
|
local count = 0
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
sum = sum + t[i]
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count > 0 and sum / count or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Median value - sorts data to find middle value (handles even/odd lengths)
|
||||||
|
function math_ext.median(t)
|
||||||
|
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||||
|
|
||||||
|
local nums = {}
|
||||||
|
local count = 0
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
count = count + 1
|
||||||
|
nums[count] = t[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if count == 0 then return 0 end
|
||||||
|
|
||||||
|
table.sort(nums)
|
||||||
|
|
||||||
|
if count % 2 == 0 then
|
||||||
|
return (nums[count/2] + nums[count/2 + 1]) / 2
|
||||||
|
else
|
||||||
|
return nums[math.ceil(count/2)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sample variance - measures spread using n-1 denominator (Bessel's correction)
|
||||||
|
function math_ext.variance(t)
|
||||||
|
if type(t) ~= "table" then return 0 end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
local m = math_ext.mean(t)
|
||||||
|
local sum = 0
|
||||||
|
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
local dev = t[i] - m
|
||||||
|
sum = sum + dev * dev
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count > 1 and sum / count or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Sample standard deviation - square root of variance
|
||||||
|
function math_ext.stdev(t)
|
||||||
|
return math.sqrt(math_ext.variance(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Population variance - uses n denominator instead of n-1
|
||||||
|
function math_ext.pvariance(t)
|
||||||
|
if type(t) ~= "table" then return 0 end
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
local m = math_ext.mean(t)
|
||||||
|
local sum = 0
|
||||||
|
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
local dev = t[i] - m
|
||||||
|
sum = sum + dev * dev
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return count > 0 and sum / count or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Population standard deviation
|
||||||
|
function math_ext.pstdev(t)
|
||||||
|
return math.sqrt(math_ext.pvariance(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Mode - most frequently occurring value (first encountered if tie)
|
||||||
|
function math_ext.mode(t)
|
||||||
|
if type(t) ~= "table" or #t == 0 then return nil end
|
||||||
|
|
||||||
|
local counts = {}
|
||||||
|
local most_frequent = nil
|
||||||
|
local max_count = 0
|
||||||
|
|
||||||
|
for i=1, #t do
|
||||||
|
local v = t[i]
|
||||||
|
counts[v] = (counts[v] or 0) + 1
|
||||||
|
if counts[v] > max_count then
|
||||||
|
max_count = counts[v]
|
||||||
|
most_frequent = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return most_frequent
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Simultaneous min/max - more efficient than separate calls
|
||||||
|
function math_ext.minmax(t)
|
||||||
|
if type(t) ~= "table" or #t == 0 then return nil, nil end
|
||||||
|
|
||||||
|
local min, max
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
min = t[i]
|
||||||
|
max = t[i]
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if min == nil then return nil, nil end
|
||||||
|
|
||||||
|
for i=1, #t do
|
||||||
|
if type(t[i]) == "number" then
|
||||||
|
if t[i] < min then min = t[i] end
|
||||||
|
if t[i] > max then max = t[i] end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return min, max
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- VECTOR OPERATIONS - 2D/3D vector math for graphics and physics
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
-- 2D Vector operations - fundamental for 2D graphics, physics, and UI
|
||||||
|
math_ext.vec2 = {
|
||||||
|
-- Create new 2D vector with default zero values
|
||||||
|
new = function(x, y)
|
||||||
|
return {x = x or 0, y = y or 0}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Create independent copy to avoid reference issues
|
||||||
|
copy = function(v)
|
||||||
|
return {x = v.x, y = v.y}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Vector addition - combines two displacement vectors
|
||||||
|
add = function(a, b)
|
||||||
|
return {x = a.x + b.x, y = a.y + b.y}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Vector subtraction - difference between two points/vectors
|
||||||
|
sub = function(a, b)
|
||||||
|
return {x = a.x - b.x, y = a.y - b.y}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Scalar or component-wise multiplication
|
||||||
|
mul = function(a, b)
|
||||||
|
if type(b) == "number" then
|
||||||
|
return {x = a.x * b, y = a.y * b}
|
||||||
|
end
|
||||||
|
return {x = a.x * b.x, y = a.y * b.y}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Scalar or component-wise division (uses multiplication for efficiency)
|
||||||
|
div = function(a, b)
|
||||||
|
if type(b) == "number" then
|
||||||
|
local inv = 1 / b
|
||||||
|
return {x = a.x * inv, y = a.y * inv}
|
||||||
|
end
|
||||||
|
return {x = a.x / b.x, y = a.y / b.y}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Dot product - measures vector similarity/projection
|
||||||
|
dot = function(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Euclidean length/magnitude of vector
|
||||||
|
length = function(v)
|
||||||
|
return math.sqrt(v.x * v.x + v.y * v.y)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Squared length - avoids sqrt for performance when comparing lengths
|
||||||
|
length_squared = function(v)
|
||||||
|
return v.x * v.x + v.y * v.y
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Distance between two points
|
||||||
|
distance = function(a, b)
|
||||||
|
local dx, dy = b.x - a.x, b.y - a.y
|
||||||
|
return math.sqrt(dx * dx + dy * dy)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Squared distance - avoids sqrt for performance
|
||||||
|
distance_squared = function(a, b)
|
||||||
|
local dx, dy = b.x - a.x, b.y - a.y
|
||||||
|
return dx * dx + dy * dy
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Convert to unit vector (length 1) - preserves direction
|
||||||
|
normalize = function(v)
|
||||||
|
local len = math.sqrt(v.x * v.x + v.y * v.y)
|
||||||
|
if len > 1e-10 then
|
||||||
|
local inv_len = 1 / len
|
||||||
|
return {x = v.x * inv_len, y = v.y * inv_len}
|
||||||
|
end
|
||||||
|
return {x = 0, y = 0}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Rotate vector by angle (counterclockwise)
|
||||||
|
rotate = function(v, angle)
|
||||||
|
local c, s = math.cos(angle), math.sin(angle)
|
||||||
|
return {
|
||||||
|
x = v.x * c - v.y * s,
|
||||||
|
y = v.x * s + v.y * c
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Get angle of vector from positive x-axis
|
||||||
|
angle = function(v)
|
||||||
|
return math.atan2(v.y, v.x)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Linear interpolation between two vectors
|
||||||
|
lerp = function(a, b, t)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
return {
|
||||||
|
x = a.x + (b.x - a.x) * t,
|
||||||
|
y = a.y + (b.y - a.y) * t
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Reflect vector across normal (like light bouncing off surface)
|
||||||
|
reflect = function(v, normal)
|
||||||
|
local dot = v.x * normal.x + v.y * normal.y
|
||||||
|
return {
|
||||||
|
x = v.x - 2 * dot * normal.x,
|
||||||
|
y = v.y - 2 * dot * normal.y
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- 3D Vector operations - essential for 3D graphics and spatial calculations
|
||||||
|
math_ext.vec3 = {
|
||||||
|
new = function(x, y, z)
|
||||||
|
return {x = x or 0, y = y or 0, z = z or 0}
|
||||||
|
end,
|
||||||
|
|
||||||
|
copy = function(v)
|
||||||
|
return {x = v.x, y = v.y, z = v.z}
|
||||||
|
end,
|
||||||
|
|
||||||
|
add = function(a, b)
|
||||||
|
return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z}
|
||||||
|
end,
|
||||||
|
|
||||||
|
sub = function(a, b)
|
||||||
|
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
|
||||||
|
end,
|
||||||
|
|
||||||
|
mul = function(a, b)
|
||||||
|
if type(b) == "number" then
|
||||||
|
return {x = a.x * b, y = a.y * b, z = a.z * b}
|
||||||
|
end
|
||||||
|
return {x = a.x * b.x, y = a.y * b.y, z = a.z * b.z}
|
||||||
|
end,
|
||||||
|
|
||||||
|
div = function(a, b)
|
||||||
|
if type(b) == "number" then
|
||||||
|
local inv = 1 / b
|
||||||
|
return {x = a.x * inv, y = a.y * inv, z = a.z * inv}
|
||||||
|
end
|
||||||
|
return {x = a.x / b.x, y = a.y / b.y, z = a.z / b.z}
|
||||||
|
end,
|
||||||
|
|
||||||
|
dot = function(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Cross product - creates perpendicular vector (right-hand rule)
|
||||||
|
cross = function(a, b)
|
||||||
|
return {
|
||||||
|
x = a.y * b.z - a.z * b.y,
|
||||||
|
y = a.z * b.x - a.x * b.z,
|
||||||
|
z = a.x * b.y - a.y * b.x
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
length = function(v)
|
||||||
|
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||||
|
end,
|
||||||
|
|
||||||
|
length_squared = function(v)
|
||||||
|
return v.x * v.x + v.y * v.y + v.z * v.z
|
||||||
|
end,
|
||||||
|
|
||||||
|
distance = function(a, b)
|
||||||
|
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||||
|
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
||||||
|
end,
|
||||||
|
|
||||||
|
distance_squared = function(a, b)
|
||||||
|
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||||
|
return dx * dx + dy * dy + dz * dz
|
||||||
|
end,
|
||||||
|
|
||||||
|
normalize = function(v)
|
||||||
|
local len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||||
|
if len > 1e-10 then
|
||||||
|
local inv_len = 1 / len
|
||||||
|
return {x = v.x * inv_len, y = v.y * inv_len, z = v.z * inv_len}
|
||||||
|
end
|
||||||
|
return {x = 0, y = 0, z = 0}
|
||||||
|
end,
|
||||||
|
|
||||||
|
lerp = function(a, b, t)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
return {
|
||||||
|
x = a.x + (b.x - a.x) * t,
|
||||||
|
y = a.y + (b.y - a.y) * t,
|
||||||
|
z = a.z + (b.z - a.z) * t
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
reflect = function(v, normal)
|
||||||
|
local dot = v.x * normal.x + v.y * normal.y + v.z * normal.z
|
||||||
|
return {
|
||||||
|
x = v.x - 2 * dot * normal.x,
|
||||||
|
y = v.y - 2 * dot * normal.y,
|
||||||
|
z = v.z - 2 * dot * normal.z
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- MATRIX OPERATIONS - Linear transformations for graphics and math
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
math_ext.mat2 = {
|
||||||
|
-- Create 2x2 matrix with specified values (defaults to identity)
|
||||||
|
new = function(a, b, c, d)
|
||||||
|
return {
|
||||||
|
{a or 1, b or 0},
|
||||||
|
{c or 0, d or 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- 2x2 identity matrix - no transformation
|
||||||
|
identity = function()
|
||||||
|
return {{1, 0}, {0, 1}}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Matrix multiplication - combines transformations (order matters)
|
||||||
|
mul = function(a, b)
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
a[1][1] * b[1][1] + a[1][2] * b[2][1],
|
||||||
|
a[1][1] * b[1][2] + a[1][2] * b[2][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a[2][1] * b[1][1] + a[2][2] * b[2][1],
|
||||||
|
a[2][1] * b[1][2] + a[2][2] * b[2][2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Determinant - measures area scaling factor
|
||||||
|
det = function(m)
|
||||||
|
return m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Inverse matrix - reverses transformation (if possible)
|
||||||
|
inverse = function(m)
|
||||||
|
local det = m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||||
|
if math.abs(det) < 1e-10 then
|
||||||
|
return nil -- Matrix is not invertible
|
||||||
|
end
|
||||||
|
|
||||||
|
local inv_det = 1 / det
|
||||||
|
return {
|
||||||
|
{m[2][2] * inv_det, -m[1][2] * inv_det},
|
||||||
|
{-m[2][1] * inv_det, m[1][1] * inv_det}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Create rotation matrix for given angle
|
||||||
|
rotation = function(angle)
|
||||||
|
local cos, sin = math.cos(angle), math.sin(angle)
|
||||||
|
return {
|
||||||
|
{cos, -sin},
|
||||||
|
{sin, cos}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Apply matrix transformation to 2D vector
|
||||||
|
transform = function(m, v)
|
||||||
|
return {
|
||||||
|
x = m[1][1] * v.x + m[1][2] * v.y,
|
||||||
|
y = m[2][1] * v.x + m[2][2] * v.y
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Create scaling matrix (uniform if sy omitted)
|
||||||
|
scale = function(sx, sy)
|
||||||
|
sy = sy or sx
|
||||||
|
return {
|
||||||
|
{sx, 0},
|
||||||
|
{0, sy}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
math_ext.mat3 = {
|
||||||
|
-- 3x3 identity matrix - useful for 2D transformations with translation
|
||||||
|
identity = function()
|
||||||
|
return {
|
||||||
|
{1, 0, 0},
|
||||||
|
{0, 1, 0},
|
||||||
|
{0, 0, 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Complete 2D transformation matrix (translate, rotate, scale)
|
||||||
|
transform = function(x, y, angle, sx, sy)
|
||||||
|
sx = sx or 1
|
||||||
|
sy = sy or sx
|
||||||
|
local cos, sin = math.cos(angle), math.sin(angle)
|
||||||
|
return {
|
||||||
|
{cos * sx, -sin * sy, x},
|
||||||
|
{sin * sx, cos * sy, y},
|
||||||
|
{0, 0, 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- 3x3 matrix multiplication - combines transformations
|
||||||
|
mul = function(a, b)
|
||||||
|
local result = {
|
||||||
|
{0, 0, 0},
|
||||||
|
{0, 0, 0},
|
||||||
|
{0, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 1, 3 do
|
||||||
|
for j = 1, 3 do
|
||||||
|
for k = 1, 3 do
|
||||||
|
result[i][j] = result[i][j] + a[i][k] * b[k][j]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Transform 2D point using homogeneous coordinates
|
||||||
|
transform_point = function(m, v)
|
||||||
|
local x = m[1][1] * v.x + m[1][2] * v.y + m[1][3]
|
||||||
|
local y = m[2][1] * v.x + m[2][2] * v.y + m[2][3]
|
||||||
|
local w = m[3][1] * v.x + m[3][2] * v.y + m[3][3]
|
||||||
|
|
||||||
|
if math.abs(w) < 1e-10 then
|
||||||
|
return {x = 0, y = 0}
|
||||||
|
end
|
||||||
|
|
||||||
|
return {x = x / w, y = y / w}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Translation matrix
|
||||||
|
translation = function(x, y)
|
||||||
|
return {
|
||||||
|
{1, 0, x},
|
||||||
|
{0, 1, y},
|
||||||
|
{0, 0, 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- 2D rotation matrix in 3x3 form
|
||||||
|
rotation = function(angle)
|
||||||
|
local cos, sin = math.cos(angle), math.sin(angle)
|
||||||
|
return {
|
||||||
|
{cos, -sin, 0},
|
||||||
|
{sin, cos, 0},
|
||||||
|
{0, 0, 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Scaling matrix in 3x3 form
|
||||||
|
scale = function(sx, sy)
|
||||||
|
sy = sy or sx
|
||||||
|
return {
|
||||||
|
{sx, 0, 0},
|
||||||
|
{0, sy, 0},
|
||||||
|
{0, 0, 1}
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- 3x3 determinant - measures volume scaling
|
||||||
|
det = function(m)
|
||||||
|
return m[1][1] * (m[2][2] * m[3][3] - m[2][3] * m[3][2]) -
|
||||||
|
m[1][2] * (m[2][1] * m[3][3] - m[2][3] * m[3][1]) +
|
||||||
|
m[1][3] * (m[2][1] * m[3][2] - m[2][2] * m[3][1])
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- GEOMETRY FUNCTIONS - Computational geometry utilities
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
math_ext.geometry = {
|
||||||
|
-- Shortest distance from point to line segment
|
||||||
|
point_line_distance = function(px, py, x1, y1, x2, y2)
|
||||||
|
local dx, dy = x2 - x1, y2 - y1
|
||||||
|
local len_sq = dx * dx + dy * dy
|
||||||
|
|
||||||
|
if len_sq < 1e-10 then
|
||||||
|
return math_ext.distance(px, py, x1, y1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
|
||||||
|
local nearestX = x1 + t * dx
|
||||||
|
local nearestY = y1 + t * dy
|
||||||
|
|
||||||
|
return math_ext.distance(px, py, nearestX, nearestY)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Point-in-polygon test using ray casting algorithm
|
||||||
|
point_in_polygon = function(px, py, vertices)
|
||||||
|
local inside = false
|
||||||
|
local n = #vertices / 2
|
||||||
|
|
||||||
|
for i = 1, n do
|
||||||
|
local x1, y1 = vertices[i*2-1], vertices[i*2]
|
||||||
|
local x2, y2
|
||||||
|
|
||||||
|
if i == n then
|
||||||
|
x2, y2 = vertices[1], vertices[2]
|
||||||
|
else
|
||||||
|
x2, y2 = vertices[i*2+1], vertices[i*2+2]
|
||||||
|
end
|
||||||
|
|
||||||
|
if ((y1 > py) ~= (y2 > py)) and
|
||||||
|
(px < (x2 - x1) * (py - y1) / (y2 - y1) + x1) then
|
||||||
|
inside = not inside
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return inside
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Calculate triangle area using cross product method
|
||||||
|
triangle_area = function(x1, y1, x2, y2, x3, y3)
|
||||||
|
return math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Point-in-triangle test using barycentric coordinates
|
||||||
|
point_in_triangle = function(px, py, x1, y1, x2, y2, x3, y3)
|
||||||
|
local area = math_ext.geometry.triangle_area(x1, y1, x2, y2, x3, y3)
|
||||||
|
local area1 = math_ext.geometry.triangle_area(px, py, x2, y2, x3, y3)
|
||||||
|
local area2 = math_ext.geometry.triangle_area(x1, y1, px, py, x3, y3)
|
||||||
|
local area3 = math_ext.geometry.triangle_area(x1, y1, x2, y2, px, py)
|
||||||
|
|
||||||
|
return math.abs(area - (area1 + area2 + area3)) < 1e-10
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Line segment intersection using parametric equations
|
||||||
|
line_intersect = function(x1, y1, x2, y2, x3, y3, x4, y4)
|
||||||
|
local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||||
|
|
||||||
|
if math.abs(d) < 1e-10 then
|
||||||
|
return false, nil, nil -- Lines are parallel
|
||||||
|
end
|
||||||
|
|
||||||
|
local ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d
|
||||||
|
local ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
|
||||||
|
|
||||||
|
if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
|
||||||
|
local x = x1 + ua * (x2 - x1)
|
||||||
|
local y = y1 + ua * (y2 - y1)
|
||||||
|
return true, x, y
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, nil, nil
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Find closest point on line segment to given point
|
||||||
|
closest_point_on_segment = function(px, py, x1, y1, x2, y2)
|
||||||
|
local dx, dy = x2 - x1, y2 - y1
|
||||||
|
local len_sq = dx * dx + dy * dy
|
||||||
|
|
||||||
|
if len_sq < 1e-10 then
|
||||||
|
return x1, y1
|
||||||
|
end
|
||||||
|
|
||||||
|
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
|
||||||
|
return x1 + t * dx, y1 + t * dy
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- ======================================================================
|
||||||
|
-- INTERPOLATION FUNCTIONS - Smooth transitions and curve generation
|
||||||
|
-- ======================================================================
|
||||||
|
|
||||||
|
math_ext.interpolation = {
|
||||||
|
-- Cubic Bézier curve - standard for smooth animations
|
||||||
|
bezier = function(t, p0, p1, p2, p3)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
local t2 = t * t
|
||||||
|
local t3 = t2 * t
|
||||||
|
local mt = 1 - t
|
||||||
|
local mt2 = mt * mt
|
||||||
|
local mt3 = mt2 * mt
|
||||||
|
|
||||||
|
return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Catmull-Rom spline - smooth interpolation through points
|
||||||
|
catmull_rom = function(t, p0, p1, p2, p3)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
local t2 = t * t
|
||||||
|
local t3 = t2 * t
|
||||||
|
|
||||||
|
return 0.5 * (
|
||||||
|
(2 * p1) +
|
||||||
|
(-p0 + p2) * t +
|
||||||
|
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
|
||||||
|
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Hermite interpolation with tangent control
|
||||||
|
hermite = function(t, p0, p1, m0, m1)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
local t2 = t * t
|
||||||
|
local t3 = t2 * t
|
||||||
|
local h00 = 2 * t3 - 3 * t2 + 1
|
||||||
|
local h10 = t3 - 2 * t2 + t
|
||||||
|
local h01 = -2 * t3 + 3 * t2
|
||||||
|
local h11 = t3 - t2
|
||||||
|
|
||||||
|
return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Quadratic Bézier - simpler curve with one control point
|
||||||
|
quadratic_bezier = function(t, p0, p1, p2)
|
||||||
|
t = math_ext.clamp(t, 0, 1)
|
||||||
|
local mt = 1 - t
|
||||||
|
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Step function - instant transition at threshold
|
||||||
|
step = function(t, edge, x)
|
||||||
|
return t < edge and 0 or x
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Smoothstep - S-curve interpolation (3rd order)
|
||||||
|
smoothstep = function(edge0, edge1, x)
|
||||||
|
local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
||||||
|
return t * t * (3 - 2 * t)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Smootherstep - Even smoother S-curve (5th order, Ken Perlin)
|
||||||
|
smootherstep = function(edge0, edge1, x)
|
||||||
|
local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
||||||
|
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return math_ext
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
local tbl = require("table")
|
||||||
local mysql = {}
|
local mysql = {}
|
||||||
|
|
||||||
local Connection = {}
|
local Connection = {}
|
||||||
@ -137,14 +138,14 @@ function Connection:insert(table_name, data)
|
|||||||
error("Table name cannot be empty")
|
error("Table name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = table.map(keys, function() return "?" end)
|
local placeholders = tbl.map(keys, function() return "?" end)
|
||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", ")
|
placeholders = tbl.concat(placeholders, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
@ -155,21 +156,21 @@ function Connection:upsert(table_name, data, update_data)
|
|||||||
error("Table name cannot be empty")
|
error("Table name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = table.map(keys, function() return "?" end)
|
local placeholders = tbl.map(keys, function() return "?" end)
|
||||||
|
|
||||||
-- Use update_data if provided, otherwise update with same data
|
-- Use update_data if provided, otherwise update with same data
|
||||||
local update_source = update_data or data
|
local update_source = update_data or data
|
||||||
local updates = table.map(table.keys(update_source), function(key)
|
local updates = tbl.map(tbl.keys(update_source), function(key)
|
||||||
return string.template("${key} = VALUES(${key})", {key = key})
|
return string.template("${key} = VALUES(${key})", {key = key})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON DUPLICATE KEY UPDATE ${updates}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", "),
|
placeholders = tbl.concat(placeholders, ", "),
|
||||||
updates = table.concat(updates, ", ")
|
updates = tbl.concat(updates, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
@ -180,14 +181,14 @@ function Connection:replace(table_name, data)
|
|||||||
error("Table name cannot be empty")
|
error("Table name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = table.map(keys, function() return "?" end)
|
local placeholders = tbl.map(keys, function() return "?" end)
|
||||||
|
|
||||||
local query = string.template("REPLACE INTO ${table} (${columns}) VALUES (${placeholders})", {
|
local query = string.template("REPLACE INTO ${table} (${columns}) VALUES (${placeholders})", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", ")
|
placeholders = tbl.concat(placeholders, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
@ -201,21 +202,21 @@ function Connection:update(table_name, data, where_clause, ...)
|
|||||||
error("WHERE clause cannot be empty for UPDATE")
|
error("WHERE clause cannot be empty for UPDATE")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local sets = table.map(keys, function(key)
|
local sets = tbl.map(keys, function(key)
|
||||||
return string.template("${key} = ?", {key = key})
|
return string.template("${key} = ?", {key = key})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
sets = table.concat(sets, ", "),
|
sets = tbl.concat(sets, ", "),
|
||||||
where = where_clause
|
where = where_clause
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Add WHERE clause parameters
|
-- Add WHERE clause parameters
|
||||||
local where_args = {...}
|
local where_args = {...}
|
||||||
table.extend(values, where_args)
|
tbl.extend(values, where_args)
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
end
|
end
|
||||||
@ -242,7 +243,7 @@ function Connection:select(table_name, columns, where_clause, ...)
|
|||||||
|
|
||||||
columns = columns or "*"
|
columns = columns or "*"
|
||||||
if type(columns) == "table" then
|
if type(columns) == "table" then
|
||||||
columns = table.concat(columns, ", ")
|
columns = tbl.concat(columns, ", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
local query
|
local query
|
||||||
@ -417,7 +418,7 @@ function Connection:create_index(index_name, table_name, columns, unique, type)
|
|||||||
|
|
||||||
local unique_clause = unique and "UNIQUE " or ""
|
local unique_clause = unique and "UNIQUE " or ""
|
||||||
local type_clause = type and string.template(" USING ${type}", {type = string.upper(type)}) or ""
|
local type_clause = type and string.template(" USING ${type}", {type = string.upper(type)}) or ""
|
||||||
local columns_str = type(columns) == "table" and table.concat(columns, ", ") or tostring(columns)
|
local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns)
|
||||||
|
|
||||||
local query = string.template("CREATE ${unique}INDEX ${index} ON ${table} (${columns})${type}", {
|
local query = string.template("CREATE ${unique}INDEX ${index} ON ${table} (${columns})${type}", {
|
||||||
unique = unique_clause,
|
unique = unique_clause,
|
||||||
@ -464,7 +465,7 @@ function Connection:check_table(table_name, options)
|
|||||||
local valid_options = {"QUICK", "FAST", "MEDIUM", "EXTENDED", "CHANGED"}
|
local valid_options = {"QUICK", "FAST", "MEDIUM", "EXTENDED", "CHANGED"}
|
||||||
local options_upper = string.upper(options)
|
local options_upper = string.upper(options)
|
||||||
|
|
||||||
if table.contains(valid_options, options_upper) then
|
if tbl.contains(valid_options, options_upper) then
|
||||||
options_clause = string.template(" ${options}", {options = options_upper})
|
options_clause = string.template(" ${options}", {options = options_upper})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -783,7 +784,7 @@ end
|
|||||||
|
|
||||||
-- Simplified result processing utilities
|
-- Simplified result processing utilities
|
||||||
function mysql.to_array(results, column_name)
|
function mysql.to_array(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -791,11 +792,11 @@ function mysql.to_array(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.map(results, function(row) return row[column_name] end)
|
return tbl.map(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function mysql.to_map(results, key_column, value_column)
|
function mysql.to_map(results, key_column, value_column)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -812,7 +813,7 @@ function mysql.to_map(results, key_column, value_column)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function mysql.group_by(results, column_name)
|
function mysql.group_by(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -820,18 +821,18 @@ function mysql.group_by(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.group_by(results, function(row) return row[column_name] end)
|
return tbl.group_by(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simplified debug helper
|
-- Simplified debug helper
|
||||||
function mysql.print_results(results)
|
function mysql.print_results(results)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
print("No results")
|
print("No results")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local columns = table.keys(results[1])
|
local columns = tbl.keys(results[1])
|
||||||
table.sort(columns)
|
tbl.sort(columns)
|
||||||
|
|
||||||
-- Calculate column widths
|
-- Calculate column widths
|
||||||
local widths = {}
|
local widths = {}
|
||||||
@ -847,19 +848,19 @@ function mysql.print_results(results)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Print header
|
-- Print header
|
||||||
local header_parts = table.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
||||||
local separator_parts = table.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
||||||
|
|
||||||
print(table.concat(header_parts, " | "))
|
print(tbl.concat(header_parts, " | "))
|
||||||
print(table.concat(separator_parts, "-+-"))
|
print(tbl.concat(separator_parts, "-+-"))
|
||||||
|
|
||||||
-- Print rows
|
-- Print rows
|
||||||
for _, row in ipairs(results) do
|
for _, row in ipairs(results) do
|
||||||
local value_parts = table.map(columns, function(col)
|
local value_parts = tbl.map(columns, function(col)
|
||||||
local value = tostring(row[col] or "")
|
local value = tostring(row[col] or "")
|
||||||
return string.pad_right(value, widths[col])
|
return string.pad_right(value, widths[col])
|
||||||
end)
|
end)
|
||||||
print(table.concat(value_parts, " | "))
|
print(tbl.concat(value_parts, " | "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -887,7 +888,7 @@ function mysql.build_dsn(options)
|
|||||||
local parts = {}
|
local parts = {}
|
||||||
|
|
||||||
if options.username and not string.is_blank(options.username) then
|
if options.username and not string.is_blank(options.username) then
|
||||||
table.insert(parts, options.username)
|
tbl.insert(parts, options.username)
|
||||||
if options.password and not string.is_blank(options.password) then
|
if options.password and not string.is_blank(options.password) then
|
||||||
parts[#parts] = string.template("${user}:${pass}", {
|
parts[#parts] = string.template("${user}:${pass}", {
|
||||||
user = parts[#parts],
|
user = parts[#parts],
|
||||||
@ -898,9 +899,9 @@ function mysql.build_dsn(options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if options.protocol and not string.is_blank(options.protocol) then
|
if options.protocol and not string.is_blank(options.protocol) then
|
||||||
table.insert(parts, string.template("${protocol}(", {protocol = options.protocol}))
|
tbl.insert(parts, string.template("${protocol}(", {protocol = options.protocol}))
|
||||||
if options.host and not string.is_blank(options.host) then
|
if options.host and not string.is_blank(options.host) then
|
||||||
table.insert(parts, options.host)
|
tbl.insert(parts, options.host)
|
||||||
if options.port then
|
if options.port then
|
||||||
parts[#parts] = string.template("${host}:${port}", {
|
parts[#parts] = string.template("${host}:${port}", {
|
||||||
host = parts[#parts],
|
host = parts[#parts],
|
||||||
@ -917,33 +918,33 @@ function mysql.build_dsn(options)
|
|||||||
port = tostring(options.port)
|
port = tostring(options.port)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
table.insert(parts, host_part .. ")")
|
tbl.insert(parts, host_part .. ")")
|
||||||
end
|
end
|
||||||
|
|
||||||
if options.database and not string.is_blank(options.database) then
|
if options.database and not string.is_blank(options.database) then
|
||||||
table.insert(parts, string.template("/${database}", {database = options.database}))
|
tbl.insert(parts, string.template("/${database}", {database = options.database}))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Add parameters
|
-- Add parameters
|
||||||
local params = {}
|
local params = {}
|
||||||
if options.charset and not string.is_blank(options.charset) then
|
if options.charset and not string.is_blank(options.charset) then
|
||||||
table.insert(params, string.template("charset=${charset}", {charset = options.charset}))
|
tbl.insert(params, string.template("charset=${charset}", {charset = options.charset}))
|
||||||
end
|
end
|
||||||
if options.parseTime ~= nil then
|
if options.parseTime ~= nil then
|
||||||
table.insert(params, string.template("parseTime=${parse}", {parse = tostring(options.parseTime)}))
|
tbl.insert(params, string.template("parseTime=${parse}", {parse = tostring(options.parseTime)}))
|
||||||
end
|
end
|
||||||
if options.timeout and not string.is_blank(options.timeout) then
|
if options.timeout and not string.is_blank(options.timeout) then
|
||||||
table.insert(params, string.template("timeout=${timeout}", {timeout = options.timeout}))
|
tbl.insert(params, string.template("timeout=${timeout}", {timeout = options.timeout}))
|
||||||
end
|
end
|
||||||
if options.tls and not string.is_blank(options.tls) then
|
if options.tls and not string.is_blank(options.tls) then
|
||||||
table.insert(params, string.template("tls=${tls}", {tls = options.tls}))
|
tbl.insert(params, string.template("tls=${tls}", {tls = options.tls}))
|
||||||
end
|
end
|
||||||
|
|
||||||
if #params > 0 then
|
if #params > 0 then
|
||||||
table.insert(parts, string.template("?${params}", {params = table.concat(params, "&")}))
|
tbl.insert(parts, string.template("?${params}", {params = tbl.concat(params, "&")}))
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(parts, "")
|
return tbl.concat(parts, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
return mysql
|
return mysql
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
local tbl = require("table")
|
||||||
local postgres = {}
|
local postgres = {}
|
||||||
|
|
||||||
local Connection = {}
|
local Connection = {}
|
||||||
@ -133,12 +134,12 @@ end
|
|||||||
|
|
||||||
-- Simplified PostgreSQL parameter builder
|
-- Simplified PostgreSQL parameter builder
|
||||||
local function build_postgres_params(data)
|
local function build_postgres_params(data)
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = {}
|
local placeholders = {}
|
||||||
|
|
||||||
for i = 1, #keys do
|
for i = 1, #keys do
|
||||||
table.insert(placeholders, string.template("$${num}", {num = tostring(i)}))
|
tbl.insert(placeholders, string.template("$${num}", {num = tostring(i)}))
|
||||||
end
|
end
|
||||||
|
|
||||||
return keys, values, placeholders, #keys
|
return keys, values, placeholders, #keys
|
||||||
@ -154,8 +155,8 @@ function Connection:insert(table_name, data, returning)
|
|||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", ")
|
placeholders = tbl.concat(placeholders, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
if returning and not string.is_blank(returning) then
|
if returning and not string.is_blank(returning) then
|
||||||
@ -175,7 +176,7 @@ function Connection:upsert(table_name, data, conflict_columns, returning)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local keys, values, placeholders = build_postgres_params(data)
|
local keys, values, placeholders = build_postgres_params(data)
|
||||||
local updates = table.map(keys, function(key)
|
local updates = tbl.map(keys, function(key)
|
||||||
return string.template("${key} = EXCLUDED.${key}", {key = key})
|
return string.template("${key} = EXCLUDED.${key}", {key = key})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -184,16 +185,16 @@ function Connection:upsert(table_name, data, conflict_columns, returning)
|
|||||||
if type(conflict_columns) == "string" then
|
if type(conflict_columns) == "string" then
|
||||||
conflict_clause = string.template("(${columns})", {columns = conflict_columns})
|
conflict_clause = string.template("(${columns})", {columns = conflict_columns})
|
||||||
else
|
else
|
||||||
conflict_clause = string.template("(${columns})", {columns = table.concat(conflict_columns, ", ")})
|
conflict_clause = string.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", "),
|
placeholders = tbl.concat(placeholders, ", "),
|
||||||
conflict = conflict_clause,
|
conflict = conflict_clause,
|
||||||
updates = table.concat(updates, ", ")
|
updates = tbl.concat(updates, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
if returning and not string.is_blank(returning) then
|
if returning and not string.is_blank(returning) then
|
||||||
@ -215,13 +216,13 @@ function Connection:update(table_name, data, where_clause, returning, ...)
|
|||||||
error("WHERE clause cannot be empty for UPDATE")
|
error("WHERE clause cannot be empty for UPDATE")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local param_count = #keys
|
local param_count = #keys
|
||||||
|
|
||||||
local sets = {}
|
local sets = {}
|
||||||
for i, key in ipairs(keys) do
|
for i, key in ipairs(keys) do
|
||||||
table.insert(sets, string.template("${key} = $${num}", {
|
tbl.insert(sets, string.template("${key} = $${num}", {
|
||||||
key = key,
|
key = key,
|
||||||
num = tostring(i)
|
num = tostring(i)
|
||||||
}))
|
}))
|
||||||
@ -232,14 +233,14 @@ function Connection:update(table_name, data, where_clause, returning, ...)
|
|||||||
local where_clause_with_params = where_clause
|
local where_clause_with_params = where_clause
|
||||||
for i = 1, #where_args do
|
for i = 1, #where_args do
|
||||||
param_count = param_count + 1
|
param_count = param_count + 1
|
||||||
table.insert(values, where_args[i])
|
tbl.insert(values, where_args[i])
|
||||||
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
||||||
string.template("$${num}", {num = tostring(param_count)}), 1)
|
string.template("$${num}", {num = tostring(param_count)}), 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
sets = table.concat(sets, ", "),
|
sets = tbl.concat(sets, ", "),
|
||||||
where = where_clause_with_params
|
where = where_clause_with_params
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -267,7 +268,7 @@ function Connection:delete(table_name, where_clause, returning, ...)
|
|||||||
local values = {}
|
local values = {}
|
||||||
local where_clause_with_params = where_clause
|
local where_clause_with_params = where_clause
|
||||||
for i = 1, #where_args do
|
for i = 1, #where_args do
|
||||||
table.insert(values, where_args[i])
|
tbl.insert(values, where_args[i])
|
||||||
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
||||||
string.template("$${num}", {num = tostring(i)}), 1)
|
string.template("$${num}", {num = tostring(i)}), 1)
|
||||||
end
|
end
|
||||||
@ -295,7 +296,7 @@ function Connection:select(table_name, columns, where_clause, ...)
|
|||||||
|
|
||||||
columns = columns or "*"
|
columns = columns or "*"
|
||||||
if type(columns) == "table" then
|
if type(columns) == "table" then
|
||||||
columns = table.concat(columns, ", ")
|
columns = tbl.concat(columns, ", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
local query
|
local query
|
||||||
@ -305,7 +306,7 @@ function Connection:select(table_name, columns, where_clause, ...)
|
|||||||
local values = {}
|
local values = {}
|
||||||
local where_clause_with_params = where_clause
|
local where_clause_with_params = where_clause
|
||||||
for i = 1, #where_args do
|
for i = 1, #where_args do
|
||||||
table.insert(values, where_args[i])
|
tbl.insert(values, where_args[i])
|
||||||
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
where_clause_with_params = string.replace(where_clause_with_params, "?",
|
||||||
string.template("$${num}", {num = tostring(i)}), 1)
|
string.template("$${num}", {num = tostring(i)}), 1)
|
||||||
end
|
end
|
||||||
@ -410,7 +411,7 @@ function Connection:create_index(index_name, table_name, columns, unique, method
|
|||||||
|
|
||||||
local unique_clause = unique and "UNIQUE " or ""
|
local unique_clause = unique and "UNIQUE " or ""
|
||||||
local method_clause = method and string.template(" USING ${method}", {method = string.upper(method)}) or ""
|
local method_clause = method and string.template(" USING ${method}", {method = string.upper(method)}) or ""
|
||||||
local columns_str = type(columns) == "table" and table.concat(columns, ", ") or tostring(columns)
|
local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns)
|
||||||
|
|
||||||
local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table}${method} (${columns})", {
|
local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table}${method} (${columns})", {
|
||||||
unique = unique_clause,
|
unique = unique_clause,
|
||||||
@ -459,7 +460,7 @@ function Connection:reindex(name, type)
|
|||||||
local valid_types = {"INDEX", "TABLE", "SCHEMA", "DATABASE", "SYSTEM"}
|
local valid_types = {"INDEX", "TABLE", "SCHEMA", "DATABASE", "SYSTEM"}
|
||||||
local type_upper = string.upper(type)
|
local type_upper = string.upper(type)
|
||||||
|
|
||||||
if not table.contains(valid_types, type_upper) then
|
if not tbl.contains(valid_types, type_upper) then
|
||||||
error(string.template("Invalid REINDEX type: ${type}", {type = type}))
|
error(string.template("Invalid REINDEX type: ${type}", {type = type}))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -709,7 +710,7 @@ end
|
|||||||
|
|
||||||
-- Simplified result processing utilities
|
-- Simplified result processing utilities
|
||||||
function postgres.to_array(results, column_name)
|
function postgres.to_array(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -717,11 +718,11 @@ function postgres.to_array(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.map(results, function(row) return row[column_name] end)
|
return tbl.map(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function postgres.to_map(results, key_column, value_column)
|
function postgres.to_map(results, key_column, value_column)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -738,7 +739,7 @@ function postgres.to_map(results, key_column, value_column)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function postgres.group_by(results, column_name)
|
function postgres.group_by(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -746,18 +747,18 @@ function postgres.group_by(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.group_by(results, function(row) return row[column_name] end)
|
return tbl.group_by(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simplified debug helper
|
-- Simplified debug helper
|
||||||
function postgres.print_results(results)
|
function postgres.print_results(results)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
print("No results")
|
print("No results")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local columns = table.keys(results[1])
|
local columns = tbl.keys(results[1])
|
||||||
table.sort(columns)
|
tbl.sort(columns)
|
||||||
|
|
||||||
-- Calculate column widths
|
-- Calculate column widths
|
||||||
local widths = {}
|
local widths = {}
|
||||||
@ -773,19 +774,19 @@ function postgres.print_results(results)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Print header
|
-- Print header
|
||||||
local header_parts = table.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
||||||
local separator_parts = table.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
||||||
|
|
||||||
print(table.concat(header_parts, " | "))
|
print(tbl.concat(header_parts, " | "))
|
||||||
print(table.concat(separator_parts, "-+-"))
|
print(tbl.concat(separator_parts, "-+-"))
|
||||||
|
|
||||||
-- Print rows
|
-- Print rows
|
||||||
for _, row in ipairs(results) do
|
for _, row in ipairs(results) do
|
||||||
local value_parts = table.map(columns, function(col)
|
local value_parts = tbl.map(columns, function(col)
|
||||||
local value = tostring(row[col] or "")
|
local value = tostring(row[col] or "")
|
||||||
return string.pad_right(value, widths[col])
|
return string.pad_right(value, widths[col])
|
||||||
end)
|
end)
|
||||||
print(table.concat(value_parts, " | "))
|
print(tbl.concat(value_parts, " | "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"Moonshark/modules/fs"
|
"Moonshark/modules/fs"
|
||||||
"Moonshark/modules/http"
|
"Moonshark/modules/http"
|
||||||
"Moonshark/modules/kv"
|
"Moonshark/modules/kv"
|
||||||
|
"Moonshark/modules/math"
|
||||||
"Moonshark/modules/sql"
|
"Moonshark/modules/sql"
|
||||||
lua_string "Moonshark/modules/string+"
|
lua_string "Moonshark/modules/string+"
|
||||||
|
|
||||||
@ -35,6 +36,7 @@ func New() *Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
maps.Copy(r.goFuncs, lua_string.GetFunctionList())
|
maps.Copy(r.goFuncs, lua_string.GetFunctionList())
|
||||||
|
maps.Copy(r.goFuncs, math.GetFunctionList())
|
||||||
maps.Copy(r.goFuncs, crypto.GetFunctionList())
|
maps.Copy(r.goFuncs, crypto.GetFunctionList())
|
||||||
maps.Copy(r.goFuncs, fs.GetFunctionList())
|
maps.Copy(r.goFuncs, fs.GetFunctionList())
|
||||||
maps.Copy(r.goFuncs, http.GetFunctionList())
|
maps.Copy(r.goFuncs, http.GetFunctionList())
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
local tbl = require("table")
|
||||||
local sqlite = {}
|
local sqlite = {}
|
||||||
|
|
||||||
local Connection = {}
|
local Connection = {}
|
||||||
@ -117,14 +118,14 @@ function Connection:insert(table_name, data)
|
|||||||
error("Table name cannot be empty")
|
error("Table name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = table.map(keys, function() return "?" end)
|
local placeholders = tbl.map(keys, function() return "?" end)
|
||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders})", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", ")
|
placeholders = tbl.concat(placeholders, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
@ -135,10 +136,10 @@ function Connection:upsert(table_name, data, conflict_columns)
|
|||||||
error("Table name cannot be empty")
|
error("Table name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local placeholders = table.map(keys, function() return "?" end)
|
local placeholders = tbl.map(keys, function() return "?" end)
|
||||||
local updates = table.map(keys, function(key)
|
local updates = tbl.map(keys, function(key)
|
||||||
return string.template("${key} = excluded.${key}", {key = key})
|
return string.template("${key} = excluded.${key}", {key = key})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -147,16 +148,16 @@ function Connection:upsert(table_name, data, conflict_columns)
|
|||||||
if type(conflict_columns) == "string" then
|
if type(conflict_columns) == "string" then
|
||||||
conflict_clause = string.template("(${columns})", {columns = conflict_columns})
|
conflict_clause = string.template("(${columns})", {columns = conflict_columns})
|
||||||
else
|
else
|
||||||
conflict_clause = string.template("(${columns})", {columns = table.concat(conflict_columns, ", ")})
|
conflict_clause = string.template("(${columns})", {columns = tbl.concat(conflict_columns, ", ")})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", {
|
local query = string.template("INSERT INTO ${table} (${columns}) VALUES (${placeholders}) ON CONFLICT ${conflict} DO UPDATE SET ${updates}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
columns = table.concat(keys, ", "),
|
columns = tbl.concat(keys, ", "),
|
||||||
placeholders = table.concat(placeholders, ", "),
|
placeholders = tbl.concat(placeholders, ", "),
|
||||||
conflict = conflict_clause,
|
conflict = conflict_clause,
|
||||||
updates = table.concat(updates, ", ")
|
updates = tbl.concat(updates, ", ")
|
||||||
})
|
})
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
@ -170,21 +171,21 @@ function Connection:update(table_name, data, where_clause, ...)
|
|||||||
error("WHERE clause cannot be empty for UPDATE")
|
error("WHERE clause cannot be empty for UPDATE")
|
||||||
end
|
end
|
||||||
|
|
||||||
local keys = table.keys(data)
|
local keys = tbl.keys(data)
|
||||||
local values = table.values(data)
|
local values = tbl.values(data)
|
||||||
local sets = table.map(keys, function(key)
|
local sets = tbl.map(keys, function(key)
|
||||||
return string.template("${key} = ?", {key = key})
|
return string.template("${key} = ?", {key = key})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
local query = string.template("UPDATE ${table} SET ${sets} WHERE ${where}", {
|
||||||
table = table_name,
|
table = table_name,
|
||||||
sets = table.concat(sets, ", "),
|
sets = tbl.concat(sets, ", "),
|
||||||
where = where_clause
|
where = where_clause
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Add WHERE clause parameters
|
-- Add WHERE clause parameters
|
||||||
local where_args = {...}
|
local where_args = {...}
|
||||||
table.extend(values, where_args)
|
tbl.extend(values, where_args)
|
||||||
|
|
||||||
return self:exec(query, unpack(values))
|
return self:exec(query, unpack(values))
|
||||||
end
|
end
|
||||||
@ -211,7 +212,7 @@ function Connection:select(table_name, columns, where_clause, ...)
|
|||||||
|
|
||||||
columns = columns or "*"
|
columns = columns or "*"
|
||||||
if type(columns) == "table" then
|
if type(columns) == "table" then
|
||||||
columns = table.concat(columns, ", ")
|
columns = tbl.concat(columns, ", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
local query
|
local query
|
||||||
@ -251,7 +252,7 @@ function Connection:column_exists(table_name, column_name)
|
|||||||
|
|
||||||
local result = self:query(string.template("PRAGMA table_info(${table})", {table = table_name}))
|
local result = self:query(string.template("PRAGMA table_info(${table})", {table = table_name}))
|
||||||
if result then
|
if result then
|
||||||
return table.any(result, function(row)
|
return tbl.any(result, function(row)
|
||||||
return string.iequals(row.name, string.trim(column_name))
|
return string.iequals(row.name, string.trim(column_name))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
@ -297,7 +298,7 @@ function Connection:create_index(index_name, table_name, columns, unique)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local unique_clause = unique and "UNIQUE " or ""
|
local unique_clause = unique and "UNIQUE " or ""
|
||||||
local columns_str = type(columns) == "table" and table.concat(columns, ", ") or tostring(columns)
|
local columns_str = type(columns) == "table" and tbl.concat(columns, ", ") or tostring(columns)
|
||||||
|
|
||||||
local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table} (${columns})", {
|
local query = string.template("CREATE ${unique}INDEX IF NOT EXISTS ${index} ON ${table} (${columns})", {
|
||||||
unique = unique_clause,
|
unique = unique_clause,
|
||||||
@ -339,7 +340,7 @@ function Connection:journal_mode(mode)
|
|||||||
mode = mode or "WAL"
|
mode = mode or "WAL"
|
||||||
local valid_modes = {"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}
|
local valid_modes = {"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}
|
||||||
|
|
||||||
if not table.contains(table.map(valid_modes, string.upper), string.upper(mode)) then
|
if not tbl.contains(tbl.map(valid_modes, string.upper), string.upper(mode)) then
|
||||||
error("Invalid journal mode: " .. mode)
|
error("Invalid journal mode: " .. mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -350,7 +351,7 @@ function Connection:synchronous(level)
|
|||||||
level = level or "NORMAL"
|
level = level or "NORMAL"
|
||||||
local valid_levels = {"OFF", "NORMAL", "FULL", "EXTRA"}
|
local valid_levels = {"OFF", "NORMAL", "FULL", "EXTRA"}
|
||||||
|
|
||||||
if not table.contains(valid_levels, string.upper(level)) then
|
if not tbl.contains(valid_levels, string.upper(level)) then
|
||||||
error("Invalid synchronous level: " .. level)
|
error("Invalid synchronous level: " .. level)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -369,7 +370,7 @@ function Connection:temp_store(mode)
|
|||||||
mode = mode or "MEMORY"
|
mode = mode or "MEMORY"
|
||||||
local valid_modes = {"DEFAULT", "FILE", "MEMORY"}
|
local valid_modes = {"DEFAULT", "FILE", "MEMORY"}
|
||||||
|
|
||||||
if not table.contains(valid_modes, string.upper(mode)) then
|
if not tbl.contains(valid_modes, string.upper(mode)) then
|
||||||
error("Invalid temp_store mode: " .. mode)
|
error("Invalid temp_store mode: " .. mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -510,7 +511,7 @@ end
|
|||||||
|
|
||||||
-- Simplified result processing using table utilities
|
-- Simplified result processing using table utilities
|
||||||
function sqlite.to_array(results, column_name)
|
function sqlite.to_array(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -518,11 +519,11 @@ function sqlite.to_array(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.map(results, function(row) return row[column_name] end)
|
return tbl.map(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sqlite.to_map(results, key_column, value_column)
|
function sqlite.to_map(results, key_column, value_column)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -539,7 +540,7 @@ function sqlite.to_map(results, key_column, value_column)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function sqlite.group_by(results, column_name)
|
function sqlite.group_by(results, column_name)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -547,21 +548,21 @@ function sqlite.group_by(results, column_name)
|
|||||||
error("Column name cannot be empty")
|
error("Column name cannot be empty")
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.group_by(results, function(row) return row[column_name] end)
|
return tbl.group_by(results, function(row) return row[column_name] end)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Simplified debug helper
|
-- Simplified debug helper
|
||||||
function sqlite.print_results(results)
|
function sqlite.print_results(results)
|
||||||
if not results or table.is_empty(results) then
|
if not results or tbl.is_empty(results) then
|
||||||
print("No results")
|
print("No results")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local columns = table.keys(results[1])
|
local columns = tbl.keys(results[1])
|
||||||
table.sort(columns)
|
tbl.sort(columns)
|
||||||
|
|
||||||
-- Calculate column widths
|
-- Calculate column widths
|
||||||
local widths = table.map_values(table.to_map(columns, function(col) return col end, function(col) return string.length(col) end), function(width) return width end)
|
local widths = tbl.map_values(tbl.to_map(columns, function(col) return col end, function(col) return string.length(col) end), function(width) return width end)
|
||||||
|
|
||||||
for _, row in ipairs(results) do
|
for _, row in ipairs(results) do
|
||||||
for _, col in ipairs(columns) do
|
for _, col in ipairs(columns) do
|
||||||
@ -571,19 +572,19 @@ function sqlite.print_results(results)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Print header
|
-- Print header
|
||||||
local header_parts = table.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
local header_parts = tbl.map(columns, function(col) return string.pad_right(col, widths[col]) end)
|
||||||
local separator_parts = table.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
local separator_parts = tbl.map(columns, function(col) return string.repeat_("-", widths[col]) end)
|
||||||
|
|
||||||
print(table.concat(header_parts, " | "))
|
print(tbl.concat(header_parts, " | "))
|
||||||
print(table.concat(separator_parts, "-+-"))
|
print(tbl.concat(separator_parts, "-+-"))
|
||||||
|
|
||||||
-- Print rows
|
-- Print rows
|
||||||
for _, row in ipairs(results) do
|
for _, row in ipairs(results) do
|
||||||
local value_parts = table.map(columns, function(col)
|
local value_parts = tbl.map(columns, function(col)
|
||||||
local value = tostring(row[col] or "")
|
local value = tostring(row[col] or "")
|
||||||
return string.pad_right(value, widths[col])
|
return string.pad_right(value, widths[col])
|
||||||
end)
|
end)
|
||||||
print(table.concat(value_parts, " | "))
|
print(tbl.concat(value_parts, " | "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
436
tests/math.lua
436
tests/math.lua
@ -1,393 +1,171 @@
|
|||||||
require("tests")
|
require("tests")
|
||||||
|
local math = require("math")
|
||||||
|
|
||||||
-- Constants tests
|
-- Test constants
|
||||||
test("math.pi", function()
|
test("Constants", function()
|
||||||
assert_close(math.pi, 3.14159265358979323846)
|
assert_close(math.pi, 3.14159265358979323846)
|
||||||
assert(math.pi > 3.14 and math.pi < 3.15)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.tau", function()
|
|
||||||
assert_close(math.tau, 6.28318530717958647693)
|
assert_close(math.tau, 6.28318530717958647693)
|
||||||
assert_close(math.tau, 2 * math.pi)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.e", function()
|
|
||||||
assert_close(math.e, 2.71828182845904523536)
|
assert_close(math.e, 2.71828182845904523536)
|
||||||
assert(math.e > 2.7 and math.e < 2.8)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.phi", function()
|
|
||||||
assert_close(math.phi, 1.61803398874989484820)
|
assert_close(math.phi, 1.61803398874989484820)
|
||||||
assert_close(math.phi, (1 + math.sqrt(5)) / 2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.infinity", function()
|
|
||||||
assert_equal(math.infinity, 1/0)
|
assert_equal(math.infinity, 1/0)
|
||||||
assert(math.infinity > 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.nan", function()
|
|
||||||
assert_equal(math.isnan(math.nan), true)
|
assert_equal(math.isnan(math.nan), true)
|
||||||
assert(math.nan ~= math.nan) -- NaN property
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Extended functions tests
|
-- Test extended functions
|
||||||
test("math.cbrt", function()
|
test("Extended Functions", function()
|
||||||
assert_close(math.cbrt(8), 2)
|
assert_close(math.cbrt(8), 2)
|
||||||
assert_close(math.cbrt(-8), -2)
|
assert_close(math.cbrt(-8), -2)
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.hypot", function()
|
|
||||||
assert_close(math.hypot(3, 4), 5)
|
assert_close(math.hypot(3, 4), 5)
|
||||||
assert_close(math.hypot(5, 12), 13)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.isnan", function()
|
|
||||||
assert_equal(math.isnan(0/0), true)
|
assert_equal(math.isnan(0/0), true)
|
||||||
assert_equal(math.isnan(5), false)
|
assert_equal(math.isnan(5), false)
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.isfinite", function()
|
|
||||||
assert_equal(math.isfinite(5), true)
|
assert_equal(math.isfinite(5), true)
|
||||||
assert_equal(math.isfinite(math.infinity), false)
|
assert_equal(math.isfinite(math.infinity), false)
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.sign", function()
|
|
||||||
assert_equal(math.sign(5), 1)
|
assert_equal(math.sign(5), 1)
|
||||||
assert_equal(math.sign(-5), -1)
|
assert_equal(math.sign(-5), -1)
|
||||||
assert_equal(math.sign(0), 0)
|
assert_equal(math.sign(0), 0)
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.clamp", function()
|
|
||||||
assert_equal(math.clamp(5, 0, 3), 3)
|
assert_equal(math.clamp(5, 0, 3), 3)
|
||||||
assert_equal(math.clamp(-1, 0, 3), 0)
|
assert_equal(math.clamp(-1, 0, 3), 0)
|
||||||
assert_equal(math.clamp(2, 0, 3), 2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.lerp", function()
|
|
||||||
assert_close(math.lerp(0, 10, 0.5), 5)
|
assert_close(math.lerp(0, 10, 0.5), 5)
|
||||||
assert_close(math.lerp(2, 8, 0.25), 3.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.smoothstep", function()
|
|
||||||
assert_close(math.smoothstep(0, 1, 0.5), 0.5)
|
|
||||||
assert_close(math.smoothstep(0, 10, 5), 0.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.map", function()
|
|
||||||
assert_close(math.map(5, 0, 10, 0, 100), 50)
|
assert_close(math.map(5, 0, 10, 0, 100), 50)
|
||||||
assert_close(math.map(2, 0, 4, 10, 20), 15)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.round", function()
|
|
||||||
assert_equal(math.round(2.7), 3)
|
assert_equal(math.round(2.7), 3)
|
||||||
assert_equal(math.round(-2.7), -3)
|
assert_equal(math.round(-2.7), -3)
|
||||||
assert_equal(math.round(2.3), 2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.roundto", function()
|
|
||||||
assert_close(math.roundto(3.14159, 2), 3.14)
|
assert_close(math.roundto(3.14159, 2), 3.14)
|
||||||
assert_close(math.roundto(123.456, 1), 123.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.normalize_angle", function()
|
|
||||||
assert_close(math.normalize_angle(math.pi * 2.5), math.pi * 0.5)
|
|
||||||
assert_close(math.normalize_angle(-math.pi * 2.5), -math.pi * 0.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.distance", function()
|
|
||||||
assert_close(math.distance(0, 0, 3, 4), 5)
|
assert_close(math.distance(0, 0, 3, 4), 5)
|
||||||
assert_close(math.distance(1, 1, 4, 5), 5)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.factorial", function()
|
-- Test random functions
|
||||||
assert_equal(math.factorial(5), 120)
|
test("Random Functions", function()
|
||||||
assert_equal(math.factorial(0), 1)
|
local r = math.randomf(0, 1)
|
||||||
assert_equal(math.factorial(-1), nil)
|
assert(r >= 0 and r < 1, "randomf should be in range [0, 1)")
|
||||||
|
local i = math.randint(1, 10)
|
||||||
|
assert(i >= 1 and i <= 10, "randint should be in range [1, 10]")
|
||||||
|
assert_equal(type(math.randboolean()), "boolean")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.gcd", function()
|
-- Test statistics
|
||||||
assert_equal(math.gcd(48, 18), 6)
|
test("Statistics", function()
|
||||||
assert_equal(math.gcd(100, 25), 25)
|
local data = {1, 2, 3, 4, 5}
|
||||||
end)
|
assert_equal(math.sum(data), 15)
|
||||||
|
assert_equal(math.mean(data), 3)
|
||||||
test("math.lcm", function()
|
assert_equal(math.median(data), 3)
|
||||||
assert_equal(math.lcm(4, 6), 12)
|
assert_close(math.variance(data), 2)
|
||||||
assert_equal(math.lcm(15, 20), 60)
|
assert_close(math.stdev(data), math.sqrt(2))
|
||||||
end)
|
local min, max = math.minmax(data)
|
||||||
|
assert_equal(min, 1)
|
||||||
-- Random functions tests
|
assert_equal(max, 5)
|
||||||
test("math.randomf", function()
|
|
||||||
local r1 = math.randomf(0, 1)
|
|
||||||
local r2 = math.randomf(5, 10)
|
|
||||||
assert(r1 >= 0 and r1 < 1)
|
|
||||||
assert(r2 >= 5 and r2 < 10)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.randint", function()
|
|
||||||
local i1 = math.randint(1, 10)
|
|
||||||
local i2 = math.randint(50, 60)
|
|
||||||
assert(i1 >= 1 and i1 <= 10)
|
|
||||||
assert(i2 >= 50 and i2 <= 60)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.randboolean", function()
|
|
||||||
local b1 = math.randboolean()
|
|
||||||
local b2 = math.randboolean(0.8)
|
|
||||||
assert_equal(type(b1), "boolean")
|
|
||||||
assert_equal(type(b2), "boolean")
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Statistics tests
|
|
||||||
test("math.sum", function()
|
|
||||||
assert_equal(math.sum({1, 2, 3, 4, 5}), 15)
|
|
||||||
assert_equal(math.sum({10, 20, 30}), 60)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.mean", function()
|
|
||||||
assert_equal(math.mean({1, 2, 3, 4, 5}), 3)
|
|
||||||
assert_equal(math.mean({10, 20, 30}), 20)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.median", function()
|
|
||||||
assert_equal(math.median({1, 2, 3, 4, 5}), 3)
|
|
||||||
assert_equal(math.median({1, 2, 3, 4}), 2.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.variance", function()
|
|
||||||
assert_close(math.variance({1, 2, 3, 4, 5}), 2)
|
|
||||||
assert_close(math.variance({10, 10, 10}), 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.stdev", function()
|
|
||||||
assert_close(math.stdev({1, 2, 3, 4, 5}), math.sqrt(2))
|
|
||||||
assert_close(math.stdev({10, 10, 10}), 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.mode", function()
|
|
||||||
assert_equal(math.mode({1, 2, 2, 3}), 2)
|
assert_equal(math.mode({1, 2, 2, 3}), 2)
|
||||||
assert_equal(math.mode({5, 5, 4, 4, 4}), 4)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.minmax", function()
|
-- Test 2D vectors
|
||||||
local min1, max1 = math.minmax({1, 2, 3, 4, 5})
|
test("2D Vectors", function()
|
||||||
local min2, max2 = math.minmax({-5, 0, 10})
|
|
||||||
assert_equal(min1, 1)
|
|
||||||
assert_equal(max1, 5)
|
|
||||||
assert_equal(min2, -5)
|
|
||||||
assert_equal(max2, 10)
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- 2D Vector tests
|
|
||||||
test("math.vec2.new", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
local v1 = math.vec2.new(3, 4)
|
||||||
local v2 = math.vec2.new()
|
local v2 = math.vec2.new(1, 2)
|
||||||
|
|
||||||
assert_equal(v1.x, 3)
|
assert_equal(v1.x, 3)
|
||||||
assert_equal(v1.y, 4)
|
assert_equal(v1.y, 4)
|
||||||
assert_equal(v2.x, 0)
|
|
||||||
assert_equal(v2.y, 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec2.add", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
|
||||||
local v2 = math.vec2.new(1, 2)
|
|
||||||
local result = math.vec2.add(v1, v2)
|
|
||||||
assert_equal(result.x, 4)
|
|
||||||
assert_equal(result.y, 6)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec2.sub", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
|
||||||
local v2 = math.vec2.new(1, 2)
|
|
||||||
local result = math.vec2.sub(v1, v2)
|
|
||||||
assert_equal(result.x, 2)
|
|
||||||
assert_equal(result.y, 2)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec2.mul", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
|
||||||
local result1 = math.vec2.mul(v1, 2)
|
|
||||||
local result2 = math.vec2.mul(v1, math.vec2.new(2, 3))
|
|
||||||
assert_equal(result1.x, 6)
|
|
||||||
assert_equal(result1.y, 8)
|
|
||||||
assert_equal(result2.x, 6)
|
|
||||||
assert_equal(result2.y, 12)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec2.dot", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
|
||||||
local v2 = math.vec2.new(1, 2)
|
|
||||||
assert_equal(math.vec2.dot(v1, v2), 11)
|
|
||||||
assert_equal(math.vec2.dot(math.vec2.new(1, 0), math.vec2.new(0, 1)), 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec2.length", function()
|
|
||||||
local v1 = math.vec2.new(3, 4)
|
|
||||||
local v2 = math.vec2.new(0, 5)
|
|
||||||
assert_close(math.vec2.length(v1), 5)
|
assert_close(math.vec2.length(v1), 5)
|
||||||
assert_close(math.vec2.length(v2), 5)
|
assert_equal(math.vec2.length_squared(v1), 25)
|
||||||
end)
|
|
||||||
|
local v3 = math.vec2.add(v1, v2)
|
||||||
test("math.vec2.distance", function()
|
assert_equal(v3.x, 4)
|
||||||
local v1 = math.vec2.new(0, 0)
|
assert_equal(v3.y, 6)
|
||||||
local v2 = math.vec2.new(3, 4)
|
|
||||||
assert_close(math.vec2.distance(v1, v2), 5)
|
local v4 = math.vec2.sub(v1, v2)
|
||||||
assert_close(math.vec2.distance(math.vec2.new(1, 1), math.vec2.new(4, 5)), 5)
|
assert_equal(v4.x, 2)
|
||||||
end)
|
assert_equal(v4.y, 2)
|
||||||
|
|
||||||
test("math.vec2.normalize", function()
|
local v5 = math.vec2.mul(v1, 2)
|
||||||
local v1 = math.vec2.new(3, 4)
|
assert_equal(v5.x, 6)
|
||||||
|
assert_equal(v5.y, 8)
|
||||||
|
|
||||||
|
assert_equal(math.vec2.dot(v1, v2), 11)
|
||||||
|
assert_close(math.vec2.distance(v1, v2), math.sqrt(8))
|
||||||
|
|
||||||
local normalized = math.vec2.normalize(v1)
|
local normalized = math.vec2.normalize(v1)
|
||||||
assert_close(math.vec2.length(normalized), 1)
|
assert_close(math.vec2.length(normalized), 1)
|
||||||
assert_close(normalized.x, 0.6)
|
|
||||||
assert_close(normalized.y, 0.8)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.vec2.rotate", function()
|
-- Test 3D vectors
|
||||||
local v1 = math.vec2.new(1, 0)
|
test("3D Vectors", function()
|
||||||
local rotated90 = math.vec2.rotate(v1, math.pi/2)
|
|
||||||
local rotated180 = math.vec2.rotate(v1, math.pi)
|
|
||||||
assert_close(rotated90.x, 0, 1e-10)
|
|
||||||
assert_close(rotated90.y, 1)
|
|
||||||
assert_close(rotated180.x, -1)
|
|
||||||
assert_close(rotated180.y, 0, 1e-10)
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- 3D Vector tests
|
|
||||||
test("math.vec3.new", function()
|
|
||||||
local v1 = math.vec3.new(1, 2, 3)
|
local v1 = math.vec3.new(1, 2, 3)
|
||||||
local v2 = math.vec3.new()
|
local v2 = math.vec3.new(4, 5, 6)
|
||||||
assert_equal(v1.x, 1)
|
|
||||||
assert_equal(v1.y, 2)
|
|
||||||
assert_equal(v1.z, 3)
|
|
||||||
assert_equal(v2.x, 0)
|
|
||||||
assert_equal(v2.y, 0)
|
|
||||||
assert_equal(v2.z, 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec3.cross", function()
|
|
||||||
local v1 = math.vec3.new(1, 0, 0)
|
|
||||||
local v2 = math.vec3.new(0, 1, 0)
|
|
||||||
local cross = math.vec3.cross(v1, v2)
|
|
||||||
assert_equal(cross.x, 0)
|
|
||||||
assert_equal(cross.y, 0)
|
|
||||||
assert_equal(cross.z, 1)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.vec3.length", function()
|
|
||||||
local v1 = math.vec3.new(1, 2, 3)
|
|
||||||
local v2 = math.vec3.new(0, 0, 5)
|
|
||||||
assert_close(math.vec3.length(v1), math.sqrt(14))
|
assert_close(math.vec3.length(v1), math.sqrt(14))
|
||||||
assert_close(math.vec3.length(v2), 5)
|
assert_equal(math.vec3.dot(v1, v2), 32)
|
||||||
|
|
||||||
|
local cross = math.vec3.cross(v1, v2)
|
||||||
|
assert_equal(cross.x, -3)
|
||||||
|
assert_equal(cross.y, 6)
|
||||||
|
assert_equal(cross.z, -3)
|
||||||
|
|
||||||
|
local add = math.vec3.add(v1, v2)
|
||||||
|
assert_equal(add.x, 5)
|
||||||
|
assert_equal(add.y, 7)
|
||||||
|
assert_equal(add.z, 9)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Matrix tests
|
-- Test 2x2 matrices
|
||||||
test("math.mat2.det", function()
|
test("2x2 Matrices", function()
|
||||||
local m1 = math.mat2.new(1, 2, 3, 4)
|
|
||||||
local m2 = math.mat2.new(2, 0, 0, 3)
|
|
||||||
assert_equal(math.mat2.det(m1), -2)
|
|
||||||
assert_equal(math.mat2.det(m2), 6)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.mat2.mul", function()
|
|
||||||
local m1 = math.mat2.new(1, 2, 3, 4)
|
local m1 = math.mat2.new(1, 2, 3, 4)
|
||||||
local m2 = math.mat2.new(2, 0, 1, 3)
|
local m2 = math.mat2.new(2, 0, 1, 3)
|
||||||
|
|
||||||
|
assert_equal(math.mat2.det(m1), -2)
|
||||||
|
|
||||||
local product = math.mat2.mul(m1, m2)
|
local product = math.mat2.mul(m1, m2)
|
||||||
assert_equal(product[1][1], 4)
|
assert_equal(product[1][1], 4)
|
||||||
assert_equal(product[1][2], 6)
|
assert_equal(product[1][2], 6)
|
||||||
assert_equal(product[2][1], 10)
|
assert_equal(product[2][1], 10)
|
||||||
assert_equal(product[2][2], 12)
|
assert_equal(product[2][2], 12)
|
||||||
|
|
||||||
|
local rotation = math.mat2.rotation(math.pi/2)
|
||||||
|
assert_close(rotation[1][1], 0, 1e-10)
|
||||||
|
assert_close(rotation[1][2], -1)
|
||||||
|
assert_close(rotation[2][1], 1)
|
||||||
|
assert_close(rotation[2][2], 0, 1e-10)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.mat2.rotation", function()
|
-- Test 3x3 matrices
|
||||||
local rot90 = math.mat2.rotation(math.pi/2)
|
test("3x3 Matrices", function()
|
||||||
local rot180 = math.mat2.rotation(math.pi)
|
|
||||||
assert_close(rot90[1][1], 0, 1e-10)
|
|
||||||
assert_close(rot90[1][2], -1)
|
|
||||||
assert_close(rot180[1][1], -1)
|
|
||||||
assert_close(rot180[2][2], -1)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.mat3.transform_point", function()
|
|
||||||
local translation = math.mat3.translation(5, 10)
|
|
||||||
local point1 = {x = 1, y = 2}
|
|
||||||
local point2 = {x = 0, y = 0}
|
|
||||||
local t1 = math.mat3.transform_point(translation, point1)
|
|
||||||
local t2 = math.mat3.transform_point(translation, point2)
|
|
||||||
assert_equal(t1.x, 6)
|
|
||||||
assert_equal(t1.y, 12)
|
|
||||||
assert_equal(t2.x, 5)
|
|
||||||
assert_equal(t2.y, 10)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.mat3.det", function()
|
|
||||||
local identity = math.mat3.identity()
|
local identity = math.mat3.identity()
|
||||||
local scale = math.mat3.scale(2, 3)
|
assert_equal(identity[1][1], 1)
|
||||||
assert_close(math.mat3.det(identity), 1)
|
assert_equal(identity[2][2], 1)
|
||||||
assert_close(math.mat3.det(scale), 6)
|
assert_equal(identity[3][3], 1)
|
||||||
|
assert_equal(identity[1][2], 0)
|
||||||
|
|
||||||
|
local translation = math.mat3.translation(5, 10)
|
||||||
|
assert_equal(translation[1][3], 5)
|
||||||
|
assert_equal(translation[2][3], 10)
|
||||||
|
|
||||||
|
local point = {x = 1, y = 2}
|
||||||
|
local transformed = math.mat3.transform_point(translation, point)
|
||||||
|
assert_equal(transformed.x, 6)
|
||||||
|
assert_equal(transformed.y, 12)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Geometry tests
|
-- Test geometry functions
|
||||||
test("math.geometry.triangle_area", function()
|
test("Geometry", function()
|
||||||
local area1 = math.geometry.triangle_area(0, 0, 4, 0, 0, 3)
|
assert_close(math.geometry.triangle_area(0, 0, 4, 0, 0, 3), 6)
|
||||||
local area2 = math.geometry.triangle_area(0, 0, 2, 0, 1, 2)
|
assert_equal(math.geometry.point_in_triangle(1, 1, 0, 0, 4, 0, 0, 3), true)
|
||||||
assert_close(area1, 6)
|
assert_equal(math.geometry.point_in_triangle(5, 5, 0, 0, 4, 0, 0, 3), false)
|
||||||
assert_close(area2, 2)
|
|
||||||
|
local intersects, x, y = math.geometry.line_intersect(0, 0, 2, 2, 0, 2, 2, 0)
|
||||||
|
assert_equal(intersects, true)
|
||||||
|
assert_close(x, 1)
|
||||||
|
assert_close(y, 1)
|
||||||
|
|
||||||
|
local closest_x, closest_y = math.geometry.closest_point_on_segment(1, 3, 0, 0, 4, 0)
|
||||||
|
assert_close(closest_x, 1)
|
||||||
|
assert_close(closest_y, 0)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("math.geometry.point_in_triangle", function()
|
-- Test interpolation
|
||||||
local inside1 = math.geometry.point_in_triangle(1, 1, 0, 0, 4, 0, 0, 3)
|
test("Interpolation", function()
|
||||||
local inside2 = math.geometry.point_in_triangle(5, 5, 0, 0, 4, 0, 0, 3)
|
assert_close(math.interpolation.bezier(0.5, 0, 1, 2, 3), 1.5)
|
||||||
assert_equal(inside1, true)
|
assert_close(math.interpolation.quadratic_bezier(0.5, 0, 2, 4), 2)
|
||||||
assert_equal(inside2, false)
|
assert_close(math.interpolation.smoothstep(0, 1, 0.5), 0.5)
|
||||||
end)
|
assert_close(math.interpolation.smootherstep(0, 1, 0.5), 0.5)
|
||||||
|
assert_close(math.interpolation.catmull_rom(0.5, 0, 1, 2, 3), 1.5)
|
||||||
test("math.geometry.line_intersect", function()
|
|
||||||
local intersects1, x1, y1 = math.geometry.line_intersect(0, 0, 2, 2, 0, 2, 2, 0)
|
|
||||||
local intersects2, x2, y2 = math.geometry.line_intersect(0, 0, 1, 1, 2, 2, 3, 3)
|
|
||||||
assert_equal(intersects1, true)
|
|
||||||
assert_close(x1, 1)
|
|
||||||
assert_close(y1, 1)
|
|
||||||
assert_equal(intersects2, false)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.geometry.closest_point_on_segment", function()
|
|
||||||
local x1, y1 = math.geometry.closest_point_on_segment(1, 3, 0, 0, 4, 0)
|
|
||||||
local x2, y2 = math.geometry.closest_point_on_segment(5, 1, 0, 0, 4, 0)
|
|
||||||
assert_close(x1, 1)
|
|
||||||
assert_close(y1, 0)
|
|
||||||
assert_close(x2, 4)
|
|
||||||
assert_close(y2, 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- Interpolation tests
|
|
||||||
test("math.interpolation.bezier", function()
|
|
||||||
local result1 = math.interpolation.bezier(0.5, 0, 1, 2, 3)
|
|
||||||
local result2 = math.interpolation.bezier(0, 0, 1, 2, 3)
|
|
||||||
assert_close(result1, 1.5)
|
|
||||||
assert_close(result2, 0)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.interpolation.quadratic_bezier", function()
|
|
||||||
local result1 = math.interpolation.quadratic_bezier(0.5, 0, 2, 4)
|
|
||||||
local result2 = math.interpolation.quadratic_bezier(1, 0, 2, 4)
|
|
||||||
assert_close(result1, 2)
|
|
||||||
assert_close(result2, 4)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.interpolation.smootherstep", function()
|
|
||||||
local result1 = math.interpolation.smootherstep(0, 1, 0.5)
|
|
||||||
local result2 = math.interpolation.smootherstep(0, 10, 5)
|
|
||||||
assert_close(result1, 0.5)
|
|
||||||
assert_close(result2, 0.5)
|
|
||||||
end)
|
|
||||||
|
|
||||||
test("math.interpolation.catmull_rom", function()
|
|
||||||
local result1 = math.interpolation.catmull_rom(0.5, 0, 1, 2, 3)
|
|
||||||
local result2 = math.interpolation.catmull_rom(0, 0, 1, 2, 3)
|
|
||||||
assert_close(result1, 1.5)
|
|
||||||
assert_close(result2, 1)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
summary()
|
summary()
|
||||||
test_exit()
|
test_exit()
|
||||||
433
tests/table.lua
433
tests/table.lua
@ -1,4 +1,5 @@
|
|||||||
require("tests")
|
require("tests")
|
||||||
|
local tbl = require("table")
|
||||||
|
|
||||||
-- Test data
|
-- Test data
|
||||||
local simple_array = {1, 2, 3, 4, 5}
|
local simple_array = {1, 2, 3, 4, 5}
|
||||||
@ -17,11 +18,11 @@ local nested_table = {
|
|||||||
test("Table Insert Operations", function()
|
test("Table Insert Operations", function()
|
||||||
local t = {1, 2, 3}
|
local t = {1, 2, 3}
|
||||||
|
|
||||||
table.insert(t, 4)
|
tbl.insert(t, 4)
|
||||||
assert_equal(4, #t)
|
assert_equal(4, #t)
|
||||||
assert_equal(4, t[4])
|
assert_equal(4, t[4])
|
||||||
|
|
||||||
table.insert(t, 2, "inserted")
|
tbl.insert(t, 2, "inserted")
|
||||||
assert_equal(5, #t)
|
assert_equal(5, #t)
|
||||||
assert_equal("inserted", t[2])
|
assert_equal("inserted", t[2])
|
||||||
assert_equal(2, t[3])
|
assert_equal(2, t[3])
|
||||||
@ -30,11 +31,11 @@ end)
|
|||||||
test("Table Remove Operations", function()
|
test("Table Remove Operations", function()
|
||||||
local t = {1, 2, 3, 4, 5}
|
local t = {1, 2, 3, 4, 5}
|
||||||
|
|
||||||
local removed = table.remove(t)
|
local removed = tbl.remove(t)
|
||||||
assert_equal(5, removed)
|
assert_equal(5, removed)
|
||||||
assert_equal(4, #t)
|
assert_equal(4, #t)
|
||||||
|
|
||||||
removed = table.remove(t, 2)
|
removed = tbl.remove(t, 2)
|
||||||
assert_equal(2, removed)
|
assert_equal(2, removed)
|
||||||
assert_equal(3, #t)
|
assert_equal(3, #t)
|
||||||
assert_equal(3, t[2])
|
assert_equal(3, t[2])
|
||||||
@ -42,23 +43,23 @@ end)
|
|||||||
|
|
||||||
test("Table Concat", function()
|
test("Table Concat", function()
|
||||||
local t = {"hello", "world", "test"}
|
local t = {"hello", "world", "test"}
|
||||||
assert_equal("helloworldtest", table.concat(t))
|
assert_equal("helloworldtest", tbl.concat(t))
|
||||||
assert_equal("hello,world,test", table.concat(t, ","))
|
assert_equal("hello,world,test", tbl.concat(t, ","))
|
||||||
assert_equal("world,test", table.concat(t, ",", 2))
|
assert_equal("world,test", tbl.concat(t, ",", 2))
|
||||||
assert_equal("world", table.concat(t, ",", 2, 2))
|
assert_equal("world", tbl.concat(t, ",", 2, 2))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Sort", function()
|
test("Table Sort", function()
|
||||||
local t = {3, 1, 4, 1, 5}
|
local t = {3, 1, 4, 1, 5}
|
||||||
table.sort(t)
|
tbl.sort(t)
|
||||||
assert_table_equal({1, 1, 3, 4, 5}, t)
|
assert_table_equal({1, 1, 3, 4, 5}, t)
|
||||||
|
|
||||||
local t2 = {"c", "a", "b"}
|
local t2 = {"c", "a", "b"}
|
||||||
table.sort(t2)
|
tbl.sort(t2)
|
||||||
assert_table_equal({"a", "b", "c"}, t2)
|
assert_table_equal({"a", "b", "c"}, t2)
|
||||||
|
|
||||||
local t3 = {3, 1, 4, 1, 5}
|
local t3 = {3, 1, 4, 1, 5}
|
||||||
table.sort(t3, function(a, b) return a > b end)
|
tbl.sort(t3, function(a, b) return a > b end)
|
||||||
assert_table_equal({5, 4, 3, 1, 1}, t3)
|
assert_table_equal({5, 4, 3, 1, 1}, t3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -67,39 +68,39 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Length and Size", function()
|
test("Table Length and Size", function()
|
||||||
assert_equal(5, table.length(simple_array))
|
assert_equal(5, tbl.length(simple_array))
|
||||||
assert_equal(0, table.length({}))
|
assert_equal(0, tbl.length({}))
|
||||||
|
|
||||||
assert_equal(3, table.size(simple_table))
|
assert_equal(3, tbl.size(simple_table))
|
||||||
assert_equal(4, table.size(mixed_table))
|
assert_equal(4, tbl.size(mixed_table))
|
||||||
assert_equal(0, table.size({}))
|
assert_equal(0, tbl.size({}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Empty Check", function()
|
test("Table Empty Check", function()
|
||||||
assert_equal(true, table.is_empty({}))
|
assert_equal(true, tbl.is_empty({}))
|
||||||
assert_equal(false, table.is_empty(simple_array))
|
assert_equal(false, tbl.is_empty(simple_array))
|
||||||
assert_equal(false, table.is_empty(simple_table))
|
assert_equal(false, tbl.is_empty(simple_table))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Array Check", function()
|
test("Table Array Check", function()
|
||||||
assert_equal(true, table.is_array(simple_array))
|
assert_equal(true, tbl.is_array(simple_array))
|
||||||
assert_equal(true, table.is_array({}))
|
assert_equal(true, tbl.is_array({}))
|
||||||
assert_equal(false, table.is_array(simple_table))
|
assert_equal(false, tbl.is_array(simple_table))
|
||||||
assert_equal(false, table.is_array(mixed_table))
|
assert_equal(false, tbl.is_array(mixed_table))
|
||||||
|
|
||||||
assert_equal(true, table.is_array({1, 2, 3}))
|
assert_equal(true, tbl.is_array({1, 2, 3}))
|
||||||
assert_equal(false, table.is_array({1, 2, nil, 4}))
|
assert_equal(false, tbl.is_array({1, 2, nil, 4}))
|
||||||
assert_equal(false, table.is_array({[0] = 1, [1] = 2}))
|
assert_equal(false, tbl.is_array({[0] = 1, [1] = 2}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Clear", function()
|
test("Table Clear", function()
|
||||||
local t = table.clone(simple_table)
|
local t = tbl.clone(simple_table)
|
||||||
table.clear(t)
|
tbl.clear(t)
|
||||||
assert_equal(true, table.is_empty(t))
|
assert_equal(true, tbl.is_empty(t))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Clone", function()
|
test("Table Clone", function()
|
||||||
local cloned = table.clone(simple_table)
|
local cloned = tbl.clone(simple_table)
|
||||||
assert_table_equal(simple_table, cloned)
|
assert_table_equal(simple_table, cloned)
|
||||||
|
|
||||||
-- Modify original shouldn't affect clone
|
-- Modify original shouldn't affect clone
|
||||||
@ -109,7 +110,7 @@ test("Table Clone", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Deep Copy", function()
|
test("Table Deep Copy", function()
|
||||||
local copied = table.deep_copy(nested_table)
|
local copied = tbl.deep_copy(nested_table)
|
||||||
assert_table_equal(nested_table, copied)
|
assert_table_equal(nested_table, copied)
|
||||||
|
|
||||||
-- Modify nested part shouldn't affect copy
|
-- Modify nested part shouldn't affect copy
|
||||||
@ -123,50 +124,50 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Contains", function()
|
test("Table Contains", function()
|
||||||
assert_equal(true, table.contains(simple_array, 3))
|
assert_equal(true, tbl.contains(simple_array, 3))
|
||||||
assert_equal(false, table.contains(simple_array, 6))
|
assert_equal(false, tbl.contains(simple_array, 6))
|
||||||
assert_equal(true, table.contains(simple_table, 2))
|
assert_equal(true, tbl.contains(simple_table, 2))
|
||||||
assert_equal(false, table.contains(simple_table, "hello"))
|
assert_equal(false, tbl.contains(simple_table, "hello"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Index Of", function()
|
test("Table Index Of", function()
|
||||||
assert_equal(3, table.index_of(simple_array, 3))
|
assert_equal(3, tbl.index_of(simple_array, 3))
|
||||||
assert_equal(nil, table.index_of(simple_array, 6))
|
assert_equal(nil, tbl.index_of(simple_array, 6))
|
||||||
assert_equal("b", table.index_of(simple_table, 2))
|
assert_equal("b", tbl.index_of(simple_table, 2))
|
||||||
assert_equal(nil, table.index_of(simple_table, "hello"))
|
assert_equal(nil, tbl.index_of(simple_table, "hello"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Find", function()
|
test("Table Find", function()
|
||||||
local value, key = table.find(simple_array, function(v) return v > 3 end)
|
local value, key = tbl.find(simple_array, function(v) return v > 3 end)
|
||||||
assert_equal(4, value)
|
assert_equal(4, value)
|
||||||
assert_equal(4, key)
|
assert_equal(4, key)
|
||||||
|
|
||||||
local value2, key2 = table.find(simple_table, function(v, k) return k == "b" end)
|
local value2, key2 = tbl.find(simple_table, function(v, k) return k == "b" end)
|
||||||
assert_equal(2, value2)
|
assert_equal(2, value2)
|
||||||
assert_equal("b", key2)
|
assert_equal("b", key2)
|
||||||
|
|
||||||
local value3 = table.find(simple_array, function(v) return v > 10 end)
|
local value3 = tbl.find(simple_array, function(v) return v > 10 end)
|
||||||
assert_equal(nil, value3)
|
assert_equal(nil, value3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Find Index", function()
|
test("Table Find Index", function()
|
||||||
local idx = table.find_index(simple_array, function(v) return v > 3 end)
|
local idx = tbl.find_index(simple_array, function(v) return v > 3 end)
|
||||||
assert_equal(4, idx)
|
assert_equal(4, idx)
|
||||||
|
|
||||||
local idx2 = table.find_index(simple_table, function(v, k) return k == "c" end)
|
local idx2 = tbl.find_index(simple_table, function(v, k) return k == "c" end)
|
||||||
assert_equal("c", idx2)
|
assert_equal("c", idx2)
|
||||||
|
|
||||||
local idx3 = table.find_index(simple_array, function(v) return v > 10 end)
|
local idx3 = tbl.find_index(simple_array, function(v) return v > 10 end)
|
||||||
assert_equal(nil, idx3)
|
assert_equal(nil, idx3)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Count", function()
|
test("Table Count", function()
|
||||||
local arr = {1, 2, 3, 2, 4, 2}
|
local arr = {1, 2, 3, 2, 4, 2}
|
||||||
assert_equal(3, table.count(arr, 2))
|
assert_equal(3, tbl.count(arr, 2))
|
||||||
assert_equal(0, table.count(arr, 5))
|
assert_equal(0, tbl.count(arr, 5))
|
||||||
|
|
||||||
assert_equal(2, table.count(arr, function(v) return v > 2 end))
|
assert_equal(2, tbl.count(arr, function(v) return v > 2 end))
|
||||||
assert_equal(2, table.count(arr, function(v) return v == 1 or v == 4 end))
|
assert_equal(2, tbl.count(arr, function(v) return v == 1 or v == 4 end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -174,39 +175,39 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Filter", function()
|
test("Table Filter", function()
|
||||||
local evens = table.filter(simple_array, function(v) return v % 2 == 0 end)
|
local evens = tbl.filter(simple_array, function(v) return v % 2 == 0 end)
|
||||||
assert_table_equal({2, 4}, evens)
|
assert_table_equal({2, 4}, evens)
|
||||||
|
|
||||||
local filtered_table = table.filter(simple_table, function(v) return v > 1 end)
|
local filtered_table = tbl.filter(simple_table, function(v) return v > 1 end)
|
||||||
assert_equal(2, table.size(filtered_table))
|
assert_equal(2, tbl.size(filtered_table))
|
||||||
assert_equal(2, filtered_table.b)
|
assert_equal(2, filtered_table.b)
|
||||||
assert_equal(3, filtered_table.c)
|
assert_equal(3, filtered_table.c)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Reject", function()
|
test("Table Reject", function()
|
||||||
local odds = table.reject(simple_array, function(v) return v % 2 == 0 end)
|
local odds = tbl.reject(simple_array, function(v) return v % 2 == 0 end)
|
||||||
assert_table_equal({1, 3, 5}, odds)
|
assert_table_equal({1, 3, 5}, odds)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Map", function()
|
test("Table Map", function()
|
||||||
local doubled = table.map(simple_array, function(v) return v * 2 end)
|
local doubled = tbl.map(simple_array, function(v) return v * 2 end)
|
||||||
assert_table_equal({2, 4, 6, 8, 10}, doubled)
|
assert_table_equal({2, 4, 6, 8, 10}, doubled)
|
||||||
|
|
||||||
local mapped_table = table.map(simple_table, function(v) return v + 10 end)
|
local mapped_table = tbl.map(simple_table, function(v) return v + 10 end)
|
||||||
assert_equal(11, mapped_table.a)
|
assert_equal(11, mapped_table.a)
|
||||||
assert_equal(12, mapped_table.b)
|
assert_equal(12, mapped_table.b)
|
||||||
assert_equal(13, mapped_table.c)
|
assert_equal(13, mapped_table.c)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Map Values", function()
|
test("Table Map Values", function()
|
||||||
local incremented = table.map_values(simple_table, function(v) return v + 1 end)
|
local incremented = tbl.map_values(simple_table, function(v) return v + 1 end)
|
||||||
assert_equal(2, incremented.a)
|
assert_equal(2, incremented.a)
|
||||||
assert_equal(3, incremented.b)
|
assert_equal(3, incremented.b)
|
||||||
assert_equal(4, incremented.c)
|
assert_equal(4, incremented.c)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Map Keys", function()
|
test("Table Map Keys", function()
|
||||||
local prefixed = table.map_keys(simple_table, function(k) return "key_" .. k end)
|
local prefixed = tbl.map_keys(simple_table, function(k) return "key_" .. k end)
|
||||||
assert_equal(1, prefixed.key_a)
|
assert_equal(1, prefixed.key_a)
|
||||||
assert_equal(2, prefixed.key_b)
|
assert_equal(2, prefixed.key_b)
|
||||||
assert_equal(3, prefixed.key_c)
|
assert_equal(3, prefixed.key_c)
|
||||||
@ -218,34 +219,34 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Reduce", function()
|
test("Table Reduce", function()
|
||||||
local sum = table.reduce(simple_array, function(acc, v) return acc + v end)
|
local sum = tbl.reduce(simple_array, function(acc, v) return acc + v end)
|
||||||
assert_equal(15, sum)
|
assert_equal(15, sum)
|
||||||
|
|
||||||
local sum_with_initial = table.reduce(simple_array, function(acc, v) return acc + v end, 10)
|
local sum_with_initial = tbl.reduce(simple_array, function(acc, v) return acc + v end, 10)
|
||||||
assert_equal(25, sum_with_initial)
|
assert_equal(25, sum_with_initial)
|
||||||
|
|
||||||
local product = table.reduce({2, 3, 4}, function(acc, v) return acc * v end)
|
local product = tbl.reduce({2, 3, 4}, function(acc, v) return acc * v end)
|
||||||
assert_equal(24, product)
|
assert_equal(24, product)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Fold", function()
|
test("Table Fold", function()
|
||||||
local sum = table.fold(simple_array, function(acc, v) return acc + v end, 0)
|
local sum = tbl.fold(simple_array, function(acc, v) return acc + v end, 0)
|
||||||
assert_equal(15, sum)
|
assert_equal(15, sum)
|
||||||
|
|
||||||
local concatenated = table.fold({"a", "b", "c"}, function(acc, v) return acc .. v end, "")
|
local concatenated = tbl.fold({"a", "b", "c"}, function(acc, v) return acc .. v end, "")
|
||||||
assert_equal("abc", concatenated)
|
assert_equal("abc", concatenated)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Math Operations", function()
|
test("Table Math Operations", function()
|
||||||
assert_equal(15, table.sum(simple_array))
|
assert_equal(15, tbl.sum(simple_array))
|
||||||
assert_equal(120, table.product(simple_array))
|
assert_equal(120, tbl.product(simple_array))
|
||||||
assert_equal(1, table.min(simple_array))
|
assert_equal(1, tbl.min(simple_array))
|
||||||
assert_equal(5, table.max(simple_array))
|
assert_equal(5, tbl.max(simple_array))
|
||||||
assert_equal(3, table.average(simple_array))
|
assert_equal(3, tbl.average(simple_array))
|
||||||
|
|
||||||
local floats = {1.5, 2.5, 3.0}
|
local floats = {1.5, 2.5, 3.0}
|
||||||
assert_close(7.0, table.sum(floats))
|
assert_close(7.0, tbl.sum(floats))
|
||||||
assert_close(2.33333, table.average(floats), 0.001)
|
assert_close(2.33333, tbl.average(floats), 0.001)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -253,30 +254,30 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table All", function()
|
test("Table All", function()
|
||||||
assert_equal(true, table.all({true, true, true}))
|
assert_equal(true, tbl.all({true, true, true}))
|
||||||
assert_equal(false, table.all({true, false, true}))
|
assert_equal(false, tbl.all({true, false, true}))
|
||||||
assert_equal(true, table.all({}))
|
assert_equal(true, tbl.all({}))
|
||||||
|
|
||||||
assert_equal(true, table.all(simple_array, function(v) return v > 0 end))
|
assert_equal(true, tbl.all(simple_array, function(v) return v > 0 end))
|
||||||
assert_equal(false, table.all(simple_array, function(v) return v > 3 end))
|
assert_equal(false, tbl.all(simple_array, function(v) return v > 3 end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Any", function()
|
test("Table Any", function()
|
||||||
assert_equal(true, table.any({false, true, false}))
|
assert_equal(true, tbl.any({false, true, false}))
|
||||||
assert_equal(false, table.any({false, false, false}))
|
assert_equal(false, tbl.any({false, false, false}))
|
||||||
assert_equal(false, table.any({}))
|
assert_equal(false, tbl.any({}))
|
||||||
|
|
||||||
assert_equal(true, table.any(simple_array, function(v) return v > 3 end))
|
assert_equal(true, tbl.any(simple_array, function(v) return v > 3 end))
|
||||||
assert_equal(false, table.any(simple_array, function(v) return v > 10 end))
|
assert_equal(false, tbl.any(simple_array, function(v) return v > 10 end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table None", function()
|
test("Table None", function()
|
||||||
assert_equal(true, table.none({false, false, false}))
|
assert_equal(true, tbl.none({false, false, false}))
|
||||||
assert_equal(false, table.none({false, true, false}))
|
assert_equal(false, tbl.none({false, true, false}))
|
||||||
assert_equal(true, table.none({}))
|
assert_equal(true, tbl.none({}))
|
||||||
|
|
||||||
assert_equal(false, table.none(simple_array, function(v) return v > 3 end))
|
assert_equal(false, tbl.none(simple_array, function(v) return v > 3 end))
|
||||||
assert_equal(true, table.none(simple_array, function(v) return v > 10 end))
|
assert_equal(true, tbl.none(simple_array, function(v) return v > 10 end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -285,36 +286,36 @@ end)
|
|||||||
|
|
||||||
test("Table Unique", function()
|
test("Table Unique", function()
|
||||||
local duplicates = {1, 2, 2, 3, 3, 3, 4}
|
local duplicates = {1, 2, 2, 3, 3, 3, 4}
|
||||||
local unique = table.unique(duplicates)
|
local unique = tbl.unique(duplicates)
|
||||||
assert_table_equal({1, 2, 3, 4}, unique)
|
assert_table_equal({1, 2, 3, 4}, unique)
|
||||||
|
|
||||||
local empty_unique = table.unique({})
|
local empty_unique = tbl.unique({})
|
||||||
assert_table_equal({}, empty_unique)
|
assert_table_equal({}, empty_unique)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Intersection", function()
|
test("Table Intersection", function()
|
||||||
local arr1 = {1, 2, 3, 4}
|
local arr1 = {1, 2, 3, 4}
|
||||||
local arr2 = {3, 4, 5, 6}
|
local arr2 = {3, 4, 5, 6}
|
||||||
local intersect = table.intersection(arr1, arr2)
|
local intersect = tbl.intersection(arr1, arr2)
|
||||||
assert_equal(2, #intersect)
|
assert_equal(2, #intersect)
|
||||||
assert_equal(true, table.contains(intersect, 3))
|
assert_equal(true, tbl.contains(intersect, 3))
|
||||||
assert_equal(true, table.contains(intersect, 4))
|
assert_equal(true, tbl.contains(intersect, 4))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Union", function()
|
test("Table Union", function()
|
||||||
local arr1 = {1, 2, 3}
|
local arr1 = {1, 2, 3}
|
||||||
local arr2 = {3, 4, 5}
|
local arr2 = {3, 4, 5}
|
||||||
local union = table.union(arr1, arr2)
|
local union = tbl.union(arr1, arr2)
|
||||||
assert_equal(5, #union)
|
assert_equal(5, #union)
|
||||||
for i = 1, 5 do
|
for i = 1, 5 do
|
||||||
assert_equal(true, table.contains(union, i))
|
assert_equal(true, tbl.contains(union, i))
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Difference", function()
|
test("Table Difference", function()
|
||||||
local arr1 = {1, 2, 3, 4, 5}
|
local arr1 = {1, 2, 3, 4, 5}
|
||||||
local arr2 = {3, 4}
|
local arr2 = {3, 4}
|
||||||
local diff = table.difference(arr1, arr2)
|
local diff = tbl.difference(arr1, arr2)
|
||||||
assert_table_equal({1, 2, 5}, diff)
|
assert_table_equal({1, 2, 5}, diff)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -323,23 +324,23 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Reverse", function()
|
test("Table Reverse", function()
|
||||||
local reversed = table.reverse(simple_array)
|
local reversed = tbl.reverse(simple_array)
|
||||||
assert_table_equal({5, 4, 3, 2, 1}, reversed)
|
assert_table_equal({5, 4, 3, 2, 1}, reversed)
|
||||||
|
|
||||||
local single = table.reverse({42})
|
local single = tbl.reverse({42})
|
||||||
assert_table_equal({42}, single)
|
assert_table_equal({42}, single)
|
||||||
|
|
||||||
local empty = table.reverse({})
|
local empty = tbl.reverse({})
|
||||||
assert_table_equal({}, empty)
|
assert_table_equal({}, empty)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Shuffle", function()
|
test("Table Shuffle", function()
|
||||||
local shuffled = table.shuffle(simple_array)
|
local shuffled = tbl.shuffle(simple_array)
|
||||||
assert_equal(5, #shuffled)
|
assert_equal(5, #shuffled)
|
||||||
|
|
||||||
-- All original elements should still be present
|
-- All original elements should still be present
|
||||||
for _, v in ipairs(simple_array) do
|
for _, v in ipairs(simple_array) do
|
||||||
assert_equal(true, table.contains(shuffled, v))
|
assert_equal(true, tbl.contains(shuffled, v))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Should be same length
|
-- Should be same length
|
||||||
@ -349,30 +350,30 @@ end)
|
|||||||
test("Table Rotate", function()
|
test("Table Rotate", function()
|
||||||
local arr = {1, 2, 3, 4, 5}
|
local arr = {1, 2, 3, 4, 5}
|
||||||
|
|
||||||
local rotated_right = table.rotate(arr, 2)
|
local rotated_right = tbl.rotate(arr, 2)
|
||||||
assert_table_equal({4, 5, 1, 2, 3}, rotated_right)
|
assert_table_equal({4, 5, 1, 2, 3}, rotated_right)
|
||||||
|
|
||||||
local rotated_left = table.rotate(arr, -2)
|
local rotated_left = tbl.rotate(arr, -2)
|
||||||
assert_table_equal({3, 4, 5, 1, 2}, rotated_left)
|
assert_table_equal({3, 4, 5, 1, 2}, rotated_left)
|
||||||
|
|
||||||
local no_rotation = table.rotate(arr, 0)
|
local no_rotation = tbl.rotate(arr, 0)
|
||||||
assert_table_equal(arr, no_rotation)
|
assert_table_equal(arr, no_rotation)
|
||||||
|
|
||||||
local full_rotation = table.rotate(arr, 5)
|
local full_rotation = tbl.rotate(arr, 5)
|
||||||
assert_table_equal(arr, full_rotation)
|
assert_table_equal(arr, full_rotation)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Slice", function()
|
test("Table Slice", function()
|
||||||
local sliced = table.slice(simple_array, 2, 4)
|
local sliced = tbl.slice(simple_array, 2, 4)
|
||||||
assert_table_equal({2, 3, 4}, sliced)
|
assert_table_equal({2, 3, 4}, sliced)
|
||||||
|
|
||||||
local from_start = table.slice(simple_array, 1, 3)
|
local from_start = tbl.slice(simple_array, 1, 3)
|
||||||
assert_table_equal({1, 2, 3}, from_start)
|
assert_table_equal({1, 2, 3}, from_start)
|
||||||
|
|
||||||
local to_end = table.slice(simple_array, 3)
|
local to_end = tbl.slice(simple_array, 3)
|
||||||
assert_table_equal({3, 4, 5}, to_end)
|
assert_table_equal({3, 4, 5}, to_end)
|
||||||
|
|
||||||
local negative_indices = table.slice(simple_array, -3, -1)
|
local negative_indices = tbl.slice(simple_array, -3, -1)
|
||||||
assert_table_equal({3, 4, 5}, negative_indices)
|
assert_table_equal({3, 4, 5}, negative_indices)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -380,19 +381,19 @@ test("Table Splice", function()
|
|||||||
local arr = {1, 2, 3, 4, 5}
|
local arr = {1, 2, 3, 4, 5}
|
||||||
|
|
||||||
-- Remove elements
|
-- Remove elements
|
||||||
local removed = table.splice(arr, 2, 2)
|
local removed = tbl.splice(arr, 2, 2)
|
||||||
assert_table_equal({2, 3}, removed)
|
assert_table_equal({2, 3}, removed)
|
||||||
assert_table_equal({1, 4, 5}, arr)
|
assert_table_equal({1, 4, 5}, arr)
|
||||||
|
|
||||||
-- Insert elements
|
-- Insert elements
|
||||||
arr = {1, 2, 3, 4, 5}
|
arr = {1, 2, 3, 4, 5}
|
||||||
removed = table.splice(arr, 3, 0, "a", "b")
|
removed = tbl.splice(arr, 3, 0, "a", "b")
|
||||||
assert_table_equal({}, removed)
|
assert_table_equal({}, removed)
|
||||||
assert_table_equal({1, 2, "a", "b", 3, 4, 5}, arr)
|
assert_table_equal({1, 2, "a", "b", 3, 4, 5}, arr)
|
||||||
|
|
||||||
-- Replace elements
|
-- Replace elements
|
||||||
arr = {1, 2, 3, 4, 5}
|
arr = {1, 2, 3, 4, 5}
|
||||||
removed = table.splice(arr, 2, 2, "x", "y", "z")
|
removed = tbl.splice(arr, 2, 2, "x", "y", "z")
|
||||||
assert_table_equal({2, 3}, removed)
|
assert_table_equal({2, 3}, removed)
|
||||||
assert_table_equal({1, "x", "y", "z", 4, 5}, arr)
|
assert_table_equal({1, "x", "y", "z", 4, 5}, arr)
|
||||||
end)
|
end)
|
||||||
@ -408,23 +409,23 @@ test("Table Sort By", function()
|
|||||||
{name = "Charlie", age = 35}
|
{name = "Charlie", age = 35}
|
||||||
}
|
}
|
||||||
|
|
||||||
local sorted_by_age = table.sort_by(people, function(p) return p.age end)
|
local sorted_by_age = tbl.sort_by(people, function(p) return p.age end)
|
||||||
assert_equal("Bob", sorted_by_age[1].name)
|
assert_equal("Bob", sorted_by_age[1].name)
|
||||||
assert_equal("Charlie", sorted_by_age[3].name)
|
assert_equal("Charlie", sorted_by_age[3].name)
|
||||||
|
|
||||||
local sorted_by_name = table.sort_by(people, function(p) return p.name end)
|
local sorted_by_name = tbl.sort_by(people, function(p) return p.name end)
|
||||||
assert_equal("Alice", sorted_by_name[1].name)
|
assert_equal("Alice", sorted_by_name[1].name)
|
||||||
assert_equal("Charlie", sorted_by_name[3].name)
|
assert_equal("Charlie", sorted_by_name[3].name)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Is Sorted", function()
|
test("Table Is Sorted", function()
|
||||||
assert_equal(true, table.is_sorted({1, 2, 3, 4, 5}))
|
assert_equal(true, tbl.is_sorted({1, 2, 3, 4, 5}))
|
||||||
assert_equal(false, table.is_sorted({1, 3, 2, 4, 5}))
|
assert_equal(false, tbl.is_sorted({1, 3, 2, 4, 5}))
|
||||||
assert_equal(true, table.is_sorted({}))
|
assert_equal(true, tbl.is_sorted({}))
|
||||||
assert_equal(true, table.is_sorted({42}))
|
assert_equal(true, tbl.is_sorted({42}))
|
||||||
|
|
||||||
assert_equal(true, table.is_sorted({5, 4, 3, 2, 1}, function(a, b) return a > b end))
|
assert_equal(true, tbl.is_sorted({5, 4, 3, 2, 1}, function(a, b) return a > b end))
|
||||||
assert_equal(false, table.is_sorted({1, 2, 3, 4, 5}, function(a, b) return a > b end))
|
assert_equal(false, tbl.is_sorted({1, 2, 3, 4, 5}, function(a, b) return a > b end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -432,21 +433,21 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Keys and Values", function()
|
test("Table Keys and Values", function()
|
||||||
local keys = table.keys(simple_table)
|
local keys = tbl.keys(simple_table)
|
||||||
assert_equal(3, #keys)
|
assert_equal(3, #keys)
|
||||||
assert_equal(true, table.contains(keys, "a"))
|
assert_equal(true, tbl.contains(keys, "a"))
|
||||||
assert_equal(true, table.contains(keys, "b"))
|
assert_equal(true, tbl.contains(keys, "b"))
|
||||||
assert_equal(true, table.contains(keys, "c"))
|
assert_equal(true, tbl.contains(keys, "c"))
|
||||||
|
|
||||||
local values = table.values(simple_table)
|
local values = tbl.values(simple_table)
|
||||||
assert_equal(3, #values)
|
assert_equal(3, #values)
|
||||||
assert_equal(true, table.contains(values, 1))
|
assert_equal(true, tbl.contains(values, 1))
|
||||||
assert_equal(true, table.contains(values, 2))
|
assert_equal(true, tbl.contains(values, 2))
|
||||||
assert_equal(true, table.contains(values, 3))
|
assert_equal(true, tbl.contains(values, 3))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Pairs", function()
|
test("Table Pairs", function()
|
||||||
local pairs_list = table.pairs({a = 1, b = 2})
|
local pairs_list = tbl.pairs({a = 1, b = 2})
|
||||||
assert_equal(2, #pairs_list)
|
assert_equal(2, #pairs_list)
|
||||||
|
|
||||||
-- Should contain key-value pairs
|
-- Should contain key-value pairs
|
||||||
@ -464,8 +465,8 @@ test("Table Merge", function()
|
|||||||
local t2 = {c = 3, d = 4}
|
local t2 = {c = 3, d = 4}
|
||||||
local t3 = {b = 20, e = 5}
|
local t3 = {b = 20, e = 5}
|
||||||
|
|
||||||
local merged = table.merge(t1, t2, t3)
|
local merged = tbl.merge(t1, t2, t3)
|
||||||
assert_equal(5, table.size(merged))
|
assert_equal(5, tbl.size(merged))
|
||||||
assert_equal(1, merged.a)
|
assert_equal(1, merged.a)
|
||||||
assert_equal(20, merged.b) -- Last one wins
|
assert_equal(20, merged.b) -- Last one wins
|
||||||
assert_equal(3, merged.c)
|
assert_equal(3, merged.c)
|
||||||
@ -477,15 +478,15 @@ test("Table Extend", function()
|
|||||||
local t1 = {a = 1, b = 2}
|
local t1 = {a = 1, b = 2}
|
||||||
local t2 = {c = 3, d = 4}
|
local t2 = {c = 3, d = 4}
|
||||||
|
|
||||||
local extended = table.extend(t1, t2)
|
local extended = tbl.extend(t1, t2)
|
||||||
assert_equal(t1, extended) -- Should return t1
|
assert_equal(t1, extended) -- Should return t1
|
||||||
assert_equal(4, table.size(t1))
|
assert_equal(4, tbl.size(t1))
|
||||||
assert_equal(3, t1.c)
|
assert_equal(3, t1.c)
|
||||||
assert_equal(4, t1.d)
|
assert_equal(4, t1.d)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Invert", function()
|
test("Table Invert", function()
|
||||||
local inverted = table.invert(simple_table)
|
local inverted = tbl.invert(simple_table)
|
||||||
assert_equal("a", inverted[1])
|
assert_equal("a", inverted[1])
|
||||||
assert_equal("b", inverted[2])
|
assert_equal("b", inverted[2])
|
||||||
assert_equal("c", inverted[3])
|
assert_equal("c", inverted[3])
|
||||||
@ -494,16 +495,16 @@ end)
|
|||||||
test("Table Pick and Omit", function()
|
test("Table Pick and Omit", function()
|
||||||
local big_table = {a = 1, b = 2, c = 3, d = 4, e = 5}
|
local big_table = {a = 1, b = 2, c = 3, d = 4, e = 5}
|
||||||
|
|
||||||
local picked = table.pick(big_table, "a", "c", "e")
|
local picked = tbl.pick(big_table, "a", "c", "e")
|
||||||
assert_equal(3, table.size(picked))
|
assert_equal(3, tbl.size(picked))
|
||||||
assert_equal(1, picked.a)
|
assert_equal(1, picked.a)
|
||||||
assert_equal(3, picked.c)
|
assert_equal(3, picked.c)
|
||||||
assert_equal(5, picked.e)
|
assert_equal(5, picked.e)
|
||||||
assert_equal(nil, picked.b)
|
assert_equal(nil, picked.b)
|
||||||
assert_equal(nil, picked.d)
|
assert_equal(nil, picked.d)
|
||||||
|
|
||||||
local omitted = table.omit(big_table, "b", "d")
|
local omitted = tbl.omit(big_table, "b", "d")
|
||||||
assert_equal(3, table.size(omitted))
|
assert_equal(3, tbl.size(omitted))
|
||||||
assert_equal(1, omitted.a)
|
assert_equal(1, omitted.a)
|
||||||
assert_equal(3, omitted.c)
|
assert_equal(3, omitted.c)
|
||||||
assert_equal(5, omitted.e)
|
assert_equal(5, omitted.e)
|
||||||
@ -520,21 +521,21 @@ test("Table Deep Equals", function()
|
|||||||
local t2 = {a = {x = 1, y = 2}, b = {1, 2, 3}}
|
local t2 = {a = {x = 1, y = 2}, b = {1, 2, 3}}
|
||||||
local t3 = {a = {x = 1, y = 3}, b = {1, 2, 3}}
|
local t3 = {a = {x = 1, y = 3}, b = {1, 2, 3}}
|
||||||
|
|
||||||
assert_equal(true, table.deep_equals(t1, t2))
|
assert_equal(true, tbl.deep_equals(t1, t2))
|
||||||
assert_equal(false, table.deep_equals(t1, t3))
|
assert_equal(false, tbl.deep_equals(t1, t3))
|
||||||
assert_equal(true, table.deep_equals({}, {}))
|
assert_equal(true, tbl.deep_equals({}, {}))
|
||||||
assert_equal(false, table.deep_equals({a = 1}, {a = 1, b = 2}))
|
assert_equal(false, tbl.deep_equals({a = 1}, {a = 1, b = 2}))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Flatten", function()
|
test("Table Flatten", function()
|
||||||
local nested = {{1, 2}, {3, 4}, {5, {6, 7}}}
|
local nested = {{1, 2}, {3, 4}, {5, {6, 7}}}
|
||||||
local flattened = table.flatten(nested)
|
local flattened = tbl.flatten(nested)
|
||||||
assert_table_equal({1, 2, 3, 4, 5, {6, 7}}, flattened)
|
assert_table_equal({1, 2, 3, 4, 5, {6, 7}}, flattened)
|
||||||
|
|
||||||
local deep_flattened = table.flatten(nested, 2)
|
local deep_flattened = tbl.flatten(nested, 2)
|
||||||
assert_table_equal({1, 2, 3, 4, 5, 6, 7}, deep_flattened)
|
assert_table_equal({1, 2, 3, 4, 5, 6, 7}, deep_flattened)
|
||||||
|
|
||||||
local already_flat = table.flatten({1, 2, 3})
|
local already_flat = tbl.flatten({1, 2, 3})
|
||||||
assert_table_equal({1, 2, 3}, already_flat)
|
assert_table_equal({1, 2, 3}, already_flat)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -542,7 +543,7 @@ test("Table Deep Merge", function()
|
|||||||
local t1 = {a = {x = 1}, b = 2}
|
local t1 = {a = {x = 1}, b = 2}
|
||||||
local t2 = {a = {y = 3}, c = 4}
|
local t2 = {a = {y = 3}, c = 4}
|
||||||
|
|
||||||
local merged = table.deep_merge(t1, t2)
|
local merged = tbl.deep_merge(t1, t2)
|
||||||
assert_equal(1, merged.a.x)
|
assert_equal(1, merged.a.x)
|
||||||
assert_equal(3, merged.a.y)
|
assert_equal(3, merged.a.y)
|
||||||
assert_equal(2, merged.b)
|
assert_equal(2, merged.b)
|
||||||
@ -558,24 +559,24 @@ end)
|
|||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
|
|
||||||
test("Table Chunk", function()
|
test("Table Chunk", function()
|
||||||
local chunks = table.chunk({1, 2, 3, 4, 5, 6, 7}, 3)
|
local chunks = tbl.chunk({1, 2, 3, 4, 5, 6, 7}, 3)
|
||||||
assert_equal(3, #chunks)
|
assert_equal(3, #chunks)
|
||||||
assert_table_equal({1, 2, 3}, chunks[1])
|
assert_table_equal({1, 2, 3}, chunks[1])
|
||||||
assert_table_equal({4, 5, 6}, chunks[2])
|
assert_table_equal({4, 5, 6}, chunks[2])
|
||||||
assert_table_equal({7}, chunks[3])
|
assert_table_equal({7}, chunks[3])
|
||||||
|
|
||||||
local exact_chunks = table.chunk({1, 2, 3, 4}, 2)
|
local exact_chunks = tbl.chunk({1, 2, 3, 4}, 2)
|
||||||
assert_equal(2, #exact_chunks)
|
assert_equal(2, #exact_chunks)
|
||||||
assert_table_equal({1, 2}, exact_chunks[1])
|
assert_table_equal({1, 2}, exact_chunks[1])
|
||||||
assert_table_equal({3, 4}, exact_chunks[2])
|
assert_table_equal({3, 4}, exact_chunks[2])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Partition", function()
|
test("Table Partition", function()
|
||||||
local evens, odds = table.partition(simple_array, function(v) return v % 2 == 0 end)
|
local evens, odds = tbl.partition(simple_array, function(v) return v % 2 == 0 end)
|
||||||
assert_table_equal({2, 4}, evens)
|
assert_table_equal({2, 4}, evens)
|
||||||
assert_table_equal({1, 3, 5}, odds)
|
assert_table_equal({1, 3, 5}, odds)
|
||||||
|
|
||||||
local empty_true, all_false = table.partition({1, 3, 5}, function(v) return v % 2 == 0 end)
|
local empty_true, all_false = tbl.partition({1, 3, 5}, function(v) return v % 2 == 0 end)
|
||||||
assert_table_equal({}, empty_true)
|
assert_table_equal({}, empty_true)
|
||||||
assert_table_equal({1, 3, 5}, all_false)
|
assert_table_equal({1, 3, 5}, all_false)
|
||||||
end)
|
end)
|
||||||
@ -588,8 +589,8 @@ test("Table Group By", function()
|
|||||||
{name = "David", department = "sales"}
|
{name = "David", department = "sales"}
|
||||||
}
|
}
|
||||||
|
|
||||||
local by_dept = table.group_by(people, function(person) return person.department end)
|
local by_dept = tbl.group_by(people, function(person) return person.department end)
|
||||||
assert_equal(2, table.size(by_dept))
|
assert_equal(2, tbl.size(by_dept))
|
||||||
assert_equal(2, #by_dept.engineering)
|
assert_equal(2, #by_dept.engineering)
|
||||||
assert_equal(2, #by_dept.sales)
|
assert_equal(2, #by_dept.sales)
|
||||||
assert_equal("Alice", by_dept.engineering[1].name)
|
assert_equal("Alice", by_dept.engineering[1].name)
|
||||||
@ -601,14 +602,14 @@ test("Table Zip", function()
|
|||||||
local ages = {25, 30, 35}
|
local ages = {25, 30, 35}
|
||||||
local cities = {"NYC", "LA", "Chicago"}
|
local cities = {"NYC", "LA", "Chicago"}
|
||||||
|
|
||||||
local zipped = table.zip(names, ages, cities)
|
local zipped = tbl.zip(names, ages, cities)
|
||||||
assert_equal(3, #zipped)
|
assert_equal(3, #zipped)
|
||||||
assert_table_equal({"Alice", 25, "NYC"}, zipped[1])
|
assert_table_equal({"Alice", 25, "NYC"}, zipped[1])
|
||||||
assert_table_equal({"Bob", 30, "LA"}, zipped[2])
|
assert_table_equal({"Bob", 30, "LA"}, zipped[2])
|
||||||
assert_table_equal({"Charlie", 35, "Chicago"}, zipped[3])
|
assert_table_equal({"Charlie", 35, "Chicago"}, zipped[3])
|
||||||
|
|
||||||
-- Different lengths
|
-- Different lengths
|
||||||
local short_zip = table.zip({1, 2, 3}, {"a", "b"})
|
local short_zip = tbl.zip({1, 2, 3}, {"a", "b"})
|
||||||
assert_equal(2, #short_zip)
|
assert_equal(2, #short_zip)
|
||||||
assert_table_equal({1, "a"}, short_zip[1])
|
assert_table_equal({1, "a"}, short_zip[1])
|
||||||
assert_table_equal({2, "b"}, short_zip[2])
|
assert_table_equal({2, "b"}, short_zip[2])
|
||||||
@ -616,28 +617,28 @@ end)
|
|||||||
|
|
||||||
test("Table Compact", function()
|
test("Table Compact", function()
|
||||||
local messy = {1, nil, false, 2, nil, 3, false}
|
local messy = {1, nil, false, 2, nil, 3, false}
|
||||||
local compacted = table.compact(messy)
|
local compacted = tbl.compact(messy)
|
||||||
assert_table_equal({1, 2, 3}, compacted)
|
assert_table_equal({1, 2, 3}, compacted)
|
||||||
|
|
||||||
local clean = {1, 2, 3}
|
local clean = {1, 2, 3}
|
||||||
local unchanged = table.compact(clean)
|
local unchanged = tbl.compact(clean)
|
||||||
assert_table_equal(clean, unchanged)
|
assert_table_equal(clean, unchanged)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Table Sample", function()
|
test("Table Sample", function()
|
||||||
local sample1 = table.sample(simple_array, 3)
|
local sample1 = tbl.sample(simple_array, 3)
|
||||||
assert_equal(3, #sample1)
|
assert_equal(3, #sample1)
|
||||||
|
|
||||||
-- All sampled elements should be from original
|
-- All sampled elements should be from original
|
||||||
for _, v in ipairs(sample1) do
|
for _, v in ipairs(sample1) do
|
||||||
assert_equal(true, table.contains(simple_array, v))
|
assert_equal(true, tbl.contains(simple_array, v))
|
||||||
end
|
end
|
||||||
|
|
||||||
local single_sample = table.sample(simple_array)
|
local single_sample = tbl.sample(simple_array)
|
||||||
assert_equal(1, #single_sample)
|
assert_equal(1, #single_sample)
|
||||||
assert_equal(true, table.contains(simple_array, single_sample[1]))
|
assert_equal(true, tbl.contains(simple_array, single_sample[1]))
|
||||||
|
|
||||||
local oversample = table.sample({1, 2}, 5)
|
local oversample = tbl.sample({1, 2}, 5)
|
||||||
assert_equal(2, #oversample)
|
assert_equal(2, #oversample)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -648,37 +649,37 @@ end)
|
|||||||
test("Empty Table Handling", function()
|
test("Empty Table Handling", function()
|
||||||
local empty = {}
|
local empty = {}
|
||||||
|
|
||||||
assert_equal(true, table.is_empty(empty))
|
assert_equal(true, tbl.is_empty(empty))
|
||||||
assert_equal(0, table.length(empty))
|
assert_equal(0, tbl.length(empty))
|
||||||
assert_equal(0, table.size(empty))
|
assert_equal(0, tbl.size(empty))
|
||||||
assert_equal(true, table.is_array(empty))
|
assert_equal(true, tbl.is_array(empty))
|
||||||
|
|
||||||
assert_table_equal({}, table.filter(empty, function() return true end))
|
assert_table_equal({}, tbl.filter(empty, function() return true end))
|
||||||
assert_table_equal({}, table.map(empty, function(v) return v * 2 end))
|
assert_table_equal({}, tbl.map(empty, function(v) return v * 2 end))
|
||||||
assert_table_equal({}, table.keys(empty))
|
assert_table_equal({}, tbl.keys(empty))
|
||||||
assert_table_equal({}, table.values(empty))
|
assert_table_equal({}, tbl.values(empty))
|
||||||
|
|
||||||
assert_equal(true, table.all(empty))
|
assert_equal(true, tbl.all(empty))
|
||||||
assert_equal(false, table.any(empty))
|
assert_equal(false, tbl.any(empty))
|
||||||
assert_equal(true, table.none(empty))
|
assert_equal(true, tbl.none(empty))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Single Element Tables", function()
|
test("Single Element Tables", function()
|
||||||
local single = {42}
|
local single = {42}
|
||||||
|
|
||||||
assert_equal(1, table.length(single))
|
assert_equal(1, tbl.length(single))
|
||||||
assert_equal(1, table.size(single))
|
assert_equal(1, tbl.size(single))
|
||||||
assert_equal(true, table.is_array(single))
|
assert_equal(true, tbl.is_array(single))
|
||||||
assert_equal(false, table.is_empty(single))
|
assert_equal(false, tbl.is_empty(single))
|
||||||
|
|
||||||
assert_equal(42, table.sum(single))
|
assert_equal(42, tbl.sum(single))
|
||||||
assert_equal(42, table.product(single))
|
assert_equal(42, tbl.product(single))
|
||||||
assert_equal(42, table.min(single))
|
assert_equal(42, tbl.min(single))
|
||||||
assert_equal(42, table.max(single))
|
assert_equal(42, tbl.max(single))
|
||||||
assert_equal(42, table.average(single))
|
assert_equal(42, tbl.average(single))
|
||||||
|
|
||||||
assert_table_equal({42}, table.reverse(single))
|
assert_table_equal({42}, tbl.reverse(single))
|
||||||
assert_table_equal({84}, table.map(single, function(v) return v * 2 end))
|
assert_table_equal({84}, tbl.map(single, function(v) return v * 2 end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
test("Circular Reference Handling", function()
|
test("Circular Reference Handling", function()
|
||||||
@ -688,7 +689,7 @@ test("Circular Reference Handling", function()
|
|||||||
t2.ref = t1
|
t2.ref = t1
|
||||||
|
|
||||||
-- Deep copy should handle circular references
|
-- Deep copy should handle circular references
|
||||||
local copied = table.deep_copy(t1)
|
local copied = tbl.deep_copy(t1)
|
||||||
assert_equal(1, copied.a)
|
assert_equal(1, copied.a)
|
||||||
assert_equal(2, copied.ref.b)
|
assert_equal(2, copied.ref.b)
|
||||||
assert_equal(copied, copied.ref.ref) -- Should maintain circular structure
|
assert_equal(copied, copied.ref.ref) -- Should maintain circular structure
|
||||||
@ -700,14 +701,14 @@ test("Large Table Performance", function()
|
|||||||
large[i] = i
|
large[i] = i
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_equal(10000, table.length(large))
|
assert_equal(10000, tbl.length(large))
|
||||||
assert_equal(true, table.is_array(large))
|
assert_equal(true, tbl.is_array(large))
|
||||||
assert_equal(50005000, table.sum(large)) -- Sum of 1 to 10000
|
assert_equal(50005000, tbl.sum(large)) -- Sum of 1 to 10000
|
||||||
|
|
||||||
local evens = table.filter(large, function(v) return v % 2 == 0 end)
|
local evens = tbl.filter(large, function(v) return v % 2 == 0 end)
|
||||||
assert_equal(5000, #evens)
|
assert_equal(5000, #evens)
|
||||||
|
|
||||||
local doubled = table.map(large, function(v) return v * 2 end)
|
local doubled = tbl.map(large, function(v) return v * 2 end)
|
||||||
assert_equal(10000, #doubled)
|
assert_equal(10000, #doubled)
|
||||||
assert_equal(2, doubled[1])
|
assert_equal(2, doubled[1])
|
||||||
assert_equal(20000, doubled[10000])
|
assert_equal(20000, doubled[10000])
|
||||||
@ -716,12 +717,12 @@ end)
|
|||||||
test("Mixed Type Table Handling", function()
|
test("Mixed Type Table Handling", function()
|
||||||
local mixed = {1, "hello", true, {a = 1}, function() end}
|
local mixed = {1, "hello", true, {a = 1}, function() end}
|
||||||
|
|
||||||
assert_equal(5, table.length(mixed))
|
assert_equal(5, tbl.length(mixed))
|
||||||
assert_equal(true, table.is_array(mixed))
|
assert_equal(true, tbl.is_array(mixed))
|
||||||
assert_equal(true, table.contains(mixed, "hello"))
|
assert_equal(true, tbl.contains(mixed, "hello"))
|
||||||
assert_equal(true, table.contains(mixed, true))
|
assert_equal(true, tbl.contains(mixed, true))
|
||||||
|
|
||||||
local strings_only = table.filter(mixed, function(v) return type(v) == "string" end)
|
local strings_only = tbl.filter(mixed, function(v) return type(v) == "string" end)
|
||||||
assert_equal(1, #strings_only)
|
assert_equal(1, #strings_only)
|
||||||
assert_equal("hello", strings_only[1])
|
assert_equal("hello", strings_only[1])
|
||||||
end)
|
end)
|
||||||
@ -737,23 +738,23 @@ test("Performance Test", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
local filtered = table.filter(large_array, function(v) return v > 500 end)
|
local filtered = tbl.filter(large_array, function(v) return v > 500 end)
|
||||||
local filter_time = os.clock() - start
|
local filter_time = os.clock() - start
|
||||||
|
|
||||||
start = os.clock()
|
start = os.clock()
|
||||||
local mapped = table.map(large_array, function(v) return v * 2 end)
|
local mapped = tbl.map(large_array, function(v) return v * 2 end)
|
||||||
local map_time = os.clock() - start
|
local map_time = os.clock() - start
|
||||||
|
|
||||||
start = os.clock()
|
start = os.clock()
|
||||||
local sum = table.sum(large_array)
|
local sum = tbl.sum(large_array)
|
||||||
local sum_time = os.clock() - start
|
local sum_time = os.clock() - start
|
||||||
|
|
||||||
start = os.clock()
|
start = os.clock()
|
||||||
local sorted = table.sort_by(large_array, function(v) return v end)
|
local sorted = tbl.sort_by(large_array, function(v) return v end)
|
||||||
local sort_time = os.clock() - start
|
local sort_time = os.clock() - start
|
||||||
|
|
||||||
start = os.clock()
|
start = os.clock()
|
||||||
local unique = table.unique(large_array)
|
local unique = tbl.unique(large_array)
|
||||||
local unique_time = os.clock() - start
|
local unique_time = os.clock() - start
|
||||||
|
|
||||||
print(string.format(" Filter %d elements: %.3fs", #filtered, filter_time))
|
print(string.format(" Filter %d elements: %.3fs", #filtered, filter_time))
|
||||||
@ -766,7 +767,7 @@ test("Performance Test", function()
|
|||||||
assert_equal(#large_array, #mapped)
|
assert_equal(#large_array, #mapped)
|
||||||
assert(sum > 0, "sum should be positive")
|
assert(sum > 0, "sum should be positive")
|
||||||
assert_equal(#large_array, #sorted)
|
assert_equal(#large_array, #sorted)
|
||||||
assert(table.is_sorted(sorted), "should be sorted")
|
assert(tbl.is_sorted(sorted), "should be sorted")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ======================================================================
|
-- ======================================================================
|
||||||
@ -783,28 +784,28 @@ test("Data Processing Pipeline", function()
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- Calculate total revenue per item
|
-- Calculate total revenue per item
|
||||||
local with_revenue = table.map(sales_data, function(item)
|
local with_revenue = tbl.map(sales_data, function(item)
|
||||||
local new_item = table.clone(item)
|
local new_item = tbl.clone(item)
|
||||||
new_item.revenue = item.price * item.quantity
|
new_item.revenue = item.price * item.quantity
|
||||||
return new_item
|
return new_item
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Filter high-value items (revenue >= 100)
|
-- Filter high-value items (revenue >= 100)
|
||||||
local high_value = table.filter(with_revenue, function(item)
|
local high_value = tbl.filter(with_revenue, function(item)
|
||||||
return item.revenue >= 100
|
return item.revenue >= 100
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Group by category
|
-- Group by category
|
||||||
local by_category = table.group_by(high_value, function(item)
|
local by_category = tbl.group_by(high_value, function(item)
|
||||||
return item.category
|
return item.category
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Calculate total revenue by category
|
-- Calculate total revenue by category
|
||||||
local category_totals = table.map_values(by_category, function(items)
|
local category_totals = tbl.map_values(by_category, function(items)
|
||||||
return table.sum(table.map(items, function(item) return item.revenue end))
|
return tbl.sum(tbl.map(items, function(item) return item.revenue end))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert_equal(2, table.size(category_totals))
|
assert_equal(2, tbl.size(category_totals))
|
||||||
assert_equal(4650, category_totals.electronics) -- laptop: 2000, mouse: 250, phone: 2400
|
assert_equal(4650, category_totals.electronics) -- laptop: 2000, mouse: 250, phone: 2400
|
||||||
assert_equal(100, category_totals.books) -- magazine: 100
|
assert_equal(100, category_totals.books) -- magazine: 100
|
||||||
end)
|
end)
|
||||||
@ -818,15 +819,15 @@ test("Complex Data Transformation", function()
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- Find Lua developers
|
-- Find Lua developers
|
||||||
local lua_devs = table.filter(users, function(user)
|
local lua_devs = tbl.filter(users, function(user)
|
||||||
return table.contains(user.skills, "lua")
|
return tbl.contains(user.skills, "lua")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Sort by age
|
-- Sort by age
|
||||||
local sorted_lua_devs = table.sort_by(lua_devs, function(user) return user.age end)
|
local sorted_lua_devs = tbl.sort_by(lua_devs, function(user) return user.age end)
|
||||||
|
|
||||||
-- Extract just names and ages
|
-- Extract just names and ages
|
||||||
local simplified = table.map(sorted_lua_devs, function(user)
|
local simplified = tbl.map(sorted_lua_devs, function(user)
|
||||||
return {name = user.name, age = user.age}
|
return {name = user.name, age = user.age}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -836,7 +837,7 @@ test("Complex Data Transformation", function()
|
|||||||
assert_equal("Bob", simplified[3].name) -- Oldest
|
assert_equal("Bob", simplified[3].name) -- Oldest
|
||||||
|
|
||||||
-- Group all users by age ranges
|
-- Group all users by age ranges
|
||||||
local age_groups = table.group_by(users, function(user)
|
local age_groups = tbl.group_by(users, function(user)
|
||||||
if user.age < 30 then return "young"
|
if user.age < 30 then return "young"
|
||||||
else return "experienced" end
|
else return "experienced" end
|
||||||
end)
|
end)
|
||||||
@ -854,26 +855,26 @@ test("Statistical Analysis", function()
|
|||||||
}
|
}
|
||||||
|
|
||||||
-- Calculate average score for each student
|
-- Calculate average score for each student
|
||||||
local with_averages = table.map(test_scores, function(student)
|
local with_averages = tbl.map(test_scores, function(student)
|
||||||
local avg = table.average(student.scores)
|
local avg = tbl.average(student.scores)
|
||||||
return {
|
return {
|
||||||
student = student.student,
|
student = student.student,
|
||||||
scores = student.scores,
|
scores = student.scores,
|
||||||
average = avg,
|
average = avg,
|
||||||
max_score = table.max(student.scores),
|
max_score = tbl.max(student.scores),
|
||||||
min_score = table.min(student.scores)
|
min_score = tbl.min(student.scores)
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Find top performer
|
-- Find top performer
|
||||||
local top_student = table.reduce(with_averages, function(best, current)
|
local top_student = tbl.reduce(with_averages, function(best, current)
|
||||||
return current.average > best.average and current or best
|
return current.average > best.average and current or best
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- Students above class average
|
-- Students above class average
|
||||||
local all_averages = table.map(with_averages, function(s) return s.average end)
|
local all_averages = tbl.map(with_averages, function(s) return s.average end)
|
||||||
local class_average = table.average(all_averages)
|
local class_average = tbl.average(all_averages)
|
||||||
local above_average = table.filter(with_averages, function(s)
|
local above_average = tbl.filter(with_averages, function(s)
|
||||||
return s.average > class_average
|
return s.average > class_average
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +1,65 @@
|
|||||||
|
-- Enhanced Test Framework - Global Functions
|
||||||
|
-- Provides better assert reporting and test runner functionality
|
||||||
|
|
||||||
|
-- Test state
|
||||||
local passed = 0
|
local passed = 0
|
||||||
local total = 0
|
local total = 0
|
||||||
|
|
||||||
|
-- Enhanced assert function with better error reporting
|
||||||
function assert(condition, message, level)
|
function assert(condition, message, level)
|
||||||
if condition then
|
if condition then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
level = level or 2
|
level = level or 2
|
||||||
local info = debug.getinfo(level, "Sl")
|
local info = debug.getinfo(level, "Sl")
|
||||||
local file = info.source
|
local file = info.source
|
||||||
|
|
||||||
-- Extract filename from source or use generic name
|
-- Extract filename from source or use generic name
|
||||||
if file:sub(1,1) == "@" then
|
if file:sub(1,1) == "@" then
|
||||||
file = file:sub(2) -- Remove @ prefix for files
|
file = file:sub(2) -- Remove @ prefix for files
|
||||||
else
|
else
|
||||||
file = "<script>" -- Generic name for inline scripts
|
file = "<script>" -- Generic name for inline scripts
|
||||||
end
|
end
|
||||||
|
|
||||||
local line = info.currentline or "unknown"
|
local line = info.currentline or "unknown"
|
||||||
local error_msg = message or "assertion failed"
|
local error_msg = message or "assertion failed"
|
||||||
local full_msg = string.format("%s:%s: %s", file, line, error_msg)
|
local full_msg = string.format("%s:%s: %s", file, line, error_msg)
|
||||||
|
|
||||||
error(full_msg, 0)
|
error(full_msg, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Assert with tolerance for floating point comparisons
|
||||||
|
function assert_close(expected, actual, tolerance, message)
|
||||||
|
tolerance = tolerance or 1e-10
|
||||||
|
local diff = math.abs(expected - actual)
|
||||||
|
if diff <= tolerance then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local msg = message or string.format("Expected %g, got %g (diff: %g, tolerance: %g)", expected, actual, diff, tolerance)
|
||||||
|
assert(false, msg, 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Assert equality with better error messages
|
||||||
function assert_equal(expected, actual, message)
|
function assert_equal(expected, actual, message)
|
||||||
if expected == actual then
|
if expected == actual then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local msg = message or string.format("Expected %s, got %s", tostring(expected), tostring(actual))
|
local msg = message or string.format("Expected %s, got %s", tostring(expected), tostring(actual))
|
||||||
assert(false, msg, 3)
|
assert(false, msg, 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Assert table equality (deep comparison)
|
||||||
function assert_table_equal(expected, actual, message, path)
|
function assert_table_equal(expected, actual, message, path)
|
||||||
path = path or "root"
|
path = path or "root"
|
||||||
|
|
||||||
if type(expected) ~= type(actual) then
|
if type(expected) ~= type(actual) then
|
||||||
local msg = message or string.format("Type mismatch at %s: expected %s, got %s", path, type(expected), type(actual))
|
local msg = message or string.format("Type mismatch at %s: expected %s, got %s", path, type(expected), type(actual))
|
||||||
assert(false, msg, 3)
|
assert(false, msg, 3)
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(expected) ~= "table" then
|
if type(expected) ~= "table" then
|
||||||
if expected ~= actual then
|
if expected ~= actual then
|
||||||
local msg = message or string.format("Value mismatch at %s: expected %s, got %s", path, tostring(expected), tostring(actual))
|
local msg = message or string.format("Value mismatch at %s: expected %s, got %s", path, tostring(expected), tostring(actual))
|
||||||
@ -48,7 +67,7 @@ function assert_table_equal(expected, actual, message, path)
|
|||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check all keys in a exist in b with same values
|
-- Check all keys in a exist in b with same values
|
||||||
for k, v in pairs(expected) do
|
for k, v in pairs(expected) do
|
||||||
local new_path = path .. "." .. tostring(k)
|
local new_path = path .. "." .. tostring(k)
|
||||||
@ -58,7 +77,7 @@ function assert_table_equal(expected, actual, message, path)
|
|||||||
end
|
end
|
||||||
assert_table_equal(v, actual[k], message, new_path)
|
assert_table_equal(v, actual[k], message, new_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check all keys in b exist in a
|
-- Check all keys in b exist in a
|
||||||
for k, v in pairs(actual) do
|
for k, v in pairs(actual) do
|
||||||
if expected[k] == nil then
|
if expected[k] == nil then
|
||||||
@ -67,29 +86,20 @@ function assert_table_equal(expected, actual, message, path)
|
|||||||
assert(false, msg, 3)
|
assert(false, msg, 3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function assert_close(expected, actual, tolerance, message)
|
-- Test runner function
|
||||||
tolerance = tolerance or 1e-10
|
|
||||||
local diff = math.abs(expected - actual)
|
|
||||||
if diff <= tolerance then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
local msg = message or string.format("Expected %g, got %g (diff: %g > %g)", expected, actual, diff, tolerance)
|
|
||||||
assert(false, msg, 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
function test(name, fn)
|
function test(name, fn)
|
||||||
print("Testing " .. name .. "...")
|
print("Testing " .. name .. "...")
|
||||||
total = total + 1
|
total = total + 1
|
||||||
|
|
||||||
local start_time = os.clock()
|
local start_time = os.clock()
|
||||||
local ok, err = pcall(fn)
|
local ok, err = pcall(fn)
|
||||||
local end_time = os.clock()
|
local end_time = os.clock()
|
||||||
local duration = end_time - start_time
|
local duration = end_time - start_time
|
||||||
|
|
||||||
if ok then
|
if ok then
|
||||||
passed = passed + 1
|
passed = passed + 1
|
||||||
print(string.format(" ✓ PASS (%.3fs)", duration))
|
print(string.format(" ✓ PASS (%.3fs)", duration))
|
||||||
@ -103,22 +113,25 @@ function test(name, fn)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Test suite runner
|
||||||
function run_tests(tests)
|
function run_tests(tests)
|
||||||
print("Running test suite...")
|
print("Running test suite...")
|
||||||
print("=" .. string.rep("=", 50))
|
print("=" .. string.rep("=", 50))
|
||||||
|
|
||||||
for name, test_fn in pairs(tests) do
|
for name, test_fn in pairs(tests) do
|
||||||
test(name, test_fn)
|
test(name, test_fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
return summary()
|
return summary()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Reset test counters
|
||||||
function reset_tests()
|
function reset_tests()
|
||||||
passed = 0
|
passed = 0
|
||||||
total = 0
|
total = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Get test statistics
|
||||||
function test_stats()
|
function test_stats()
|
||||||
return {
|
return {
|
||||||
passed = passed,
|
passed = passed,
|
||||||
@ -128,10 +141,11 @@ function test_stats()
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Print test summary and return success status
|
||||||
function summary()
|
function summary()
|
||||||
print("=" .. string.rep("=", 50))
|
print("=" .. string.rep("=", 50))
|
||||||
print(string.format("Test Results: %d/%d passed", passed, total))
|
print(string.format("Test Results: %d/%d passed", passed, total))
|
||||||
|
|
||||||
local success = passed == total
|
local success = passed == total
|
||||||
if success then
|
if success then
|
||||||
print("🎉 All tests passed!")
|
print("🎉 All tests passed!")
|
||||||
@ -140,29 +154,32 @@ function summary()
|
|||||||
local rate = total > 0 and (passed / total * 100) or 0
|
local rate = total > 0 and (passed / total * 100) or 0
|
||||||
print(string.format("❌ %d test(s) failed! (%.1f%% success rate)", failed, rate))
|
print(string.format("❌ %d test(s) failed! (%.1f%% success rate)", failed, rate))
|
||||||
end
|
end
|
||||||
|
|
||||||
return success
|
return success
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Exit with appropriate code based on test results
|
||||||
function test_exit()
|
function test_exit()
|
||||||
local success = passed == total
|
local success = passed == total
|
||||||
os.exit(success and 0 or 1)
|
os.exit(success and 0 or 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Convenience function to run and exit
|
||||||
function run_and_exit(tests)
|
function run_and_exit(tests)
|
||||||
local success = run_tests(tests)
|
local success = run_tests(tests)
|
||||||
os.exit(success and 0 or 1)
|
os.exit(success and 0 or 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Benchmark function
|
||||||
function benchmark(name, fn, iterations)
|
function benchmark(name, fn, iterations)
|
||||||
iterations = iterations or 1000
|
iterations = iterations or 1000
|
||||||
print("Benchmarking " .. name .. " (" .. iterations .. " iterations)...")
|
print("Benchmarking " .. name .. " (" .. iterations .. " iterations)...")
|
||||||
|
|
||||||
-- Warmup
|
-- Warmup
|
||||||
for i = 1, math.min(10, iterations) do
|
for i = 1, math.min(10, iterations) do
|
||||||
fn()
|
fn()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Actual benchmark
|
-- Actual benchmark
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
for i = 1, iterations do
|
for i = 1, iterations do
|
||||||
@ -170,10 +187,10 @@ function benchmark(name, fn, iterations)
|
|||||||
end
|
end
|
||||||
local total_time = os.clock() - start
|
local total_time = os.clock() - start
|
||||||
local avg_time = total_time / iterations
|
local avg_time = total_time / iterations
|
||||||
|
|
||||||
print(string.format(" Total: %.3fs, Average: %.6fs, Rate: %.0f ops/sec",
|
print(string.format(" Total: %.3fs, Average: %.6fs, Rate: %.0f ops/sec",
|
||||||
total_time, avg_time, 1/avg_time))
|
total_time, avg_time, 1/avg_time))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total_time = total_time,
|
total_time = total_time,
|
||||||
avg_time = avg_time,
|
avg_time = avg_time,
|
||||||
@ -182,6 +199,7 @@ function benchmark(name, fn, iterations)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Helper to check if file exists
|
||||||
function file_exists(filename)
|
function file_exists(filename)
|
||||||
local file = io.open(filename, "r")
|
local file = io.open(filename, "r")
|
||||||
if file then
|
if file then
|
||||||
@ -189,4 +207,4 @@ function file_exists(filename)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user