* Space Lua: Fix truthiness semantic deviation Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
229 lines
4.8 KiB
Lua
229 lines
4.8 KiB
Lua
local function assert_eq(actual, expected, msg)
|
|
if actual ~= expected then
|
|
error('assert_eq failed: ' .. msg)
|
|
end
|
|
end
|
|
|
|
local function assert_true(cond, msg)
|
|
if not cond then
|
|
error('assert_true failed: ' .. msg)
|
|
end
|
|
end
|
|
|
|
local function assert_false(cond, msg)
|
|
if cond then
|
|
error('assert_false failed: ' .. msg)
|
|
end
|
|
end
|
|
|
|
-- Helpers for multi-return tests
|
|
|
|
local function ret_nil_one()
|
|
return nil, 1
|
|
end
|
|
|
|
local function ret_false_one()
|
|
return false, 1
|
|
end
|
|
|
|
local function ret_zero_one()
|
|
return 0, 1
|
|
end
|
|
|
|
local function ret_emptystr_one()
|
|
return "", 1
|
|
end
|
|
|
|
local function ret_tbl_one()
|
|
return {}, 1
|
|
end
|
|
|
|
local function ret_one_two()
|
|
return 1, 2
|
|
end
|
|
|
|
-- 1. Basic boolean/nil
|
|
|
|
assert_false(false, "false must be falsey")
|
|
assert_true(true, "true must be truthy")
|
|
assert_false(nil, "nil must be falsey")
|
|
|
|
-- 2. Numbers: all numbers are truthy
|
|
|
|
assert_true(0, "0 must be truthy")
|
|
assert_true(1, "1 must be truthy")
|
|
assert_true(-1, "-1 must be truthy")
|
|
assert_true(0.0, "0.0 must be truthy")
|
|
assert_true(-0.0, "-0.0 must be truthy")
|
|
|
|
-- 3. Strings: empty and non-empty strings are truthy
|
|
|
|
assert_true("", "empty string must be truthy")
|
|
assert_true("0", '"0" must be truthy')
|
|
assert_true("a", '"a" must be truthy')
|
|
|
|
-- 4. Tables: empty and non-empty tables are truthy
|
|
|
|
assert_true({}, "empty table must be truthy")
|
|
assert_true({1}, "non-empty table must be truthy")
|
|
|
|
-- 5. Functions are truthy
|
|
|
|
local fn = function() end
|
|
|
|
assert_true(fn, "function must be truthy")
|
|
assert_true(function() end, "function must be truthy")
|
|
|
|
-- 6. Multi-return: only the first value determines truthiness
|
|
|
|
if ret_nil_one() then
|
|
error("ret_nil_one() must be falsey (first result nil)")
|
|
end
|
|
|
|
if ret_false_one() then
|
|
error("ret_false_one() must be falsey (first result false)")
|
|
end
|
|
|
|
if not ret_zero_one() then
|
|
error("ret_zero_one() must be truthy (first result 0)")
|
|
end
|
|
|
|
if not ret_emptystr_one() then
|
|
error("ret_emptystr_one() must be truthy (first result empty string)")
|
|
end
|
|
|
|
if not ret_tbl_one() then
|
|
error("ret_tbl_one() must be truthy (first result table)")
|
|
end
|
|
|
|
-- 7. `and`/`or` short-circuiting and value returns
|
|
|
|
-- 7.1. `and` returns first falsey value, otherwise last value
|
|
|
|
assert_eq((true and 2), 2, "and: true and 2 -> 2")
|
|
assert_eq((false and 2), false, "and: false and 2 -> false")
|
|
assert_eq((nil and 2), nil, "and: nil and 2 -> nil")
|
|
assert_eq((0 and "x"), "x", "and: 0 and 'x' -> 'x' (0 is truthy)")
|
|
|
|
-- 7.2. `or` returns first truthy value, otherwise last value
|
|
|
|
assert_eq((false or 3), 3, "or: false or 3 -> 3")
|
|
assert_eq((nil or 3), 3, "or: nil or 3 -> 3")
|
|
assert_eq((0 or 3), 0, "or: 0 or 3 -> 0 (0 is truthy)")
|
|
assert_eq(("" or "x"), "", "or: '' or 'x' -> '' ('' is truthy)")
|
|
|
|
-- 7.3. Multi-return: operand evaluation yields first value
|
|
|
|
assert_eq((true and ret_one_two()), 1,
|
|
"and: true and (1,2) yields first result 1")
|
|
|
|
assert_eq((false or ret_one_two()), 1,
|
|
"or: false or (1,2) yields first result 1")
|
|
|
|
-- 8. Litmus tests involving tables and `and`/`or`
|
|
|
|
-- {} and 2 or 3 -> 2
|
|
-- {} or 2 and 3 -> {} (because {} is truthy, or returns left operand)
|
|
|
|
local t = {}
|
|
|
|
assert_eq((t and 2 or 3), 2, "{} and 2 or 3 must be 2")
|
|
|
|
-- `v` should be the same table reference `t`
|
|
|
|
local v = (t or 2 and 3)
|
|
|
|
assert_true(type(v) == "table",
|
|
"{} or 2 and 3 must return the table (truthy left operand)")
|
|
|
|
-- changing `t` reflects in `v` (same reference)
|
|
|
|
t.key = "ok"
|
|
|
|
assert_eq(v.key, "ok",
|
|
"{} or 2 and 3 returns original table reference")
|
|
|
|
-- 9. `if`/`while` with truthiness
|
|
|
|
local ran_if_truthy = false
|
|
|
|
if {} then
|
|
ran_if_truthy = true
|
|
end
|
|
|
|
assert_true(ran_if_truthy, "if {} must run branch")
|
|
|
|
local ran_if_falsey = false
|
|
|
|
if nil then
|
|
ran_if_falsey = true
|
|
end
|
|
|
|
assert_false(ran_if_falsey, "if nil must not run branch")
|
|
|
|
-- `while` must stop only on `false`/`nil`
|
|
|
|
local n = 0
|
|
|
|
local function next_or_nil()
|
|
n = n + 1
|
|
|
|
if n <= 2 then
|
|
return 0
|
|
-- 0 truthy -> loop two times
|
|
end
|
|
|
|
return nil -- stop
|
|
end
|
|
|
|
local count = 0
|
|
|
|
while next_or_nil() do
|
|
count = count + 1
|
|
end
|
|
|
|
assert_eq(count, 2,
|
|
"while should loop while condition is truthy")
|
|
|
|
-- 10. Short-circuit evaluation (no RHS evaluation when not needed)
|
|
|
|
local side = 0
|
|
|
|
local function bump()
|
|
side = side + 1
|
|
return true
|
|
end
|
|
|
|
-- For `true or bump()` RHS must not be evaluated
|
|
|
|
local s0 = side
|
|
local _ = (true or bump())
|
|
|
|
assert_eq(side, s0, "or short-circuit must avoid RHS when LHS truthy")
|
|
|
|
-- For `false and bump()` RHS must not be evaluated
|
|
|
|
local _ = (false and bump())
|
|
|
|
assert_eq(side, s0, "and short-circuit must avoid RHS when LHS falsey")
|
|
|
|
-- 11. Multi-return short-circuit
|
|
|
|
local function falsy_pair()
|
|
return nil, "x"
|
|
end
|
|
|
|
local function truthy_pair()
|
|
return 1, "x"
|
|
end
|
|
|
|
-- `or`: pick RHS only when LHS falsey (first result)
|
|
|
|
local s1 = (falsy_pair() or 5)
|
|
|
|
assert_eq(s1, 5, "or should see falsy first result and evaluate RHS")
|
|
|
|
local s2 = (truthy_pair() or 5)
|
|
|
|
assert_eq(s2, 1, "or should return first result of LHS when truthy")
|