use omnilua::{Lua, LuaVersion};
fn run(version: LuaVersion, code: &str) -> Result<String, String> {
let lua = Lua::new_versioned(version);
let wrapper = format!(
"local f, e = load([==[\n{code}\n]==])\n\
if not f then return 'E\\0' .. e end\n\
local ok, r = pcall(f)\n\
if not ok then return 'E\\0' .. tostring(r) end\n\
return 'V\\0' .. tostring(r)"
);
let out: String = lua
.load(&wrapper)
.eval()
.unwrap_or_else(|e| panic!("harness failure for `{code}`: {e:?}"));
if let Some(v) = out.strip_prefix("V\0") {
Ok(v.to_string())
} else if let Some(e) = out.strip_prefix("E\0") {
Err(e.to_string())
} else {
panic!("harness: unexpected output `{out}` for `{code}`")
}
}
fn eq(version: LuaVersion, code: &str, expected: &str) {
match run(version, code) {
Ok(got) => assert_eq!(got, expected, "code: {code}"),
Err(e) => panic!("code `{code}` errored (`{e}`), expected `{expected}`"),
}
}
fn err_contains(version: LuaVersion, code: &str, needle: &str) {
match run(version, code) {
Ok(got) => {
panic!("code `{code}` returned `{got}`, expected error containing `{needle}`")
}
Err(e) => assert!(e.contains(needle), "code `{code}` error `{e}` lacked `{needle}`"),
}
}
const V52: LuaVersion = LuaVersion::V52;
#[test]
fn fold_ops_basic_and_identities() {
eq(V52, "return bit32.band(6, 3)", "2");
eq(V52, "return bit32.bor(1, 2, 4)", "7");
eq(V52, "return bit32.bxor(0xF, 0x3)", "12");
eq(V52, "return bit32.bnot(0)", "4294967295");
eq(V52, "return bit32.bnot(0xFFFFFFFF)", "0");
eq(V52, "return bit32.band()", "4294967295");
eq(V52, "return bit32.bor()", "0");
eq(V52, "return bit32.bxor()", "0");
}
#[test]
fn fold_ops_mod_2_32_and_negative_inputs() {
eq(V52, "return bit32.band(0x1FFFFFFFF)", "4294967295");
eq(V52, "return bit32.bor(0x1FFFFFFFF, 0)", "4294967295");
eq(V52, "return bit32.band(-1)", "4294967295");
eq(V52, "return bit32.bor(-1, 0)", "4294967295");
eq(V52, "return bit32.bnot(-1)", "0");
eq(V52, "return bit32.bxor(-2, 0)", "4294967294");
}
#[test]
fn btest_truth_table() {
eq(V52, "return bit32.btest(6, 3)", "true");
eq(V52, "return bit32.btest(4, 3)", "false");
eq(V52, "return bit32.btest(0)", "false");
eq(V52, "return bit32.btest()", "true");
}
#[test]
fn lshift_rshift_logical() {
eq(V52, "return bit32.lshift(1, 4)", "16");
eq(V52, "return bit32.lshift(1, 31)", "2147483648");
eq(V52, "return bit32.lshift(0xFFFFFFFF, 4)", "4294967280");
eq(V52, "return bit32.rshift(0x80000000, 4)", "134217728");
eq(V52, "return bit32.rshift(1, 1)", "0");
}
#[test]
fn lshift_rshift_displacement_bounds() {
eq(V52, "return bit32.lshift(1, 32)", "0");
eq(V52, "return bit32.rshift(1, 32)", "0");
eq(V52, "return bit32.lshift(1, 100)", "0");
eq(V52, "return bit32.rshift(0xFFFFFFFF, 100)", "0");
eq(V52, "return bit32.lshift(1, -1)", "0");
eq(V52, "return bit32.rshift(1, -1)", "2");
}
#[test]
fn arshift_sign_propagation() {
eq(V52, "return bit32.arshift(-8, 1)", "4294967292");
eq(V52, "return bit32.arshift(0x80000000, 1)", "3221225472");
eq(V52, "return bit32.arshift(0x40000000, 1)", "536870912");
eq(V52, "return bit32.arshift(0x80000000, 32)", "4294967295");
eq(V52, "return bit32.arshift(0x80000000, 100)", "4294967295");
eq(V52, "return bit32.arshift(0x80000000, -1)", "0");
eq(V52, "return bit32.arshift(0x40000000, -1)", "2147483648");
}
#[test]
fn rotate_ops() {
eq(V52, "return bit32.lrotate(1, 1)", "2");
eq(V52, "return bit32.rrotate(1, 1)", "2147483648");
eq(V52, "return bit32.lrotate(0x80000000, 1)", "1");
eq(V52, "return bit32.lrotate(0x12345678, 4)", "591751041");
eq(V52, "return bit32.rrotate(0x12345678, 4)", "2166572391");
}
#[test]
fn rotate_displacement_is_mod_32() {
eq(V52, "return bit32.lrotate(0xDEADBEEF, 0)", "3735928559");
eq(V52, "return bit32.lrotate(0xDEADBEEF, 32)", "3735928559");
eq(V52, "return bit32.lrotate(0xDEADBEEF, 36)", "3940282109");
eq(V52, "return bit32.lrotate(0xDEADBEEF, -36)", "4260027374");
eq(V52, "return bit32.lrotate(1, -1)", "2147483648");
}
#[test]
fn extract_values() {
eq(V52, "return bit32.extract(0xF0, 4, 4)", "15");
eq(V52, "return bit32.extract(0x12345678, 0, 32)", "305419896");
eq(V52, "return bit32.extract(0xFFFFFFFF, 31, 1)", "1");
eq(V52, "return bit32.extract(0xFF, 0)", "1");
eq(V52, "return bit32.extract(5, 0)", "1");
eq(V52, "return bit32.extract(5, 0, 1)", "1");
}
#[test]
fn extract_bounds_errors() {
err_contains(V52, "return bit32.extract(0, -1)", "field cannot be negative");
err_contains(V52, "return bit32.extract(0, 0, 0)", "width must be positive");
err_contains(V52, "return bit32.extract(0, 0, -1)", "width must be positive");
err_contains(
V52,
"return bit32.extract(0, 32, 1)",
"trying to access non-existent bits",
);
err_contains(
V52,
"return bit32.extract(0, 0, 33)",
"trying to access non-existent bits",
);
err_contains(
V52,
"return bit32.extract(0, 30, 4)",
"trying to access non-existent bits",
);
}
#[test]
fn replace_values() {
eq(V52, "return bit32.replace(0, 5, 0, 4)", "5");
eq(V52, "return bit32.replace(0xFFFFFFFF, 0, 0, 4)", "4294967280");
eq(V52, "return bit32.replace(0, 0xFF, 0, 4)", "15");
eq(V52, "return bit32.replace(0, 1, 31)", "2147483648");
eq(V52, "return bit32.replace(0x12345678, 0xAB, 8, 8)", "305441656");
}
#[test]
fn replace_bounds_errors() {
err_contains(V52, "return bit32.replace(0, 1, -1)", "field cannot be negative");
err_contains(V52, "return bit32.replace(0, 1, 0, 0)", "width must be positive");
err_contains(
V52,
"return bit32.replace(0, 1, 0, 33)",
"trying to access non-existent bits",
);
err_contains(
V52,
"return bit32.replace(0, 1, 32, 1)",
"trying to access non-existent bits",
);
}
#[test]
fn non_number_argument_errors() {
err_contains(V52, "return bit32.band('x')", "number expected, got string");
err_contains(V52, "return bit32.band(nil)", "number expected, got nil");
err_contains(V52, "return bit32.band({})", "number expected, got table");
err_contains(V52, "return bit32.extract(0, 'x')", "number expected, got string");
}
#[test]
fn v52_fractional_float_args_round_ties_even() {
eq(V52, "return bit32.band(1.5)", "2");
eq(V52, "return bit32.band(2.5)", "2");
eq(V52, "return bit32.band(3.5)", "4");
eq(V52, "return bit32.band(0.5)", "0");
eq(V52, "return bit32.band(0.4)", "0");
eq(V52, "return bit32.band(0.6)", "1");
eq(V52, "return bit32.band(2.49)", "2");
eq(V52, "return bit32.band(2.51)", "3");
eq(V52, "return bit32.band(-1.5)", "4294967294");
eq(V52, "return bit32.band(-2.5)", "4294967294");
eq(V52, "return bit32.band(-0.5)", "0");
eq(V52, "return bit32.band(3.0)", "3");
eq(V52, "return bit32.band(2^31)", "2147483648");
eq(V52, "return bit32.band(2^32)", "0");
eq(V52, "return bit32.band(4294967296.0)", "0");
}
#[test]
fn v52_fractional_count_args_truncate_toward_zero() {
eq(V52, "return bit32.lshift(1, 1.5)", "2");
eq(V52, "return bit32.lshift(1, 2.5)", "4");
eq(V52, "return bit32.rshift(256, 2.5)", "64");
eq(V52, "return bit32.lshift(256, -1.5)", "128");
eq(V52, "return bit32.rshift(256, -1.5)", "512");
eq(V52, "return bit32.lrotate(1, -1.5)", "2147483648");
eq(V52, "return bit32.lrotate(1, -0.5)", "1");
eq(V52, "return bit32.extract(0xAA, 1.5)", "1");
eq(V52, "return bit32.extract(0xAA, 2.5)", "0");
eq(V52, "return bit32.extract(0xFF, 1.5, 2)", "3");
}
#[test]
fn v53_fractional_float_args_are_rejected() {
err_contains(
LuaVersion::V53,
"return bit32.band(1.5)",
"number has no integer representation",
);
err_contains(
LuaVersion::V53,
"return bit32.lshift(1, 1.5)",
"number has no integer representation",
);
err_contains(
LuaVersion::V53,
"return bit32.extract(0xFF, 1.5)",
"number has no integer representation",
);
eq(LuaVersion::V53, "return bit32.band(3.0)", "3");
eq(LuaVersion::V53, "return bit32.lshift(1, 4.0)", "16");
}