use std::hash::Hash; use crate::{ instruction::Instruction as Inst, loader, state::{ Block, Chunk, State, }, value::Value, }; fn calculate_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 { 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 { 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 { let bc = loader::compile_bytecode (s.as_bytes ().to_vec ()); run_bytecode (args, &bc) } #[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), Inst::Return (1, 2, 1, false), Inst::Return (2, 1, 1, false), ], constants: vec! [ "arg".into (), "print".into (), ], upvalue_count: 1, }, Block { instructions: vec! [ Inst::Test (0, false), Inst::Jmp (3), Inst::LoadI (1, 99), Inst::Return1 (1), Inst::Jmp (2), Inst::LoadI (1, 98), Inst::Return1 (1), Inst::Return0, ], constants: vec! [], upvalue_count: 0, }, ], }; for (arg, expected) in [ (vec! ["_exe_name"], vec! [98.into ()]), (vec! ["_exe_name", "asdf"], vec! [99.into ()]), ] { let expected: Vec = expected; 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); } } #[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 (vec! ["_exe_name"], vec! [23.0.into ()]), (vec! ["_exe_name"], vec! [23.0.into ()]), ] { let expected: Vec = expected; 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); } } #[test] fn floats () { /* local a = 0.5 local b = 3 local x = a + b print (x) return x */ let block = Block { 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), Inst::Return (2, 2, 1, false), Inst::Return (3, 1, 1, false), ], constants: vec! [ 0.5.into (), "print".into (), ], upvalue_count: 1, }; let chunk = Chunk { blocks: vec! [block], }; for (arg, expected) in [ (vec! ["_exe_name"], vec! [3.5.into ()]), (vec! ["_exe_name", " "], vec! [3.5.into ()]), ] { let expected: Vec = expected; 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); } } #[test] fn fma () { 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 (); assert_eq! (file.blocks.len (), 4); for (arg, expected) in [ (vec! ["_exe_name"], vec! [122.into ()]), (vec! ["_exe_name"], vec! [122.into ()]), ] { let expected: Vec = expected; 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); } } #[test] fn is_93 () { 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")); let src = r#" if arg [1] == "93" then print "it's 93" return 0 else print "it's not 93" return 1 end "#; let bc = loader::compile_bytecode (src.as_bytes ().to_vec ()); let chunk = loader::parse_chunk_from_bytes (&bc).unwrap (); 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)]); } #[test] fn tables_1 () { // I don't capture stdout yet, but I can at least make sure basic table // operations don't crash let src = r#" print " 1" print (nil) print (false) print (true) print (1993) print (1993.00) print "Hello." print " 2" local t = {} print (t) print (t [1]) t [1] = "asdf" print (t [1]) t.x = 1993 print (t.x) t.t = { 3.14159 } print (t ["t"][1]) "#; run_source (&[], src); } #[test] fn tables_2 () { let src = r#" print " 3" local c = {} local a = { c } print (a [1]) c [2] = "eee" print (a [2]) "#; run_source (&[], src); } #[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 // // It would be nice if LunarWaveVM is the same or better. // There are some exploratory things in this test, too use std::mem::size_of; assert! (size_of::> () <= 8); assert! (size_of::> () <= 8); let sz = size_of:: (); let expected = 16; assert! (sz <= expected, "{sz} > {expected}"); }