parent
b8dd59cd7c
commit
f9e8f26ac3
|
@ -1,6 +1,9 @@
|
|||
// cargo run -- --script lunar_wave_vm/test_vectors/fizz_buzz.lua
|
||||
|
||||
use std::io::Read;
|
||||
use std::io::{
|
||||
Read,
|
||||
Write,
|
||||
};
|
||||
|
||||
use lunar_wave_vm as lwvm;
|
||||
|
||||
|
@ -36,9 +39,8 @@ fn lunar_wave (args: Vec <String>) -> Result <Vec <lwvm::Value>, lwvm::StepError
|
|||
"-" => {
|
||||
let mut buf = vec! [];
|
||||
std::io::stdin ().read_to_end (&mut buf).unwrap ();
|
||||
let bc = lwvm::ensure_bytecode (buf);
|
||||
let mut rdr = std::io::Cursor::new (bc);
|
||||
chunk = Some (lwvm::parse_chunk (&mut rdr).unwrap ());
|
||||
let bc = lwvm::ensure_bytecode (buf).unwrap ();
|
||||
chunk = Some (lwvm::parse_chunk (&bc).unwrap ());
|
||||
|
||||
lua_args = vec! ["-".to_string ()];
|
||||
},
|
||||
|
@ -49,8 +51,7 @@ fn lunar_wave (args: Vec <String>) -> Result <Vec <lwvm::Value>, lwvm::StepError
|
|||
}
|
||||
else if chunk.is_none () {
|
||||
let bc = lwvm::compile_bytecode_from_file (x);
|
||||
let mut rdr = std::io::Cursor::new (bc);
|
||||
chunk = Some (lwvm::parse_chunk (&mut rdr).unwrap ());
|
||||
chunk = Some (lwvm::parse_chunk (&bc).unwrap ());
|
||||
|
||||
lua_args = vec! [x.to_string ()];
|
||||
}
|
||||
|
@ -61,24 +62,55 @@ fn lunar_wave (args: Vec <String>) -> Result <Vec <lwvm::Value>, lwvm::StepError
|
|||
}
|
||||
}
|
||||
|
||||
let chunk = chunk.unwrap ();
|
||||
match chunk {
|
||||
Some (chunk) => debugger (DebuggerParams {
|
||||
breakpoints,
|
||||
chunk,
|
||||
list_bytecode,
|
||||
lua_args,
|
||||
}),
|
||||
None => repl (ReplParams {
|
||||
list_bytecode,
|
||||
lua_args,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
if list_bytecode {
|
||||
dbg! (&chunk);
|
||||
struct DebuggerParams {
|
||||
breakpoints: Vec <lwvm::Breakpoint>,
|
||||
chunk: lwvm::Chunk,
|
||||
list_bytecode: bool,
|
||||
lua_args: Vec <String>,
|
||||
}
|
||||
|
||||
struct ReplParams {
|
||||
list_bytecode: bool,
|
||||
lua_args: Vec <String>,
|
||||
}
|
||||
|
||||
/// The interpreter mode, which has optional debugging abilities
|
||||
/// sort of like a cut-down gdb.
|
||||
|
||||
fn debugger (params: DebuggerParams) -> Result <Vec <lwvm::Value>, lwvm::StepError>
|
||||
{
|
||||
if params.list_bytecode {
|
||||
dbg! (¶ms.chunk);
|
||||
}
|
||||
|
||||
let upvalues = lwvm::State::upvalues_from_args (lua_args.into_iter ());
|
||||
let upvalues = lwvm::State::upvalues_from_args (params.lua_args.into_iter ());
|
||||
|
||||
let mut vm = lwvm::State::new (chunk, upvalues);
|
||||
let mut vm = lwvm::State::new (params.chunk, upvalues);
|
||||
if std::env::var("LWVM_DEBUG").is_ok() {
|
||||
vm.debug_print = true;
|
||||
}
|
||||
|
||||
// Variables for interactive debugging
|
||||
|
||||
let mut in_break = false;
|
||||
let mut last_input = String::new ();
|
||||
|
||||
loop {
|
||||
if in_break || breakpoints.iter ().any (|bp| vm.at_breakpoint (bp)) {
|
||||
if in_break || params.breakpoints.iter ().any (|bp| vm.at_breakpoint (bp)) {
|
||||
in_break = true;
|
||||
dbg! (&vm.stack);
|
||||
|
||||
|
@ -122,3 +154,52 @@ fn lunar_wave (args: Vec <String>) -> Result <Vec <lwvm::Value>, lwvm::StepError
|
|||
}
|
||||
}
|
||||
|
||||
/// A REPL that's sort of like the PUC Lua or LuaJIT REPL,
|
||||
/// but with fewer features.
|
||||
/// It still have to cheat and run `luac5.4` as a subprocess.
|
||||
|
||||
fn repl (params: ReplParams) -> Result <Vec <lwvm::Value>, lwvm::StepError>
|
||||
{
|
||||
let upvalues = lwvm::State::upvalues_from_args (params.lua_args.into_iter ());
|
||||
|
||||
let mut vm = lwvm::State::new (lwvm::Chunk::default (), upvalues);
|
||||
|
||||
println! ("Lunar Wave 0.1.0-modified Copyright (C) 2023 ReactorScram (implements Lua 5.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio");
|
||||
|
||||
loop {
|
||||
{
|
||||
let mut stdout = std::io::stdout ().lock ();
|
||||
stdout.write_all (b"> ").unwrap ();
|
||||
stdout.flush ().unwrap ();
|
||||
}
|
||||
|
||||
let mut input = Default::default ();
|
||||
std::io::stdin ().read_line (&mut input).unwrap ();
|
||||
if input.is_empty () {
|
||||
println! ();
|
||||
return Ok (vec! []);
|
||||
}
|
||||
|
||||
let bytecode = match lwvm::compile_bytecode (input.into_bytes ()) {
|
||||
Ok (x) => x,
|
||||
Err (e) => {
|
||||
eprintln! ("Compile error from luac subprocess:");
|
||||
eprintln! ("{}", e);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let chunk = lwvm::parse_chunk (&bytecode).unwrap ();
|
||||
|
||||
if params.list_bytecode {
|
||||
dbg! (&chunk);
|
||||
}
|
||||
|
||||
vm.set_chunk (chunk);
|
||||
match vm.execute () {
|
||||
Ok (x) => if ! x.is_empty () {
|
||||
println! ("{x:?}")
|
||||
},
|
||||
Err (e) => println! ("{e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ pub enum Instruction {
|
|||
|
||||
Call (u8, u8, u8),
|
||||
Closure (u8, u32),
|
||||
Concat (u8, u8),
|
||||
|
||||
Div (u8, u8, u8),
|
||||
|
||||
|
@ -84,7 +85,7 @@ pub enum Instruction {
|
|||
|
||||
SetList (u8, u8, u8, bool),
|
||||
|
||||
SetTabUp (u8, u8, u8),
|
||||
SetTabUp (u8, u8, u8, bool),
|
||||
|
||||
Sub (u8, u8, u8),
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ pub use loader::compile_bytecode_from_file as compile_bytecode_from_file;
|
|||
pub use loader::compile_bytecode as compile_bytecode;
|
||||
pub use loader::ensure_bytecode as ensure_bytecode;
|
||||
pub use loader::parse_chunk as parse_chunk;
|
||||
pub use loader::parse_chunk_from_reader as parse_chunk_from_reader;
|
||||
pub use state::Breakpoint as Breakpoint;
|
||||
pub use state::Chunk as Chunk;
|
||||
pub use state::State as State;
|
||||
pub use state::StepError as StepError;
|
||||
pub use state::StepOutput as StepOutput;
|
||||
|
|
|
@ -34,7 +34,7 @@ pub fn compile_bytecode_from_file (path: &str) -> Vec <u8> {
|
|||
///
|
||||
/// `source` is a Vec because we move it to a worker thread
|
||||
|
||||
pub fn compile_bytecode (source: Vec <u8>) -> Vec <u8> {
|
||||
pub fn compile_bytecode (source: Vec <u8>) -> Result <Vec <u8>, String> {
|
||||
use std::{
|
||||
io::Write,
|
||||
process::{
|
||||
|
@ -48,6 +48,7 @@ pub fn compile_bytecode (source: Vec <u8>) -> Vec <u8> {
|
|||
.arg ("-") // Standard output
|
||||
.arg ("-") // Input from standard input
|
||||
.stdin (Stdio::piped ())
|
||||
.stderr (Stdio::piped ())
|
||||
.stdout (Stdio::piped ())
|
||||
.spawn ()
|
||||
.expect ("failed to execute `luac5.4`. Is Lua installed?");
|
||||
|
@ -61,17 +62,23 @@ pub fn compile_bytecode (source: Vec <u8>) -> Vec <u8> {
|
|||
.wait_with_output ()
|
||||
.expect ("failed to wait on child");
|
||||
|
||||
output.stdout.as_slice ().to_vec ()
|
||||
if output.status.success () && output.status.code () == Some (0)
|
||||
{
|
||||
Ok (output.stdout)
|
||||
}
|
||||
else {
|
||||
Err (String::from_utf8 (output.stderr).unwrap ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the input is already bytecode, or is possibly
|
||||
/// Lua source code. If it's source code, compiles and returns bytecode.
|
||||
/// If it's bytecode, just returns the input.
|
||||
|
||||
pub fn ensure_bytecode (buffer: Vec <u8>) -> Vec <u8> {
|
||||
pub fn ensure_bytecode (buffer: Vec <u8>) -> Result <Vec <u8>, String> {
|
||||
let bytecode_header = &[0x1b, 0x4c, 0x75, 0x61, 0x54, 0x00, 0x19, 0x93];
|
||||
if buffer.starts_with (bytecode_header) {
|
||||
return buffer;
|
||||
return Ok (buffer);
|
||||
}
|
||||
|
||||
compile_bytecode (buffer)
|
||||
|
@ -116,7 +123,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x0c => Inst::GetTable (a, b, c),
|
||||
0x0d => Inst::GetI (a, b, c),
|
||||
0x0e => Inst::GetField (a, b, c),
|
||||
0x0f => Inst::SetTabUp (a, b, c),
|
||||
0x0f => Inst::SetTabUp (a, b, c, k),
|
||||
0x11 => Inst::SetI (a, b, c, k),
|
||||
0x12 => Inst::SetField (a, b, c, k),
|
||||
0x13 => Inst::NewTable (a),
|
||||
|
@ -133,6 +140,7 @@ pub fn parse_inst (buf: [u8; 4]) -> Option <Inst>
|
|||
0x31 => Inst::UnM (a, b),
|
||||
0x33 => Inst::Not (a, b),
|
||||
0x34 => Inst::Len (a, b),
|
||||
0x35 => Inst::Concat (a, b),
|
||||
0x3c => Inst::EqK (a, b, k),
|
||||
0x3d => Inst::EqI (a, i_sb (buf)?, k),
|
||||
0x38 => Inst::Jmp (s_j),
|
||||
|
@ -333,8 +341,12 @@ pub fn parse_block <R: Read> (rdr: &mut R, blocks: &mut Vec <Block>)
|
|||
Some (())
|
||||
}
|
||||
|
||||
pub fn parse_chunk (buf: &[u8]) -> Option <Chunk> {
|
||||
let mut rdr = std::io::Cursor::new (buf);
|
||||
parse_chunk_from_reader (&mut rdr)
|
||||
}
|
||||
|
||||
pub fn parse_chunk <R: Read> (rdr: &mut R) -> Option <Chunk> {
|
||||
pub fn parse_chunk_from_reader <R: Read> (rdr: &mut R) -> Option <Chunk> {
|
||||
// Discard 32 bytes from the start of the file.
|
||||
// This is magic number, version number, etc.
|
||||
|
||||
|
@ -352,11 +364,6 @@ pub fn parse_chunk <R: Read> (rdr: &mut R) -> Option <Chunk> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_chunk_from_bytes (b: &[u8]) -> Option <Chunk> {
|
||||
let mut rdr = std::io::Cursor::new (b);
|
||||
parse_chunk (&mut rdr)
|
||||
}
|
||||
|
||||
#[cfg (test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
@ -447,8 +454,7 @@ mod tests {
|
|||
}
|
||||
|
||||
if false {
|
||||
let mut rdr = std::io::Cursor::new (bytecode.clone ());
|
||||
let file = crate::loader::parse_chunk (&mut rdr).unwrap ();
|
||||
let file = crate::loader::parse_chunk (bytecode).unwrap ();
|
||||
|
||||
assert_eq! (file.blocks.len (), 5);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,12 @@ pub struct Block {
|
|||
pub upvalues: Vec <Upvalue>,
|
||||
}
|
||||
|
||||
#[derive (Clone, Debug)]
|
||||
#[derive (Clone, Debug, Default)]
|
||||
pub struct Chunk {
|
||||
pub blocks: Vec <Block>,
|
||||
}
|
||||
|
||||
#[derive (Clone, Debug)]
|
||||
#[derive (Clone, Debug, Default)]
|
||||
pub struct StackFrame {
|
||||
// i32 makes it a little easier to implement jumps
|
||||
// Starts at 0 right after OP_CALL
|
||||
|
@ -148,11 +148,7 @@ impl State {
|
|||
registers: vec! [Value::Nil; 256],
|
||||
top: 0,
|
||||
stack: vec! [
|
||||
StackFrame {
|
||||
program_counter: 0,
|
||||
block_idx: 0,
|
||||
register_offset: 0,
|
||||
},
|
||||
StackFrame::default (),
|
||||
],
|
||||
debug_print: false,
|
||||
step_count: 0,
|
||||
|
@ -386,6 +382,9 @@ impl State {
|
|||
upvalues: new_upvalues,
|
||||
});
|
||||
},
|
||||
Instruction::Concat (a, b) => {
|
||||
unimplemented! ("OP_CONCAT")
|
||||
},
|
||||
Instruction::Div (a, b, c) => {
|
||||
let v_b = self.reg (b);
|
||||
let v_c = self.reg (c);
|
||||
|
@ -760,7 +759,22 @@ impl State {
|
|||
dst.insert_int (i64::from (c + i), src.clone ());
|
||||
}
|
||||
},
|
||||
Instruction::SetTabUp (_a, _b, _c) => unimplemented! (),
|
||||
Instruction::SetTabUp (a, b, c, k_flag) => {
|
||||
let a = usize::try_from (a).unwrap ();
|
||||
let b = usize::try_from (b).unwrap ();
|
||||
|
||||
let value = if k_flag {
|
||||
&k [usize::from (c)]
|
||||
}
|
||||
else {
|
||||
self.reg (c)
|
||||
}
|
||||
.clone ();
|
||||
|
||||
let table = self.upvalues.get_mut (a).unwrap ().as_table ().unwrap ();
|
||||
let key = k.get (b).unwrap ().as_str ().expect ("SetTabUp K[B] must be a string");
|
||||
table.borrow_mut ().insert_str (key, value);
|
||||
},
|
||||
Instruction::Sub (a, b, c) => {
|
||||
let v_b = self.reg (b);
|
||||
let v_c = self.reg (c);
|
||||
|
@ -886,15 +900,11 @@ impl State {
|
|||
Ok (None)
|
||||
}
|
||||
|
||||
pub fn execute_chunk (&mut self, breakpoints: &[Breakpoint])
|
||||
pub fn execute (&mut self)
|
||||
-> Result <Vec <Value>, StepError> {
|
||||
let max_iters = 2000;
|
||||
|
||||
for _ in 0..max_iters {
|
||||
if breakpoints.iter ().any (|bp| self.at_breakpoint (bp)) {
|
||||
dbg! (&self);
|
||||
}
|
||||
|
||||
match self.step ()? {
|
||||
None => (),
|
||||
Some (StepOutput::ChunkReturned (x)) => return Ok (x),
|
||||
|
@ -904,4 +914,9 @@ impl State {
|
|||
dbg! (self);
|
||||
panic! ("Hit max iterations before block returned");
|
||||
}
|
||||
|
||||
pub fn set_chunk (&mut self, chunk: Chunk) {
|
||||
self.stack = vec! [Default::default ()];
|
||||
self.chunk = chunk;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,14 @@ fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
|||
fn run_chunk (args: &[&str], chunk: Chunk) -> Vec <Value> {
|
||||
let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ()));
|
||||
let mut vm = State::new (chunk, upvalues);
|
||||
vm.execute_chunk (&[]).unwrap ()
|
||||
vm.execute ().unwrap ()
|
||||
}
|
||||
|
||||
/// 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 ();
|
||||
let chunk = loader::parse_chunk (&bc).unwrap ();
|
||||
run_chunk (args, chunk)
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
|
|||
/// and returns the output
|
||||
|
||||
fn run_source (args: &[&str], s: &str) -> Vec <Value> {
|
||||
let bc = loader::compile_bytecode (s.as_bytes ().to_vec ());
|
||||
let bc = loader::compile_bytecode (s.as_bytes ().to_vec ()).unwrap ();
|
||||
run_bytecode (args, &bc)
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ fn bools () {
|
|||
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let mut vm = State::new (chunk.clone (), upvalues);
|
||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||
let actual = vm.execute ().unwrap ();
|
||||
assert_eq! (actual, expected);
|
||||
}
|
||||
}
|
||||
|
@ -128,8 +128,8 @@ fn bools () {
|
|||
#[test]
|
||||
fn closure () {
|
||||
let source = include_bytes! ("../test_vectors/closure.lua");
|
||||
let bytecode = &crate::loader::compile_bytecode (source.to_vec ());
|
||||
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
||||
let bytecode = &crate::loader::compile_bytecode (source.to_vec ()).unwrap ();
|
||||
let chunk = crate::loader::parse_chunk (bytecode).unwrap ();
|
||||
|
||||
assert_eq! (run_chunk (&["_exe_name"], chunk), vec! [Value::from (23i64)]);
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ fn floats () {
|
|||
let expected: Vec <Value> = expected;
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let mut vm = State::new (chunk.clone (), upvalues);
|
||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||
let actual = vm.execute ().unwrap ();
|
||||
|
||||
assert_eq! (actual, expected);
|
||||
}
|
||||
|
@ -185,8 +185,8 @@ fn floats () {
|
|||
#[test]
|
||||
fn fma () {
|
||||
let source = include_bytes! ("../test_vectors/fma.lua");
|
||||
let bytecode = &crate::loader::compile_bytecode (source.to_vec ());
|
||||
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
||||
let bytecode = &crate::loader::compile_bytecode (source.to_vec ()).unwrap ();
|
||||
let chunk = crate::loader::parse_chunk (bytecode).unwrap ();
|
||||
assert_eq! (chunk.blocks.len (), 5);
|
||||
|
||||
assert_eq! (chunk.blocks [3].upvalues.len (), 2);
|
||||
|
@ -194,12 +194,43 @@ fn fma () {
|
|||
let arg = vec! ["_exe_name"];
|
||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||
let mut vm = State::new (chunk, upvalues);
|
||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||
let actual = vm.execute ().unwrap ();
|
||||
let expected = vec! [Value::from (122)];
|
||||
|
||||
assert_eq! (actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_calls () {
|
||||
let upvalues = crate::State::upvalues_from_args (vec! ["_exe_name".to_string ()].into_iter ());
|
||||
|
||||
let mut vm = crate::State::new (crate::Chunk::default (), upvalues);
|
||||
|
||||
{
|
||||
let bc = crate::compile_bytecode (b"print (x ())".to_vec ()).unwrap ();
|
||||
let chunk = crate::parse_chunk (&bc).unwrap ();
|
||||
|
||||
vm.set_chunk (chunk);
|
||||
vm.execute ().unwrap ();
|
||||
}
|
||||
|
||||
{
|
||||
let bc = crate::compile_bytecode (b"x = function () return 5 end".to_vec ()).unwrap ();
|
||||
let chunk = crate::parse_chunk (&bc).unwrap ();
|
||||
|
||||
vm.set_chunk (chunk);
|
||||
vm.execute ().unwrap ();
|
||||
}
|
||||
|
||||
{
|
||||
let bc = crate::compile_bytecode (b"print (x ())".to_vec ()).unwrap ();
|
||||
let chunk = crate::parse_chunk (&bc).unwrap ();
|
||||
|
||||
vm.set_chunk (chunk);
|
||||
vm.execute ().unwrap ();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn heap () {
|
||||
use std::{
|
||||
|
@ -265,7 +296,7 @@ fn is_93 () {
|
|||
assert_ne! (calculate_hash (&Value::from ("94")), calculate_hash (&Value::from ("93")));
|
||||
assert_ne! (Value::Nil, Value::from ("93"));
|
||||
|
||||
let src = r#"
|
||||
let src = br#"
|
||||
if arg [1] == "93" then
|
||||
print "it's 93"
|
||||
return 0
|
||||
|
@ -275,8 +306,8 @@ fn is_93 () {
|
|||
end
|
||||
"#;
|
||||
|
||||
let bc = loader::compile_bytecode (src.as_bytes ().to_vec ());
|
||||
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
||||
let bc = loader::compile_bytecode (src.to_vec ()).unwrap ();
|
||||
let chunk = loader::parse_chunk (&bc).unwrap ();
|
||||
|
||||
assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false));
|
||||
|
||||
|
@ -359,12 +390,12 @@ fn tables_2 () {
|
|||
fn tailcall () {
|
||||
use crate::instruction::Instruction;
|
||||
|
||||
let src = r#"
|
||||
let src = br#"
|
||||
return tonumber ("5")
|
||||
"#;
|
||||
|
||||
let bc = loader::compile_bytecode (src.as_bytes ().to_vec ());
|
||||
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
||||
let bc = loader::compile_bytecode (src.to_vec ()).unwrap ();
|
||||
let chunk = loader::parse_chunk (&bc).unwrap ();
|
||||
|
||||
assert_eq! (chunk.blocks [0].instructions [3], Instruction::TailCall (0, 2, 1, false));
|
||||
|
||||
|
|
|
@ -19,9 +19,8 @@ fn embedding () {
|
|||
1
|
||||
}
|
||||
|
||||
let bytecode = lwvm::compile_bytecode (src.to_vec ());
|
||||
let mut rdr = std::io::Cursor::new (bytecode);
|
||||
let chunk = lwvm::parse_chunk (&mut rdr).unwrap ();
|
||||
let bc = lwvm::compile_bytecode (src.to_vec ()).unwrap ();
|
||||
let chunk = lwvm::parse_chunk (&bc).unwrap ();
|
||||
|
||||
let host_lib = [
|
||||
("add", Value::RsFunc (host_add)),
|
||||
|
@ -36,7 +35,7 @@ fn embedding () {
|
|||
];
|
||||
|
||||
let mut vm = State::new (chunk, upvalues);
|
||||
let output = vm.execute_chunk (&vec! []).unwrap ();
|
||||
let output = vm.execute ().unwrap ();
|
||||
|
||||
assert_eq! (output, vec! [Value::from (2019)]);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue