2024-10-09 01:53:09 +08:00
|
|
|
local function assert_equal(a, b)
|
|
|
|
if a ~= b then
|
|
|
|
error("Assertion failed: " .. a .. " is not equal to " .. b)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
-- Basic checks
|
|
|
|
assert(true, "True is true")
|
|
|
|
|
|
|
|
-- Basic arithmetic
|
|
|
|
assert(1 + 2 == 3)
|
|
|
|
-- Slightly more complex arithmetic with presedence
|
|
|
|
assert(1 + 2 * 3 == 7)
|
|
|
|
-- Arithmetic with variables
|
|
|
|
local a = 1
|
|
|
|
local b = 2
|
|
|
|
assert(a + b == 3)
|
|
|
|
|
2024-10-10 02:35:07 +08:00
|
|
|
-- Basic string stuff
|
2024-10-04 23:15:50 +08:00
|
|
|
assert("Hello " .. "world" == "Hello world")
|
2024-10-10 02:35:07 +08:00
|
|
|
assert_equal([[Hello world]], "Hello world")
|
|
|
|
assert_equal([==[Hello [[world]]!]==], "Hello [[world]]!")
|
2024-10-04 23:15:50 +08:00
|
|
|
|
|
|
|
-- Various forms of function definitions
|
|
|
|
function f1()
|
|
|
|
return 1
|
|
|
|
end
|
2024-10-09 01:53:09 +08:00
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
assert(f1() == 1)
|
|
|
|
|
|
|
|
function sqr(a)
|
|
|
|
return a * a
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(sqr(2) == 4)
|
|
|
|
|
|
|
|
local f2 = function()
|
|
|
|
return 2
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(f2() == 2)
|
|
|
|
|
|
|
|
-- Using functions as arguments
|
|
|
|
function apply(f, a)
|
|
|
|
return f(a)
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(apply(sqr, 3) == 9)
|
|
|
|
|
|
|
|
-- Supporting multiple return values
|
|
|
|
function multi_return()
|
|
|
|
return 1, 2
|
|
|
|
end
|
2024-10-09 01:53:09 +08:00
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
local a, b = multi_return()
|
|
|
|
assert(a == 1 and b == 2)
|
|
|
|
|
|
|
|
local a, b, c = 0, multi_return()
|
|
|
|
assert(a == 0 and b == 1 and c == 2)
|
|
|
|
|
|
|
|
-- Some table lookups
|
|
|
|
local t = { a = 1, b = 2 }
|
|
|
|
assert(t.a == 1 and t.b == 2)
|
|
|
|
assert(t["a"] == 1 and t["b"] == 2)
|
|
|
|
|
|
|
|
-- Unpacking tables
|
|
|
|
local a, b = unpack({ 1, 2 })
|
|
|
|
assert(a == 1 and b == 2)
|
|
|
|
|
|
|
|
-- Scope tests
|
|
|
|
local a = 1
|
|
|
|
do
|
|
|
|
local a = 2
|
|
|
|
assert(a == 2)
|
|
|
|
end
|
|
|
|
assert(a == 1)
|
|
|
|
|
2025-01-09 18:45:15 +08:00
|
|
|
-- Async function calling
|
|
|
|
function multiplier(a)
|
|
|
|
-- Anything will be async in practice
|
|
|
|
return function(b)
|
|
|
|
return a * b
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local multiplier = multiplier(2)
|
|
|
|
assert(multiplier(3) == 6)
|
|
|
|
|
2024-10-04 23:15:50 +08:00
|
|
|
-- Function definitions in tables
|
|
|
|
ns = { name = "Pete" }
|
|
|
|
function ns.returnOne()
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
|
|
|
|
function ns:getName()
|
|
|
|
return self.name
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(ns.returnOne() == 1)
|
|
|
|
assert(ns.getName(ns) == "Pete")
|
|
|
|
-- Support colon syntax
|
|
|
|
assert(ns:getName() == "Pete")
|
|
|
|
-- Update the table
|
|
|
|
ns.name = "John"
|
|
|
|
assert(ns:getName() == "John")
|
|
|
|
|
|
|
|
-- Basic OOP with metatables
|
|
|
|
Person = {}
|
|
|
|
Person.__index = Person
|
|
|
|
|
|
|
|
-- Constructor
|
|
|
|
function Person:new(name, age)
|
|
|
|
local self = setmetatable({}, Person)
|
|
|
|
self.name = name
|
|
|
|
-- Initialize object properties
|
|
|
|
self.age = age
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Method for the Person class
|
|
|
|
function Person:greet()
|
|
|
|
return "Hello, my name is " .. self.name .. " and I am " .. self.age .. " years old."
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Create a new instance of the Person class
|
|
|
|
local p = Person:new("John", 30)
|
|
|
|
assert(p:greet() == "Hello, my name is John and I am 30 years old.")
|
|
|
|
|
|
|
|
-- Metatables test
|
|
|
|
mt = {
|
|
|
|
__index = function(table, key)
|
|
|
|
return "Key not found: " .. key
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
t = setmetatable({}, mt)
|
|
|
|
t.bar = "bar"
|
|
|
|
assert(t.bar == "bar")
|
2024-10-20 21:06:23 +08:00
|
|
|
assert_equal(t.foo, "Key not found: foo")
|
2024-10-04 23:15:50 +08:00
|
|
|
|
|
|
|
-- Test the __newindex metamethod
|
|
|
|
t = setmetatable(
|
|
|
|
{}, {
|
|
|
|
__newindex = function(table, key, value)
|
|
|
|
rawset(table, key, "Value: " .. value)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
t.name = "John"
|
|
|
|
-- rawset ignores the metamethod
|
|
|
|
rawset(t, "age", 100)
|
|
|
|
assert(t.name == "Value: John")
|
|
|
|
assert(t.age == 100)
|
|
|
|
|
|
|
|
-- Test some of the operator metamethods
|
|
|
|
t = setmetatable(
|
|
|
|
{ 1, 2, 3 },
|
|
|
|
{
|
|
|
|
-- Assume b to be a same length table and add the two
|
|
|
|
__add = function(a, b)
|
|
|
|
local result = {}
|
|
|
|
for i = 1, #a do
|
|
|
|
result[i] = a[i] + b[i]
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end,
|
|
|
|
-- Assume b to be a scalar and multiply the table by it
|
|
|
|
__mul = function(a, b)
|
|
|
|
local result = {}
|
|
|
|
for i = 1, #a do
|
|
|
|
result[i] = a[i] * b
|
|
|
|
end
|
|
|
|
return result
|
|
|
|
end
|
|
|
|
}
|
|
|
|
)
|
|
|
|
local added = t + { 4, 5, 6 }
|
|
|
|
assert(added[1] == 5 and added[2] == 7 and added[3] == 9)
|
|
|
|
local muliplied = t * 2
|
|
|
|
assert(muliplied[1] == 2 and muliplied[2] == 4 and muliplied[3] == 6)
|
|
|
|
|
|
|
|
-- Let's try somethings lightly more complicated, like a deep comparison function implemented in Lua
|
|
|
|
function deepCompare(t1, t2)
|
|
|
|
if t1 == t2 then return true end
|
|
|
|
-- If they are the same object, return true
|
|
|
|
if type(t1) ~= "table" or type(t2) ~= "table" then return false end
|
|
|
|
-- If not both tables, return false
|
|
|
|
-- Check if both tables have the same number of keys
|
|
|
|
local t1_keys = 0
|
|
|
|
local t2_keys = 0
|
|
|
|
for k in pairs(t1) do
|
|
|
|
t1_keys = t1_keys + 1
|
|
|
|
end
|
|
|
|
for k in pairs(t2) do
|
|
|
|
t2_keys = t2_keys + 1
|
|
|
|
end
|
|
|
|
if t1_keys ~= t2_keys then return false end
|
|
|
|
|
|
|
|
-- Recursively compare each key-value pair
|
|
|
|
for k, v in pairs(t1) do
|
|
|
|
if not deepCompare(v, t2[k]) then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
assert(deepCompare({ 1, 2, 3 }, { 1, 2, 3 }))
|
|
|
|
assert(not deepCompare({ 1, 2, 3 }, { 1, 2 }))
|
|
|
|
assert(deepCompare({ a = 1, b = 2 }, { a = 1, b = 2 }))
|
|
|
|
assert(deepCompare(
|
|
|
|
{ a = { 1, 2, 3 }, b = { 4, 5, 6 } },
|
|
|
|
{ a = { 1, 2, 3 }, b = { 4, 5, 6 } }
|
|
|
|
))
|
|
|
|
assert(not deepCompare(
|
|
|
|
{ a = { 1, 2, 3 }, b = { 4, 5, 6 } },
|
|
|
|
{ a = { 1, 2, 3 }, b = { 4, 5, 7 } }
|
2024-10-05 21:37:36 +08:00
|
|
|
))
|
|
|
|
|
|
|
|
-- String serialization
|
|
|
|
assert(tostring({ 1, 2, 3 }) == "{1, 2, 3}")
|
2024-10-09 01:53:09 +08:00
|
|
|
assert(tostring({ a = 1, b = 2 }) == "{a = 1, b = 2}")
|
|
|
|
|
|
|
|
-- Error handling
|
|
|
|
local status, err = pcall(function()
|
|
|
|
error("This is an error")
|
|
|
|
end)
|
|
|
|
|
|
|
|
assert(not status)
|
|
|
|
assert(err == "This is an error")
|
|
|
|
|
|
|
|
local status, err = xpcall(function()
|
|
|
|
error("This is an error")
|
|
|
|
end, function(err)
|
|
|
|
return "Caught error: " .. err
|
|
|
|
end)
|
|
|
|
|
|
|
|
assert(not status)
|
|
|
|
assert_equal(err, "Caught error: This is an error")
|
|
|
|
|
|
|
|
-- ipairs
|
|
|
|
local p = ipairs({ 3, 2, 1 })
|
|
|
|
local idx, value = p()
|
|
|
|
assert(idx == 1 and value == 3)
|
|
|
|
idx, value = p()
|
|
|
|
assert(idx == 2 and value == 2)
|
|
|
|
idx, value = p()
|
|
|
|
assert(idx == 3 and value == 1)
|
|
|
|
idx, value = p()
|
|
|
|
assert(idx == nil and value == nil)
|
|
|
|
|
|
|
|
for index, value in ipairs({ 1, 2, 3 }) do
|
|
|
|
assert(index == value)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- pairs
|
|
|
|
local p = pairs({ a = 1, b = 2, c = 3 })
|
|
|
|
local key, value = p()
|
|
|
|
assert(key == "a" and value == 1)
|
|
|
|
key, value = p()
|
|
|
|
assert(key == "b" and value == 2)
|
|
|
|
key, value = p()
|
|
|
|
assert(key == "c" and value == 3)
|
|
|
|
key, value = p()
|
|
|
|
assert(key == nil and value == nil)
|
|
|
|
for key, value in pairs({ a = "a", b = "b" }) do
|
|
|
|
assert_equal(key, value)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- type
|
|
|
|
assert(type(1) == "number")
|
|
|
|
assert(type("Hello") == "string")
|
|
|
|
assert(type({}) == "table")
|
|
|
|
assert(type(nil) == "nil")
|
|
|
|
assert(type(true) == "boolean")
|
|
|
|
assert_equal(type(function() end), "function")
|
|
|
|
|
|
|
|
-- string functions
|
|
|
|
assert(string.len("Hello") == 5)
|
|
|
|
assert(string.byte("Hello", 1) == 72)
|
|
|
|
assert(string.char(72) == "H")
|
|
|
|
assert(string.find("Hello", "l") == 3)
|
|
|
|
assert(string.rep("Hello", 3) == "HelloHelloHello")
|
|
|
|
assert(string.sub("Hello", 2, 4) == "ell")
|
|
|
|
assert(string.upper("Hello") == "HELLO")
|
|
|
|
assert(string.lower("Hello") == "hello")
|
|
|
|
|
2025-01-09 16:00:29 +08:00
|
|
|
-- Test string.gsub with various replacement types
|
|
|
|
-- Simple string replacement
|
|
|
|
local result, count = string.gsub("hello world", "hello", "hi")
|
|
|
|
assert(result == "hi world", "Basic string replacement failed")
|
|
|
|
assert(count == 1, "Basic replacement count failed")
|
|
|
|
|
|
|
|
-- Multiple replacements
|
|
|
|
result, count = string.gsub("hello hello hello", "hello", "hi")
|
|
|
|
assert(result == "hi hi hi", "Multiple replacements failed")
|
|
|
|
assert(count == 3, "Multiple replacement count failed")
|
|
|
|
|
|
|
|
-- Limited replacements with n parameter
|
|
|
|
result, count = string.gsub("hello hello hello", "hello", "hi", 2)
|
|
|
|
assert(result == "hi hi hello", "Limited replacements failed")
|
|
|
|
assert(count == 2, "Limited replacement count failed")
|
|
|
|
|
|
|
|
-- Function replacement without captures
|
|
|
|
result = string.gsub("hello world", "hello", function(match)
|
|
|
|
assert(match == "hello", "Function received incorrect match")
|
|
|
|
return string.upper(match)
|
|
|
|
end)
|
|
|
|
assert(result == "HELLO world", "Function replacement without captures failed")
|
|
|
|
|
|
|
|
-- Function replacement with single capture
|
|
|
|
result = string.gsub("hello world", "(h)ello", function(h)
|
|
|
|
assert(h == "h", "Function received incorrect capture")
|
|
|
|
return string.upper(h) .. "i"
|
|
|
|
end)
|
|
|
|
assert(result == "Hi world", "Function replacement with single capture failed")
|
|
|
|
|
|
|
|
-- Function replacement with multiple captures
|
|
|
|
result = string.gsub("hello world", "(h)(e)(l)(l)o", function(h, e, l1, l2)
|
|
|
|
assert(h == "h" and e == "e" and l1 == "l" and l2 == "l",
|
|
|
|
"Function received incorrect captures: " .. h .. ", " .. e .. ", " .. l1 .. ", " .. l2)
|
|
|
|
return string.upper(h) .. string.upper(e) .. l1 .. l2 .. "o"
|
|
|
|
end)
|
|
|
|
assert(result == "HEllo world", "Function replacement with multiple captures failed")
|
|
|
|
|
|
|
|
-- Function returning nil (should keep original match)
|
|
|
|
result = string.gsub("hello world", "hello", function() return nil end)
|
|
|
|
assert(result == "hello world", "Function returning nil failed")
|
|
|
|
|
|
|
|
-- Pattern with multiple matches on same position
|
|
|
|
result = string.gsub("hello world", "h?e", "X")
|
|
|
|
assert(result == "Xllo world", "Overlapping matches failed")
|
|
|
|
|
|
|
|
-- Empty captures
|
|
|
|
result = string.gsub("hello", "(h()e)", function(full, empty)
|
|
|
|
assert(full == "he" and empty == "", "Empty capture handling failed")
|
|
|
|
return "XX"
|
|
|
|
end)
|
|
|
|
assert(result == "XXllo", "Empty capture replacement failed")
|
|
|
|
|
|
|
|
-- Patterns with magic characters
|
|
|
|
result = string.gsub("hello.world", "%.", "-")
|
|
|
|
assert(result == "hello-world", "Magic character replacement failed")
|
|
|
|
|
2024-10-09 01:53:09 +08:00
|
|
|
-- table functions
|
|
|
|
local t = { 1, 2, 3 }
|
|
|
|
table.insert(t, 4)
|
|
|
|
assert_equal(t[4], 4)
|
|
|
|
table.remove(t, 1)
|
|
|
|
assert_equal(t[1], 2)
|
|
|
|
table.insert(t, 1, 1)
|
|
|
|
assert_equal(t[1], 1)
|
|
|
|
assert_equal(table.concat({ "Hello", "world" }, " "), "Hello world")
|
|
|
|
|
|
|
|
local t = { 3, 1, 2 }
|
|
|
|
table.sort(t)
|
|
|
|
assert_equal(t[1], 1)
|
|
|
|
assert_equal(t[2], 2)
|
|
|
|
assert_equal(t[3], 3)
|
|
|
|
table.sort(t, function(a, b)
|
|
|
|
return a > b
|
|
|
|
end)
|
|
|
|
assert_equal(t[1], 3)
|
|
|
|
assert_equal(t[2], 2)
|
|
|
|
assert_equal(t[3], 1)
|
|
|
|
|
|
|
|
local data = { { name = "John", age = 30 }, { name = "Jane", age = 25 } }
|
|
|
|
table.sort(data, function(a, b)
|
|
|
|
return a.age < b.age
|
|
|
|
end)
|
|
|
|
assert_equal(data[1].name, "Jane")
|
|
|
|
assert_equal(data[2].name, "John")
|
2024-10-10 02:35:07 +08:00
|
|
|
|
|
|
|
-- os functions
|
|
|
|
assert(os.time() > 0)
|
|
|
|
assert(os.date("%Y-%m-%d", os.time({ year = 2020, month = 1, day = 1 })) == "2020-01-01")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
|
|
|
-- Closure tests
|
|
|
|
local function make_counter()
|
|
|
|
local count = 0
|
|
|
|
return function()
|
|
|
|
count = count + 1
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local counter1 = make_counter()
|
|
|
|
local counter2 = make_counter()
|
|
|
|
assert(counter1() == 1, "First counter first call")
|
|
|
|
assert(counter1() == 2, "First counter second call")
|
|
|
|
assert(counter2() == 1, "Second counter should be independent")
|
|
|
|
assert(counter1() == 3, "First counter maintains state")
|
|
|
|
|
|
|
|
-- Test nested closures
|
|
|
|
local function make_adder(x)
|
|
|
|
return function(y)
|
|
|
|
return function(z)
|
|
|
|
return x + y + z
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local add5 = make_adder(5)
|
|
|
|
local add5and2 = add5(2)
|
|
|
|
assert(add5and2(3) == 10, "Nested closure should maintain all scopes")
|
|
|
|
|
|
|
|
-- Test closure variable independence
|
|
|
|
local function make_value_keeper()
|
|
|
|
local value = 0
|
|
|
|
return {
|
|
|
|
set = function(v) value = v end,
|
|
|
|
get = function() return value end
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local keeper1 = make_value_keeper()
|
|
|
|
local keeper2 = make_value_keeper()
|
|
|
|
keeper1.set(5)
|
|
|
|
keeper2.set(10)
|
|
|
|
assert(keeper1.get() == 5, "First keeper maintains its own value")
|
|
|
|
assert(keeper2.get() == 10, "Second keeper maintains its own value")
|
|
|
|
|
|
|
|
-- Test closure over loop variables
|
|
|
|
local functions = {}
|
|
|
|
for i = 1, 3 do
|
|
|
|
functions[i] = function() return i end
|
|
|
|
end
|
|
|
|
assert(functions[1]() == 1, "Closure should capture loop variable value at creation time")
|
|
|
|
assert(functions[2]() == 2, "Each closure should have its own value")
|
|
|
|
assert(functions[3]() == 3, "Each closure should have its own value")
|
|
|
|
|
|
|
|
-- Test closure over mutating variables
|
|
|
|
local function make_accumulator(initial)
|
|
|
|
local sum = initial
|
|
|
|
return {
|
|
|
|
add = function(x) sum = sum + x end,
|
|
|
|
get = function() return sum end
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local acc = make_accumulator(5)
|
|
|
|
acc.add(3)
|
|
|
|
acc.add(2)
|
|
|
|
assert(acc.get() == 10, "Accumulator should maintain state through multiple calls")
|
|
|
|
|
|
|
|
-- Test closures with upvalues modified in nested scopes
|
|
|
|
local function make_counter_with_reset()
|
|
|
|
local count = 0
|
|
|
|
return {
|
|
|
|
increment = function()
|
|
|
|
local old = count
|
|
|
|
count = count + 1
|
|
|
|
return old
|
|
|
|
end,
|
|
|
|
reset = function()
|
|
|
|
local old = count
|
|
|
|
count = 0
|
|
|
|
return old
|
|
|
|
end
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local counter = make_counter_with_reset()
|
|
|
|
assert(counter.increment() == 0)
|
|
|
|
assert(counter.increment() == 1)
|
|
|
|
local final = counter.reset()
|
|
|
|
assert(final == 2, "Reset should return last value")
|
|
|
|
assert(counter.increment() == 0, "Counter should start fresh after reset")
|
|
|
|
|
|
|
|
|
|
|
|
-- Advanced closure tests with multiple functions sharing state
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Counter that can count by custom steps
|
|
|
|
local function make_counter_with_step()
|
|
|
|
local count = 0
|
|
|
|
return {
|
|
|
|
increment = function(step)
|
|
|
|
count = count + (step or 1)
|
|
|
|
return count
|
|
|
|
end,
|
|
|
|
decrement = function(step)
|
|
|
|
count = count - (step or 1)
|
|
|
|
return count
|
|
|
|
end,
|
|
|
|
get = function()
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
}
|
|
|
|
end
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
local counter = make_counter_with_step()
|
|
|
|
assert(counter.increment(5) == 5, "Counter should increment by 5")
|
|
|
|
assert(counter.decrement(2) == 3, "Counter should decrement by 2")
|
|
|
|
assert(counter.get() == 3, "Counter should maintain state")
|
|
|
|
assert(counter.increment() == 4, "Counter should default to 1")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test multiple independent counters
|
|
|
|
local c1 = make_counter_with_step()
|
|
|
|
local c2 = make_counter_with_step()
|
|
|
|
c1.increment(10)
|
|
|
|
c2.increment(5)
|
|
|
|
assert(c1.get() == 10, "First counter should be independent")
|
|
|
|
assert(c2.get() == 5, "Second counter should be independent")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
|
|
|
-- Test varargs handling
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Basic varargs sum function
|
|
|
|
local function sum(...)
|
|
|
|
local total = 0
|
|
|
|
for i, v in ipairs({ ... }) do
|
|
|
|
total = total + v
|
2025-01-08 17:00:46 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
return total
|
|
|
|
end
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
assert(sum() == 0, "Sum should handle no arguments")
|
|
|
|
assert(sum(42) == 42, "Sum should handle single argument")
|
|
|
|
assert(sum(1, 2) == 3, "Sum should handle two arguments")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test varargs propagation
|
|
|
|
local function pass_varargs(...)
|
|
|
|
return sum(...)
|
2025-01-08 17:00:46 +08:00
|
|
|
end
|
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
assert(pass_varargs() == 0, "Should propagate empty varargs")
|
|
|
|
assert(pass_varargs(1, 2, 3) == 6, "Should propagate varargs")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:04:33 +08:00
|
|
|
-- Test closure behavior
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Counter that can count by custom steps
|
|
|
|
local function make_counter_with_step()
|
|
|
|
local count = 0
|
|
|
|
return {
|
|
|
|
increment = function(step)
|
|
|
|
count = count + (step or 1)
|
2025-01-08 18:04:33 +08:00
|
|
|
return count
|
2025-01-08 18:23:30 +08:00
|
|
|
end,
|
|
|
|
decrement = function(step)
|
|
|
|
count = count - (step or 1)
|
2025-01-08 18:04:33 +08:00
|
|
|
return count
|
2025-01-08 18:23:30 +08:00
|
|
|
end,
|
|
|
|
get = function()
|
2025-01-08 18:04:33 +08:00
|
|
|
return count
|
2025-01-08 17:00:46 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local counter = make_counter_with_step()
|
|
|
|
assert(counter.increment(5) == 5, "Counter should increment by 5")
|
|
|
|
assert(counter.decrement(2) == 3, "Counter should decrement by 2")
|
|
|
|
assert(counter.get() == 3, "Counter should maintain state")
|
|
|
|
assert(counter.increment() == 4, "Counter should default to 1")
|
2025-01-08 17:00:46 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test multiple independent counters
|
|
|
|
local c1 = make_counter_with_step()
|
|
|
|
local c2 = make_counter_with_step()
|
|
|
|
c1.increment(10)
|
|
|
|
c2.increment(5)
|
|
|
|
assert(c1.get() == 10, "First counter should be independent")
|
|
|
|
assert(c2.get() == 5, "Second counter should be independent")
|
|
|
|
|
|
|
|
-- Test closures with shared upvalues
|
|
|
|
local function make_shared_counter()
|
|
|
|
local count = 0
|
|
|
|
local function inc()
|
|
|
|
count = count + 1
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
local function dec()
|
|
|
|
count = count - 1
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
local function get()
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
return inc, dec, get
|
2025-01-08 17:00:46 +08:00
|
|
|
end
|
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
local inc, dec, get = make_shared_counter()
|
|
|
|
assert(inc() == 1, "First increment")
|
|
|
|
assert(inc() == 2, "Second increment")
|
|
|
|
assert(dec() == 1, "First decrement")
|
|
|
|
assert(get() == 1, "Get should return current value")
|
|
|
|
|
2025-01-08 18:12:26 +08:00
|
|
|
|
|
|
|
-- Test custom iterators
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Basic iterator that counts down from n to 1
|
|
|
|
local function countdown(n)
|
|
|
|
local count = n
|
|
|
|
return function()
|
|
|
|
if count > 0 then
|
|
|
|
local current = count
|
|
|
|
count = count - 1
|
|
|
|
return current
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
end
|
2025-01-08 18:12:26 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test basic iterator usage
|
|
|
|
local sum = 0
|
|
|
|
for num in countdown(3) do
|
|
|
|
sum = sum + num
|
|
|
|
end
|
|
|
|
assert(sum == 6, "Countdown iterator should sum to 6 (3+2+1)")
|
2025-01-08 18:12:26 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Iterator that returns even numbers from an array
|
|
|
|
local function even_values(arr)
|
|
|
|
local index = 0
|
|
|
|
return function()
|
|
|
|
repeat
|
|
|
|
index = index + 1
|
|
|
|
if index > #arr then return nil end
|
|
|
|
if arr[index] % 2 == 0 then
|
|
|
|
return index, arr[index]
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
until false
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
end
|
2025-01-08 18:12:26 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test array iterator
|
|
|
|
local arr = { 1, 2, 3, 4, 6, 7, 8 }
|
|
|
|
local count = 0
|
|
|
|
local sum = 0
|
|
|
|
for i, v in even_values(arr) do
|
|
|
|
count = count + 1
|
|
|
|
sum = sum + v
|
|
|
|
end
|
|
|
|
assert(count == 4, "Should find 4 even numbers")
|
|
|
|
assert(sum == 20, "Sum of even numbers should be 20 (2+4+6+8)")
|
|
|
|
|
|
|
|
-- Range iterator with step
|
|
|
|
local function range(from, to, step)
|
|
|
|
step = step or 1
|
|
|
|
local current = from
|
|
|
|
return function()
|
|
|
|
if current > to then
|
|
|
|
return nil
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
local value = current
|
|
|
|
current = current + step
|
|
|
|
return value
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
end
|
2025-01-08 18:12:26 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
-- Test range iterator with different steps
|
|
|
|
local function collect_range(from, to, step)
|
|
|
|
local values = {}
|
|
|
|
for v in range(from, to, step) do
|
|
|
|
table.insert(values, v)
|
|
|
|
end
|
|
|
|
return values
|
|
|
|
end
|
|
|
|
|
|
|
|
local values1 = collect_range(1, 5, 2)
|
|
|
|
assert(#values1 == 3, "Range with step 2 should return 3 values")
|
|
|
|
assert(values1[1] == 1 and values1[2] == 3 and values1[3] == 5, "Range values with step 2 should be correct")
|
|
|
|
|
|
|
|
local values2 = collect_range(10, 15)
|
|
|
|
assert(#values2 == 6, "Range with default step should return 6 values")
|
|
|
|
assert(values2[1] == 10 and values2[6] == 15, "Range values with default step should be correct")
|
|
|
|
|
|
|
|
local values3 = collect_range(1, 10, 3)
|
|
|
|
assert(#values3 == 4, "Range with step 3 should return 4 values")
|
|
|
|
assert(values3[1] == 1 and values3[2] == 4 and values3[3] == 7 and values3[4] == 10,
|
|
|
|
"Range values with step 3 should be correct")
|
|
|
|
|
|
|
|
-- Test nested iterators
|
|
|
|
local function grid(rows, cols)
|
|
|
|
local row = 0
|
|
|
|
return function()
|
|
|
|
row = row + 1
|
|
|
|
if row <= rows then
|
|
|
|
local col = 0
|
|
|
|
return function()
|
|
|
|
col = col + 1
|
|
|
|
if col <= cols then
|
|
|
|
return row, col
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2025-01-08 18:23:30 +08:00
|
|
|
end
|
2025-01-08 18:12:26 +08:00
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
local points = {}
|
|
|
|
for row_iter in grid(2, 3) do
|
|
|
|
for r, c in row_iter do
|
|
|
|
table.insert(points, { r, c })
|
2025-01-08 18:12:26 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-01-08 18:23:30 +08:00
|
|
|
assert(#points == 6, "Grid should generate 6 points")
|
|
|
|
assert(points[1][1] == 1 and points[1][2] == 1, "First point should be (1,1)")
|
|
|
|
assert(points[6][1] == 2 and points[6][2] == 3, "Last point should be (2,3)")
|
2025-01-10 01:22:12 +08:00
|
|
|
|
|
|
|
-- Test space_lua stuff
|
|
|
|
local parsedExpr = space_lua.parse_expression("1 + 1")
|
|
|
|
local evalResult = space_lua.eval_expression(parsedExpr)
|
|
|
|
assert(evalResult == 2, "Eval should return 2")
|
|
|
|
|
|
|
|
-- Slightly more advanced example with augmented environment
|
|
|
|
local parsedExpr = space_lua.parse_expression("tostring(a + 1)")
|
|
|
|
local evalResult = space_lua.eval_expression(parsedExpr, { a = 1 })
|
|
|
|
assert(evalResult == "2", "Eval should return 2 as a string")
|