Files
plainleaf/client/space_lua/stdlib/table_test.lua
T
Matouš Jan Fialka d2eb8fe6ec [SpaceLua] Use luaFormatNumber for number-to-string coercion in .. and table.concat
Replace raw `String(v)` calls with `luaFormatNumber` in the concatenation
operator and `table.concat`, so that floats are formatted according to Lua
semantics rather than JavaScript's. This fixes cases like `10.8*22..""`
producing `237.60000000000002` instead of `237.6`, and ensures
integer-valued floats carry the `.0` suffix as Lua requires.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
2026-02-20 07:31:32 +01:00

288 lines
6.8 KiB
Lua

local function deepCompare(t1, t2, seen)
if t1 == t2 then return true end
if type(t1) ~= "table" or type(t2) ~= "table" then return false end
seen = seen or {}
if seen[t1] and seen[t1] == t2 then return true end
seen[t1] = t2
local n1, n2 = 0, 0
for _ in pairs(t1) do n1 = n1 + 1 end
for _ in pairs(t2) do n2 = n2 + 1 end
if n1 ~= n2 then return false end
for k, v in pairs(t1) do
if not deepCompare(v, t2[k], seen) then
return false
end
end
return true
end
local function assertEqual(a, b)
if not deepCompare(a, b) then
error("Assertion failed: " .. tostring(a) .. " is not equal to " .. tostring(b))
end
end
local function assertTrue(v, msg)
if not v then error(msg or "Assertion failed") end
end
local function assertFalse(v, msg)
if v then error(msg or "Assertion failed (expected false)") end
end
local function assertError(fn, msg)
local ok, err = pcall(fn)
if ok then
error(msg or "Assertion failed (expected error)")
end
return err
end
-- Basic table operations (insert/remove)
do
local t = { 1, 2, 3 }
table.insert(t, 4)
assertEqual(t[4], 4)
table.insert(t, 1, 99)
assertEqual(t[1], 99)
assertEqual(#t, 5)
local x = table.remove(t, 1)
assertEqual(x, 99)
table.remove(t) -- remove last
assertEqual(#t, 3)
assertEqual(t, { 1, 2, 3 })
-- remove out of range should error
assertError(function() table.remove(t, 0) end)
assertError(function() table.remove(t, #t + 2) end)
end
-- Reference semantics / aliasing (tables are mutable references)
do
local a = { x = 1 }
local b = a
b.x = 2
assertEqual(a.x, 2)
local nested = { inner = { 1, 2 } }
local alias = nested.inner
alias[1] = 10
assertEqual(nested.inner[1], 10)
end
-- rawget/rawset/rawequal ignore metamethods
do
local backing = {}
local t = setmetatable({}, {
__index = function(_, k)
return backing[k]
end,
__newindex = function(_, k, v)
backing[k] = v
end,
})
t.a = 1
assertEqual(backing.a, 1)
assertEqual(t.a, 1)
rawset(t, "a", 2)
assertEqual(rawget(t, "a"), 2)
assertEqual(backing.a, 1)
assertEqual(rawget(t, "missing"), nil)
local t2 = t
local t3 = {}
assertTrue(rawequal(t2, t))
assertFalse(rawequal(t3, t))
end
-- Length operator semantics
do
local seq = { 1, 2, 3 }
assertEqual(#seq, 3)
local holes = { 1, 2, 3, nil, nil, nil, 4 }
assertEqual(rawlen(holes), 7)
local t = { 1, 2, 3 }
t[5] = 5
local collected = {}
for i, v in ipairs(t) do
collected[i] = v
end
assertEqual(collected, { 1, 2, 3 })
end
-- __len metamethod can override #t
do
local t = setmetatable({ 1, 2, 3 }, {
__len = function(_) return 123 end
})
assertEqual(#t, 123)
end
-- pairs / __pairs
do
local t = setmetatable({ a = 1, b = 2 }, {
__pairs = function(_)
local yielded = false
return function()
if yielded then return nil end
yielded = true
return "only", 42
end
end
})
local out = {}
for k, v in pairs(t) do
out[k] = v
end
assertEqual(out, { only = 42 })
end
-- next
do
local t = { a = 1, b = 2, c = 3 }
local k, v = next(t, nil)
assertTrue(k ~= nil)
assertTrue(v ~= nil)
local seen = {}
while k do
seen[k] = v
k, v = next(t, k)
end
assertEqual(seen.a, 1)
assertEqual(seen.b, 2)
assertEqual(seen.c, 3)
end
-- table.concat
do
assertEqual(table.concat({ "Hello", "world" }, " "), "Hello world")
assertEqual(table.concat({ "Hello", "world", "three" }, " ", 2, 3), "world three")
assertEqual(table.concat({ "a", "b", "c" }), "abc")
assertEqual(table.concat({ "a", "b", "c" }, "", 2, 1), "")
assertError(function() table.concat({ "a", {} }, "") end)
end
-- table.sort
do
local t = { 3, 1, 2 }
table.sort(t)
assertEqual(t, { 1, 2, 3 })
table.sort(t, function(a, b) return a > b end)
assertEqual(t, { 3, 2, 1 })
local data = { { name = "John", age = 30 }, { name = "Jane", age = 25 } }
table.sort(data, function(a, b) return a.age < b.age end)
assertEqual(data[1].name, "Jane")
assertEqual(data[2].name, "John")
assertError(function() table.sort({ 1, "x" }) end)
-- Portable error test: comparator itself throws -> sort must raise
assertError(function()
table.sort({ 2, 1 }, function()
error("boom")
end)
end)
-- NOTE: Do NOT require an error for comparator returning nil; Lua may treat it as false.
end
-- table.pack / table.unpack
do
local t = { 1, 2, 3 }
local packed = table.pack(table.unpack(t))
assertEqual(packed[1], 1)
assertEqual(packed[2], 2)
assertEqual(packed[3], 3)
assertEqual(packed.n, 3)
local p = table.pack(1, nil, 3, nil)
assertEqual(p.n, 4)
assertEqual(p[1], 1)
assertEqual(p[2], nil)
assertEqual(p[3], 3)
assertEqual(p[4], nil)
local a, b, c, d = table.unpack(p, 1, p.n)
assertEqual(a, 1)
assertEqual(b, nil)
assertEqual(c, 3)
assertEqual(d, nil)
local seq = { "x", "y" }
local x, y = table.unpack(seq)
assertEqual(x, "x")
assertEqual(y, "y")
end
-- __index / __newindex semantics
do
local backing = { a = 10 }
local t = setmetatable({}, {
__index = backing,
__newindex = function(_, k, v)
backing[k] = v * 2
end,
})
assertEqual(t.a, 10)
t.b = 5
assertEqual(backing.b, 10)
assertEqual(rawget(t, "b"), nil)
end
-- Equality semantics: tables compare by reference
do
local a = { 1 }
local b = { 1 }
assertFalse(a == b)
local c = a
assertTrue(a == c)
end
-- table.concat
do
assertEqual(table.concat(
{ "Hello", "world" }, " "), "Hello world")
assertEqual(table.concat(
{ "Hello", "world", "three" }, " ", 2, 3), "world three")
assertEqual(table.concat({ "a", "b", "c" }), "abc")
assertEqual(table.concat({ "a", "b", "c" }, "", 2, 1), "")
assertError(function() table.concat({ "a", {} }, "") end)
-- number coercion: integers must not get a decimal point
assertEqual(table.concat({ 1, 2, 3 }, ","), "1,2,3")
-- integer-valued floats must show .0 suffix (Lua semantics)
assertEqual(table.concat({ 1.0, 2.0 }, ","), "1.0,2.0")
-- motivating case: 10.8*22 must produce "237.6" not "237.60000000000002"
assertEqual(table.concat({ 10.8 * 22 }, ""), "237.6")
-- special float values
assertEqual(table.concat({ 1/0.0 }, ""), "inf")
assertEqual(table.concat({ -1/0.0 }, ""), "-inf")
assertEqual(table.concat({ 0.0/0.0 }, ""), "-nan")
-- mixed strings and numbers
assertEqual(table.concat({ "a", 1, "b", 2.5 }, "-"), "a-1-b-2.5")
end