In Lua, only the last expression in an argument list preserves multiple results and all earlier expressions yield exactly one value (their first). For example: ``` f(a(), b(), c()) ``` where ``` a -> (1,2) b -> (3,4) c -> (5,6) ``` becomes ``` f(1, 3, 5, 6) ``` which matches Lua behaviour (tested in version 5.4). Changes: * Function argument lists keep multiple results only from the last argument. * Fixes failing truthiness tests (`true and ret_one_two()`). Sidenote: the `truthiness_test.lua` was not called at all because of my obvious copy/paste typo (the `arithmetical_test.lua` was called twice instead; now fixed too and both test suites pass ok).
255 lines
5.6 KiB
Lua
255 lines
5.6 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")
|
|
|
|
-- 12. Only the last expression expands
|
|
|
|
local function ret() return "A", "B" end
|
|
local function a() return "A1", "A2" end
|
|
local function b() return "B1", "B2" end
|
|
local function c() return "C1", "C2" end
|
|
|
|
assert_eq(string.format("%s-%s", ret()), "A-B",
|
|
"last-only: single arg expands")
|
|
|
|
assert_eq(string.format("%s-%s", ret(), "Z"), "A-Z",
|
|
"last-only: earlier arg is single")
|
|
|
|
assert_eq(string.format("%s-%s-%s-%s", a(), b(), c()), "A1-B1-C1-C2",
|
|
"last-only: only last expands, earlier args single")
|
|
|
|
-- 13. Only false and `nil` are falsey
|
|
|
|
assert_false(not 0, "not 0 must be false")
|
|
assert_false(not -0.0, "not -0.0 must be false")
|
|
assert_false(not "", "not '' must be false")
|
|
assert_false(not {}, "not {} must be false")
|
|
|
|
assert_true(not nil, "not nil must be true")
|
|
assert_true(not false, "not false must be true")
|