local function assert_eq(actual, expected, message) if actual ~= expected then error('Assertion failed: ' .. message) end end local function assertThrows(msg_substr, fn) local ok, err = pcall(fn) if ok then error('Assertion failed: expected error containing "' .. msg_substr .. '"') end if type(err) ~= 'string' then err = tostring(err) end if not string.find(err, msg_substr, 1, true) then error('Assertion failed: expected error message to contain "' .. msg_substr .. '", got: "' .. err .. '"') end end -- 1. Integer vs float zero divisors -- 1.1. Integer zeros collapse (no -0 for integers) assert_eq(1/0 == 1/-0, true, 'int: 1/0 == 1/-0 (+Inf)') assert_eq(-1/0 == 1/-0, false, 'int: -1/0 != 1/-0') -- 1.2. Float zeros sign preservation assert_eq(1/0.0 == 1/-0.0, false, 'float: +Inf != -Inf') assert_eq(1/0.0 == -1/-0.0, true, 'float: 1/0.0 == -1/-0.0') assert_eq(-1/0.0 == 1/-0.0, true, 'float: -1/0.0 == 1/-0.0') -- 1.3. Basic division assert_eq(5/2, 2.5, 'div: 5/2 == 2.5') assert_eq(-5/2, -2.5, 'div: -5/2 == -2.5') assert_eq(5/-2, -2.5, 'div: 5/-2 == -2.5') assert_eq(-5/-2, 2.5, 'div: -5/-2 == 2.5') -- 2. Unary minus literals and simple expressions assert_eq(1/-(0) == 1/0, true, 'unary minus: int literal (+Inf)') assert_eq(1/-(0.0) == -1/0.0, true, 'unary minus: float literal (-Inf)') assert_eq(1/-(1-1) == 1/0, true, 'unary minus: int expr (+Inf)') assert_eq(1/-(1.0-1.0) == -1/0.0, true, 'unary minus: float expr (-Inf)') -- 2.1. Unary minus coercion and precedence with power assert_eq(-2^2, -4, 'precedence: -2^2 == -(2^2)') assert_eq((-2)^2, 4, 'precedence: (-2)^2 == 4') -- 3. Integer operations (must not produce -0) assert_eq(1/(1-1) == 1/0, true, 'int: sub (+0)') assert_eq(1/(0*-1) == 1/0, true, 'int: mul (+0)') assert_eq(1/(0%1) == 1/0, true, 'int: mod (+0)') assert_eq(1/(0%-1) == 1/0, true, 'int: mod neg divisor (+0)') -- 4. Float operations (must preserve -0.0) assert_eq(1/(0.0*-1.0) == -1/ 0.0, true, 'float: mul (-0.0)') assert_eq(1/((-0.0)%1.0) == -1/ 0.0, true, 'float: mod (-0.0)') assert_eq(1/((-0.0)%-1.0) == -1/ 0.0, true, 'float: mod neg divisor (-0.0)') -- 4.1. Zero result from float addition prefers +0.0 assert_eq(1/((-0.0)+0.0) == 1/0.0, true, 'float: (-0.0)+0.0 yields +0.0') -- 5. Mixed arithmetic producing zero assert_eq(1/(0*-1.0) == -1/0.0, true, 'mixed: mul int*float (-0.0)') assert_eq(1/(0.0*-1 ) == -1/0.0, true, 'mixed: mul float*int (-0.0)') assert_eq(1/(1.0+(-1)) == 1/0.0, true, 'mixed: add (+0.0)') assert_eq(1/(-(1-1.0)) == -1/0.0, true, 'mixed: sub then unary minus (-0.0)') -- 6. Variables local zi, zf, zfn = 0, 0.0, -0.0 assert_eq(1/zi == 1/0, true, 'var: zi (+Inf)') assert_eq(1/zf == 1/0.0, true, 'var: zf (+Inf)') assert_eq(1/zfn == -1/0.0, true, 'var: zfn (-Inf)') assert_eq(1/-(zi) == 1/0, true, 'var: unary minus zi (+Inf)') assert_eq(1/-(zfn) == 1/0.0, true, 'var: unary minus zfn (+Inf)') -- 7. Functions returning zeros and unary minus local function ret_zi() return 0 end local function ret_zf() return 0.0 end local function ret_zfn() return -0.0 end assert_eq(1/ret_zi() == 1/0, true, 'fn: ret_zi (+Inf)') assert_eq(1/ret_zf() == 1/0.0, true, 'fn: ret_zf (+Inf)') assert_eq(1/ret_zfn() == -1/0.0, true, 'fn: ret_zfn (-Inf)') assert_eq(1/-(ret_zi()) == 1/0, true, 'fn unary minus: ret_zi (+Inf)') assert_eq(1/-(ret_zf()) == -1/0.0, true, 'fn unary minus: ret_zf (-Inf)') assert_eq(1/-(ret_zfn()) == 1/0.0, true, 'fn unary minus: ret_zfn (+Inf)') -- 8. Tables and arrays local t, arr = {zi=zi, zfn=zfn}, {zi, zfn} -- 8.1. Tables assert_eq(1/t.zi == 1/0, true, 'table: t.zi (+Inf)') assert_eq(1/t.zfn == -1/0.0, true, 'table: t.zfn (-Inf)') -- 8.2. Arrays assert_eq(1/arr[1] == 1/0, true, 'array: arr[1]=zi (+Inf)') assert_eq(1/arr[2] == -1/0.0, true, 'array: arr[2]=zfn (-Inf)') -- 9. Deeply nested parentheses and expressions local xi, xf = 1-1, 1.0-1.0 local deepi = -((((0+0)-(1-1))+(zi-xi))) -- int path (+0) local deepf = -((((0.0+0.0)-(1.0-1.0))+(zf-xf))) -- float path (-0.0) assert_eq(1/deepi == 1/0, true, 'nested: int (+Inf)') assert_eq(1/deepf == -1/0.0, true, 'nested: float (-Inf)') -- 10. Floor division near zero assert_eq(1/(0//1) == 1/0, true, 'floor div: int (+0)') assert_eq(1/(0.0//1.0) == 1/0.0, true, 'floor div: float (+Inf)') assert_eq(1/((-0.0)//1.0) == -1/0.0, true, 'floor div: float (-Inf)') assert_eq(1/(0//1.0) == 1/0.0, true, 'floor div: mixed (+0.0)') -- 10.1. Modulo/division identity local function id_ok(a, b) return a == b * (a // b) + a % b end assert_eq(id_ok(5, 2), true, 'identity: 5, 2') assert_eq(id_ok(-5, 2), true, 'identity: -5, 2') assert_eq(id_ok(5, -2), true, 'identity: 5, -2') assert_eq(id_ok(-5, -2), true, 'identity: -5, -2') -- 10.2. Floor division signs assert_eq(5//-2, -3, 'idiv: 5//-2 == -3') assert_eq(-5//2, -3, 'idiv: -5//2 == -3') assert_eq(-5//-2, 2, 'idiv: -5//-2 == 2') -- 11. Ordering and NaN assert_eq((-0.0) < (0.0), false, 'ordering: -0.0 < 0.0 is false') assert_eq((0.0) < (-0.0), false, 'ordering: 0.0 < -0.0 is false') assert_eq((-0.0) <= (0.0), true, 'ordering: -0.0 <= 0.0') assert_eq((0.0) <= (-0.0), true, 'ordering: 0.0 <= -0.0') assert_eq((0/0) == (0/0), false, 'NaN: never equals itself') -- 12. Bitwise operators assert_eq((~0) == -1, true, 'bitwise not on int ok') val = pcall( function() return ~0.0 end ) assert_eq(val, true, 'bitwise not on float ok') val = pcall( function() return 0<<1 end ) assert_eq(val, true, 'shl int ok') val = pcall( function() return 0.0<<1 end ) assert_eq(val, true, 'shl float ok') -- 12.1 Bitwise ops results assert_eq((5&3) == 1, true, 'bitwise and result') assert_eq((5|2) == 7, true, 'bitwise or result') assert_eq((5~1) == 4, true, 'bitwise xor result') assert_eq((1<<5) == 32, true, 'bitwise shl result') assert_eq((32>>5) == 1, true, 'bitwise shr result') -- 12.2 Bitwise with float values assert_eq((~(-0.0)) == -1, true, 'bitwise not on -0.0 == -1') -- 13. Evaluation order (left-to-right) for binary ops local log, val -- arithmetic + - * / local function lhs_num() log[#log + 1] = 'L' return 1 end local function rhs_num() log[#log + 1] = 'R' return 2 end log = {} val = lhs_num()+rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: +') log = {} val = lhs_num()-rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: -') log = {} val = lhs_num()*rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: *') log = {} val = lhs_num()/rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: /') -- floor div and mod log = {} val = lhs_num()//rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: //') log = {} val = lhs_num()%rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: %') -- power and concatenation log = {} val = lhs_num()^rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: ^') local function lhs_str() log[#log + 1] = 'L' return 'a' end local function rhs_str() log[#log + 1] = 'R' return 'b' end log = {} val = lhs_str()..rhs_str() assert_eq(table.concat(log, ''), 'LR', 'order: ..') -- relational log = {} val = (lhs_num() < rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: <') log = {} val = (lhs_num() <= rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: <=') log = {} val = (lhs_num() > rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: >') log = {} val = (lhs_num() >= rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: >=') log = {} val = (lhs_num() == rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: ==') log = {} val = (lhs_num() ~= rhs_num()) assert_eq(table.concat(log, ''), 'LR', 'order: ~=') -- bitwise log = {} val = lhs_num()&rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: &') log = {} val = lhs_num()|rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: |') log = {} val = lhs_num()~rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: ~ (xor)') log = {} val = lhs_num()<>rhs_num() assert_eq(table.concat(log, ''), 'LR', 'order: >>') -- 13.2. Nested expressions and associativity local function exp_a() log[#log + 1] = 'A' return 2 end local function exp_b() log[#log + 1] = 'B' return 3 end local function exp_c() log[#log + 1] = 'C' return 2 end log = {} val = exp_a() ^ (exp_b() ^ exp_c()) assert_eq(val, 512, 'nested expression result') assert_eq(table.concat(log, ''), 'ABC', 'nested expression associativity') assert_eq(2^3^2, 512, 'power associativity') -- 14. String-to-number coercion around zero assert_eq(1/('0') == 1/0.0, true, 'str: ("0") (+Inf)') assert_eq(1/('-0') == 1/0.0, true, 'str: ("-0") (+Inf)') assert_eq(1/('0.0') == 1/0.0, true, 'str: ("0.0") (+Inf)') assert_eq(1/('-0.0') == -1/0.0, true, 'str: ("-0.0") (-Inf)') assert_eq(1/-('0') == 1/0.0, true, 'str: unary minus -("0") (+Inf)') assert_eq(1/-('-0') == 1/0.0, true, 'str: unary minus -("-0") (+Inf)') -- 14.1. General arithmetic with numeric strings assert_eq('1'+2, 3, 'str-num: "1"+2 == 3') assert_eq(' -2 ' * '3', -6, 'str-num: " -2 " * "3" == -6') assert_eq('0x10' + 1, 17, 'str-num: hex int string + 1 == 17') assert_eq('0x1p4' + 0, 16, 'str-num: hex float string + 0 == 16') assertThrows("attempt to add a 'string' with a 'number'", function() return 'x1'+1 end ) -- 15. Recursive function producing int zero (and unary minus) local function rec_zero(n) if n == 0 then return 0 end return -rec_zero(n - 1) end assert_eq(1/rec_zero(5) == 1/0, true, 'recursive: rec_zero (+Inf)') assert_eq(1/(rec_zero(5)) == 1/0, true, 'recursive: (rec_zero) (+Inf)') assert_eq(1/-(rec_zero(5)) == 1/0, true, 'recursive: -(rec_zero) (+Inf)') assert_eq(1/-rec_zero(5) == 1/0, true, 'recursive: -(rec_zero) (+Inf)') -- 16. Modulo and integer division by zero -- 16.1. Modulo by zero assertThrows("attempt to perform 'n%0'", function() return 1%0 end ) val = pcall( function() return 1.0%0.0 end ) assert_eq(val, true, 'float mod by zero ok (NaN)') val = pcall( function() return 1.0%0 end ) assert_eq(val, true, 'mixed (float,int) mod by zero ok (NaN)') val = pcall( function() return 1%0.0 end ) assert_eq(val, true, 'mixed (int,float) mod by zero ok (NaN)') -- 16.2. Integer division by zero assertThrows('divide by zero', function() return 1//0 end ) val = pcall( function() return 1.0//0.0 end ) assert_eq(val, true, 'float idiv by zero ok (+Inf/-Inf)') val = pcall( function() return 1.0//0 end ) assert_eq(val, true, 'mixed (float,int) idiv by zero ok (+Inf/-Inf)') val = pcall( function() return 1//0.0 end ) assert_eq(val, true, 'mixed (int,float) idiv by zero ok (+Inf/-Inf)') -- 16.3. Modulo sign semantics (explicit) assert_eq(5 % -2, -1, 'mod: 5 % -2 == -1') assert_eq(-5 % 2, 1, 'mod: -5 % 2 == 1') assert_eq(-5 % -2, -1, 'mod: -5 % -2 == -1') -- 17. Metamethod precedence: __add should dispatch local mt = { __add = function(_, _) return 'added' end } local tbl = setmetatable({}, mt) assert_eq(tbl + tbl, 'added', '__add dispatched') -- 17.1 Unary metamethod: __unm (negation) local mt_unm = { __unm = function(_) return 'negated' end } local u = setmetatable({}, mt_unm) assert_eq(-u, 'negated', '__unm dispatched') -- 17.2 Unary metamethod: __bnot (bitwise NOT) local bnot_calls = 0 local mt_bnot = { __bnot = function(_) bnot_calls = bnot_calls + 1 return 123 end } local b = setmetatable({}, mt_bnot) assert_eq(~b, 123, '__bnot dispatched') assert_eq(bnot_calls, 1, '__bnot called exactly once') -- 17.3. Unary metamethods multi-return (first return only) local mt_unm_mr = { __unm = function(_) return 7, 8 end } local um = setmetatable({}, mt_unm_mr) assert_eq(-um, 7, '__unm uses first return value') local mt_bnot_mr = { __bnot = function(_) return 9, 10 end } local bm = setmetatable({}, mt_bnot_mr) assert_eq(~bm, 9, '__bnot uses first return value') -- 18. Multi-return in arithmetic (first return only) local function multi_ret() return 0, 1 end assert_eq(1/(multi_ret()) == 1/0, true, 'multi-ret: 1st used (+Inf)') assert_eq(1/-(multi_ret()) == 1/0, true, 'multi-ret: unary minus 1st used (+Inf)') -- 18.1 Exponentiation zero edge cases assert_eq(0^0 == 1, true, 'pow: 0^0 == 1') assert_eq((-0.0)^0 == 1, true, 'pow: (-0.0)^0 == 1') -- 19. Error tests assertThrows('has no integer representation', function() return ~0.5 end ) assertThrows("attempt to add a 'string' with a 'number'", function() return 'a'+1 end ) assertThrows('attempt to perform arithmetic on a table value', function() return -{} end ) -- 19.1. Bitwise on non-integers should error assertThrows('has no integer representation', function() return 1.5&1 end ) assertThrows( "attempt to perform bitwise operation on a string value (constant '3')", function() return '3'|1 end ) assertThrows('has no integer representation', function() return 1~1.2 end ) assertThrows('has no integer representation', function() return 1<<0.1 end ) -- 19.2. Relational type error assertThrows('attempt to compare number with string', function() return 1<'1' end ) -- 19.3. Additional negative tests assertThrows('attempt to perform arithmetic on a table value', function() return 1+{} end ) assertThrows("attempt to unm a 'string' with a 'string'", function() return -'x' end ) assertThrows( "attempt to perform bitwise operation on a string value (constant '1')", function() return ~'1' end ) assertThrows('attempt to compare string with number', function() return '1'<1 end ) assertThrows('attempt to compare number with table', function() return 1<{} end ) -- 19.4. String arithmetic: exact verb mapping assertThrows("attempt to sub a 'string' with a 'number'", function() return 'x' - 1 end ) assertThrows("attempt to mul a 'string' with a 'number'", function() return 'x' * 2 end ) assertThrows("attempt to div a 'string' with a 'number'", function() return 'x' / 2 end ) assertThrows("attempt to idiv a 'string' with a 'number'", function() return 'x' // 2 end ) assertThrows("attempt to mod a 'string' with a 'number'", function() return 'x' % 2 end ) assertThrows("attempt to pow a 'string' with a 'number'", function() return 'x' ^ 2 end ) -- 19.4.1. String arithmetic: type pairing and string-vs-string cases assertThrows("attempt to add a 'string' with a 'string'", function() return 'x' + 'y' end ) assertThrows("attempt to mul a 'number' with a 'string'", function() return 2 * 'x' end ) -- 19.5. Bitwise: exact type errors assertThrows("attempt to perform bitwise operation on a table value", function() return {} & 1 end ) assertThrows("attempt to perform bitwise operation on a nil value", function() return nil | 1 end ) assertThrows("attempt to perform bitwise operation on a boolean value", function() return true ~ 1 end ) -- 19.6. Bitwise shifts: non-integer RHS assertThrows("number has no integer representation", function() return 1 << 0.5 end ) assertThrows("number has no integer representation", function() return 8 >> 0.25 end ) -- 19.7. Concatenation: exact messages assertThrows("attempt to concatenate a nil value", function() return nil .. "x" end ) assertThrows("attempt to concatenate a nil value", function() return "x" .. nil end ) assertThrows("attempt to concatenate a table value", function() return "x" .. {} end ) -- 19.8. Length operator: exact message assertThrows("attempt to get length of a number value", function() return #1 end ) assertThrows("attempt to get length of a nil value", function() return #nil end ) -- 19.9. Relational mismatches assertThrows("attempt to compare number with string", function() return 1 <= '1' end ) assertThrows("attempt to compare number with string", function() return '1' >= 1 end ) assertThrows("attempt to compare table with number", function() return 1 > {} end ) assertThrows("attempt to compare table with number", function() return {} < 1 end ) -- 19.10. Explicit int-path division/modulo by zero through expr assertThrows("attempt to perform 'n%0'", function() return 1 % (1-1) end ) assertThrows("attempt to divide by zero", function() return 1 // (1-1) end )