2023-09-26 21:45:04 +00:00
|
|
|
use std::hash::Hash;
|
|
|
|
|
2023-09-26 20:49:12 +00:00
|
|
|
use crate::{
|
2023-09-26 21:45:04 +00:00
|
|
|
loader,
|
2023-09-26 20:49:12 +00:00
|
|
|
state::{
|
|
|
|
Block,
|
|
|
|
Chunk,
|
|
|
|
Instruction as Inst,
|
|
|
|
State,
|
|
|
|
},
|
|
|
|
value::Value,
|
2023-09-24 22:40:45 +00:00
|
|
|
};
|
|
|
|
|
2023-09-26 21:45:04 +00:00
|
|
|
fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
|
|
|
use std::hash::Hasher;
|
|
|
|
|
|
|
|
let mut s = std::collections::hash_map::DefaultHasher::new ();
|
|
|
|
t.hash(&mut s);
|
|
|
|
s.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes arguments and a parsed Lua chunk, runs its,
|
|
|
|
/// and returns the output
|
|
|
|
|
|
|
|
fn run_chunk (args: &[&str], chunk: &Chunk) -> Vec <Value> {
|
|
|
|
let mut vm = State::default ();
|
|
|
|
let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ()));
|
|
|
|
vm.execute_chunk (chunk, &upvalues)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes arguments and Lua bytecode, loads it, runs it,
|
|
|
|
/// and return the output
|
|
|
|
|
|
|
|
fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
|
|
|
|
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
|
|
|
run_chunk (args, &chunk)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Takes arguments and Lua source code,
|
|
|
|
/// invokes `luac` to compile it to bytecode,
|
|
|
|
/// runs it,
|
|
|
|
/// and returns the output
|
|
|
|
|
|
|
|
fn run_source (args: &[&str], s: &str) -> Vec <Value> {
|
|
|
|
let bc = loader::compile_bytecode (s.as_bytes ().to_vec ());
|
|
|
|
run_bytecode (args, &bc)
|
|
|
|
}
|
|
|
|
|
2023-09-25 00:47:17 +00:00
|
|
|
#[test]
|
|
|
|
fn bools () {
|
|
|
|
/*
|
|
|
|
local function bool_to_x (b)
|
|
|
|
if b then
|
|
|
|
return 99
|
|
|
|
else
|
|
|
|
return 98
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local x = bool_to_x (not not arg [1])
|
|
|
|
print (x)
|
|
|
|
return x
|
|
|
|
*/
|
|
|
|
|
|
|
|
let chunk = Chunk {
|
|
|
|
blocks: vec! [
|
|
|
|
Block {
|
|
|
|
instructions: vec! [
|
|
|
|
Inst::VarArgPrep (0),
|
|
|
|
Inst::Closure (0, 0),
|
|
|
|
Inst::Move (1, 0),
|
|
|
|
|
|
|
|
Inst::LoadFalse (2),
|
|
|
|
Inst::Call (1, 2, 1),
|
|
|
|
|
|
|
|
Inst::Move (1, 0),
|
|
|
|
Inst::LoadTrue (2),
|
|
|
|
Inst::Call (1, 2, 1),
|
|
|
|
|
|
|
|
Inst::Move (1, 0),
|
|
|
|
Inst::GetTabUp (2, 0, 0),
|
|
|
|
Inst::GetI (2, 2, 1),
|
|
|
|
Inst::Not (2, 2),
|
|
|
|
Inst::Not (2, 2),
|
|
|
|
Inst::Call (1, 2, 2),
|
|
|
|
Inst::GetTabUp (2, 0, 1),
|
|
|
|
Inst::Move (3, 1),
|
|
|
|
Inst::Call (2, 2, 1),
|
2023-09-25 01:40:28 +00:00
|
|
|
Inst::Return (1, 2, 1, false),
|
|
|
|
Inst::Return (2, 1, 1, false),
|
2023-09-25 00:47:17 +00:00
|
|
|
],
|
|
|
|
constants: vec! [
|
|
|
|
"arg".into (),
|
|
|
|
"print".into (),
|
|
|
|
],
|
2023-09-25 01:40:28 +00:00
|
|
|
upvalue_count: 1,
|
2023-09-25 00:47:17 +00:00
|
|
|
},
|
|
|
|
Block {
|
|
|
|
instructions: vec! [
|
2023-09-26 19:10:57 +00:00
|
|
|
Inst::Test (0, false),
|
2023-09-25 00:47:17 +00:00
|
|
|
Inst::Jmp (3),
|
|
|
|
Inst::LoadI (1, 99),
|
|
|
|
Inst::Return1 (1),
|
|
|
|
Inst::Jmp (2),
|
|
|
|
Inst::LoadI (1, 98),
|
|
|
|
Inst::Return1 (1),
|
|
|
|
Inst::Return0,
|
|
|
|
],
|
|
|
|
constants: vec! [],
|
2023-09-25 01:40:28 +00:00
|
|
|
upvalue_count: 0,
|
2023-09-25 00:47:17 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
|
|
|
for (arg, expected) in [
|
|
|
|
(vec! ["_exe_name"], vec! [98.into ()]),
|
|
|
|
(vec! ["_exe_name", "asdf"], vec! [99.into ()]),
|
|
|
|
] {
|
2023-09-26 20:49:12 +00:00
|
|
|
let expected: Vec <Value> = expected;
|
|
|
|
|
2023-09-25 00:47:17 +00:00
|
|
|
let mut vm = State::default ();
|
|
|
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
|
|
|
|
|
|
|
let actual = vm.execute_chunk (&chunk, &upvalues);
|
|
|
|
assert_eq! (actual, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-25 05:23:53 +00:00
|
|
|
#[test]
|
|
|
|
fn closure () {
|
|
|
|
let bytecode = include_bytes! ("../test_vectors/closure.luac");
|
|
|
|
let mut rdr = std::io::Cursor::new (bytecode);
|
|
|
|
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
|
|
|
|
|
|
|
|
for (arg, expected) in [
|
|
|
|
// Run the same test twice so clippy won't complain about a vec of 1 element
|
2023-09-26 19:23:57 +00:00
|
|
|
(vec! ["_exe_name"], vec! [23.0.into ()]),
|
|
|
|
(vec! ["_exe_name"], vec! [23.0.into ()]),
|
2023-09-25 05:23:53 +00:00
|
|
|
] {
|
2023-09-26 20:49:12 +00:00
|
|
|
let expected: Vec <Value> = expected;
|
2023-09-25 05:23:53 +00:00
|
|
|
let mut vm = State::default ();
|
|
|
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
|
|
|
let actual = vm.execute_chunk (&file, &upvalues);
|
|
|
|
|
|
|
|
assert_eq! (actual, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-24 22:40:45 +00:00
|
|
|
#[test]
|
|
|
|
fn floats () {
|
|
|
|
/*
|
|
|
|
local a = 0.5
|
|
|
|
local b = 3
|
|
|
|
|
|
|
|
local x = a + b
|
|
|
|
|
|
|
|
print (x)
|
|
|
|
return x
|
|
|
|
*/
|
|
|
|
|
2023-09-25 00:47:17 +00:00
|
|
|
let block = Block {
|
2023-09-24 22:40:45 +00:00
|
|
|
instructions: vec! [
|
|
|
|
Inst::VarArgPrep (0),
|
|
|
|
Inst::LoadK (0, 0),
|
|
|
|
Inst::LoadI (1, 3),
|
|
|
|
Inst::Add (2, 0, 1),
|
|
|
|
Inst::MmBin (0, 1, 6),
|
|
|
|
Inst::GetTabUp (3, 0, 1),
|
|
|
|
Inst::Move (4, 2),
|
|
|
|
Inst::Call (3, 2, 1),
|
2023-09-25 01:40:28 +00:00
|
|
|
Inst::Return (2, 2, 1, false),
|
|
|
|
Inst::Return (3, 1, 1, false),
|
2023-09-24 22:40:45 +00:00
|
|
|
],
|
|
|
|
constants: vec! [
|
|
|
|
0.5.into (),
|
|
|
|
"print".into (),
|
|
|
|
],
|
2023-09-25 01:40:28 +00:00
|
|
|
upvalue_count: 1,
|
2023-09-24 22:40:45 +00:00
|
|
|
};
|
2023-09-25 00:47:17 +00:00
|
|
|
let chunk = Chunk {
|
|
|
|
blocks: vec! [block],
|
|
|
|
};
|
2023-09-24 22:40:45 +00:00
|
|
|
|
|
|
|
for (arg, expected) in [
|
|
|
|
(vec! ["_exe_name"], vec! [3.5.into ()]),
|
2023-09-24 22:42:56 +00:00
|
|
|
(vec! ["_exe_name", " "], vec! [3.5.into ()]),
|
2023-09-24 22:40:45 +00:00
|
|
|
] {
|
2023-09-26 20:49:12 +00:00
|
|
|
let expected: Vec <Value> = expected;
|
2023-09-24 22:40:45 +00:00
|
|
|
let mut vm = State::default ();
|
|
|
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
|
|
|
let actual = vm.execute_chunk (&chunk, &upvalues);
|
|
|
|
|
|
|
|
assert_eq! (actual, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-25 01:40:28 +00:00
|
|
|
#[test]
|
|
|
|
fn fma () {
|
2023-09-25 05:23:53 +00:00
|
|
|
let bytecode = include_bytes! ("../test_vectors/fma.luac");
|
|
|
|
let mut rdr = std::io::Cursor::new (bytecode);
|
|
|
|
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
|
2023-09-25 06:57:57 +00:00
|
|
|
assert_eq! (file.blocks.len (), 4);
|
2023-09-25 01:40:28 +00:00
|
|
|
|
|
|
|
for (arg, expected) in [
|
|
|
|
(vec! ["_exe_name"], vec! [122.into ()]),
|
|
|
|
(vec! ["_exe_name"], vec! [122.into ()]),
|
|
|
|
] {
|
2023-09-26 20:49:12 +00:00
|
|
|
let expected: Vec <Value> = expected;
|
2023-09-25 01:40:28 +00:00
|
|
|
let mut vm = State::default ();
|
|
|
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
2023-09-25 05:23:53 +00:00
|
|
|
let actual = vm.execute_chunk (&file, &upvalues);
|
2023-09-25 01:40:28 +00:00
|
|
|
|
|
|
|
assert_eq! (actual, expected);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-24 22:40:45 +00:00
|
|
|
#[test]
|
|
|
|
fn is_93 () {
|
2023-09-26 21:45:04 +00:00
|
|
|
assert_eq! (Value::from ("93"), Value::from ("93"));
|
|
|
|
assert_ne! (Value::from ("94"), Value::from ("93"));
|
|
|
|
assert_ne! (calculate_hash (&Value::from ("94")), calculate_hash (&Value::from ("93")));
|
|
|
|
assert_ne! (Value::Nil, Value::from ("93"));
|
2023-09-26 17:04:17 +00:00
|
|
|
|
2023-09-26 21:45:04 +00:00
|
|
|
let src = r#"
|
|
|
|
if arg [1] == "93" then
|
|
|
|
print "it's 93"
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
print "it's not 93"
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
"#;
|
2023-09-26 17:04:17 +00:00
|
|
|
|
2023-09-26 21:45:04 +00:00
|
|
|
let bc = loader::compile_bytecode (src.as_bytes ().to_vec ());
|
|
|
|
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
2023-09-24 22:40:45 +00:00
|
|
|
|
2023-09-26 21:45:04 +00:00
|
|
|
assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false));
|
|
|
|
|
|
|
|
let run = run_chunk;
|
|
|
|
|
|
|
|
assert_eq! (run (&[""], &chunk), vec! [Value::from (1)]);
|
|
|
|
assert_eq! (run (&["", "93"], &chunk), vec! [Value::from (0)]);
|
|
|
|
assert_eq! (run (&["", "94"], &chunk), vec! [Value::from (1)]);
|
2023-09-25 01:40:28 +00:00
|
|
|
}
|
2023-09-26 19:00:11 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn value_size () {
|
|
|
|
// Per https://www.lua.org/doc/jucs05.pdf,
|
|
|
|
// "The Implementation of Lua 5.0",
|
|
|
|
//
|
|
|
|
// Lua's tagged union values are 12-16 bytes on a 32-bit system
|
|
|
|
// with 64-bit floats
|
|
|
|
//
|
2023-09-26 20:49:12 +00:00
|
|
|
// It would be nice if LunarWaveVM is the same or better.
|
|
|
|
// There are some exploratory things in this test, too
|
2023-09-26 19:00:11 +00:00
|
|
|
|
|
|
|
use std::{
|
|
|
|
mem::size_of,
|
|
|
|
};
|
|
|
|
|
|
|
|
assert! (size_of::<Box <()>> () <= 8);
|
|
|
|
assert! (size_of::<std::rc::Rc <()>> () <= 8);
|
|
|
|
|
2023-09-26 19:41:10 +00:00
|
|
|
let sz = size_of::<crate::value::Value> ();
|
2023-09-26 19:10:57 +00:00
|
|
|
let expected = 16;
|
|
|
|
assert! (sz <= expected, "{sz} > {expected}");
|
2023-09-26 19:00:11 +00:00
|
|
|
}
|