Compare commits
4 Commits
51b04be1ab
...
b8dd59cd7c
Author | SHA1 | Date |
---|---|---|
_ | b8dd59cd7c | |
_ | db84365c27 | |
_ | 700b273a11 | |
_ | ffb1950f80 |
|
@ -3,60 +3,12 @@
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "lunar_wave_cli"
|
||||||
version = "0.3.7"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake3"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayref",
|
"lunar_wave_vm",
|
||||||
"arrayvec",
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"constant_time_eq",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.0.83"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "constant_time_eq"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libc"
|
|
||||||
version = "0.2.148"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lunar_wave_vm"
|
name = "lunar_wave_vm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"blake3",
|
|
||||||
]
|
|
||||||
|
|
27
Cargo.toml
27
Cargo.toml
|
@ -1,21 +1,6 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "lunar_wave_vm"
|
resolver = "2"
|
||||||
description = "A Lua virtual machine implementation"
|
members = [
|
||||||
version = "0.1.0"
|
"lunar_wave_cli",
|
||||||
edition = "2021"
|
"lunar_wave_vm",
|
||||||
|
]
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
|
|
||||||
# blake3, used to hash test vectors
|
|
||||||
blake3 = "1.5.0"
|
|
||||||
|
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "/usr/bin/clang"
|
|
||||||
# Recommended for flamegraph
|
|
||||||
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
# Recommended for profiling, e.g. flamegraph
|
|
||||||
debug = true
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "lunar_wave_cli"
|
||||||
|
description = "A Lua CLI implementation"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["ReactorScram"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lunar_wave_vm = { path = "../lunar_wave_vm" }
|
||||||
|
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
linker = "/usr/bin/clang"
|
||||||
|
# Recommended for flamegraph
|
||||||
|
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Recommended for profiling, e.g. flamegraph
|
||||||
|
debug = true
|
|
@ -0,0 +1,124 @@
|
||||||
|
// cargo run -- --script lunar_wave_vm/test_vectors/fizz_buzz.lua
|
||||||
|
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use lunar_wave_vm as lwvm;
|
||||||
|
|
||||||
|
fn main () -> Result <(), lwvm::StepError> {
|
||||||
|
let args: Vec <_> = std::env::args ().collect ();
|
||||||
|
lunar_wave (args)?;
|
||||||
|
Ok (())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lunar_wave (args: Vec <String>) -> Result <Vec <lwvm::Value>, lwvm::StepError> {
|
||||||
|
let mut list_bytecode = false;
|
||||||
|
let mut breakpoints = vec![];
|
||||||
|
let mut chunk = None;
|
||||||
|
let mut lua_args = vec! [];
|
||||||
|
|
||||||
|
let mut arg_iter = args.iter ();
|
||||||
|
let _exe_name = arg_iter.next ().unwrap ();
|
||||||
|
|
||||||
|
while let Some (arg) = arg_iter.next () {
|
||||||
|
match arg.as_str () {
|
||||||
|
"--break" => {
|
||||||
|
let s = arg_iter.next ().unwrap ();
|
||||||
|
let (block_idx, program_counter) = s.split_once (":").unwrap ();
|
||||||
|
let block_idx = str::parse (block_idx).unwrap ();
|
||||||
|
let program_counter = str::parse (program_counter).unwrap ();
|
||||||
|
|
||||||
|
breakpoints.push (lwvm::Breakpoint {
|
||||||
|
block_idx,
|
||||||
|
program_counter,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"--list-bytecode" => list_bytecode = true,
|
||||||
|
"-" => {
|
||||||
|
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 ());
|
||||||
|
|
||||||
|
lua_args = vec! ["-".to_string ()];
|
||||||
|
},
|
||||||
|
"--" => break,
|
||||||
|
x => {
|
||||||
|
if x.starts_with ('-') {
|
||||||
|
panic! ("Unknown flag `{x}`");
|
||||||
|
}
|
||||||
|
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 ());
|
||||||
|
|
||||||
|
lua_args = vec! [x.to_string ()];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lua_args.push (x.into ());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunk = chunk.unwrap ();
|
||||||
|
|
||||||
|
if list_bytecode {
|
||||||
|
dbg! (&chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
let upvalues = lwvm::State::upvalues_from_args (lua_args.into_iter ());
|
||||||
|
|
||||||
|
let mut vm = lwvm::State::new (chunk, upvalues);
|
||||||
|
if std::env::var("LWVM_DEBUG").is_ok() {
|
||||||
|
vm.debug_print = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut in_break = false;
|
||||||
|
let mut last_input = String::new ();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if in_break || breakpoints.iter ().any (|bp| vm.at_breakpoint (bp)) {
|
||||||
|
in_break = true;
|
||||||
|
dbg! (&vm.stack);
|
||||||
|
|
||||||
|
let mut input = Default::default ();
|
||||||
|
std::io::stdin ().read_line (&mut input).unwrap ();
|
||||||
|
|
||||||
|
let input = if input == "" {
|
||||||
|
&last_input
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
last_input = input;
|
||||||
|
&last_input
|
||||||
|
};
|
||||||
|
|
||||||
|
match input.as_str ().trim_end () {
|
||||||
|
"c" => in_break = false,
|
||||||
|
"q" => return Ok (vec! []),
|
||||||
|
"registers" => {
|
||||||
|
dbg! (&vm.registers);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
"s" => {
|
||||||
|
match vm.step ()? {
|
||||||
|
None => (),
|
||||||
|
Some (lwvm::StepOutput::ChunkReturned (x)) => {
|
||||||
|
return Ok (x);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
x => { dbg! (x); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match vm.step ()? {
|
||||||
|
None => (),
|
||||||
|
Some (lwvm::StepOutput::ChunkReturned (x)) => {
|
||||||
|
return Ok (x);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "lunar_wave_vm"
|
||||||
|
description = "A Lua virtual machine implementation"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["ReactorScram"]
|
|
@ -0,0 +1,17 @@
|
||||||
|
mod instruction;
|
||||||
|
mod loader;
|
||||||
|
mod state;
|
||||||
|
mod value;
|
||||||
|
|
||||||
|
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 state::Breakpoint as Breakpoint;
|
||||||
|
pub use state::State as State;
|
||||||
|
pub use state::StepError as StepError;
|
||||||
|
pub use state::StepOutput as StepOutput;
|
||||||
|
pub use value::Value as Value;
|
||||||
|
|
||||||
|
#[cfg (test)]
|
||||||
|
mod tests;
|
|
@ -8,12 +8,10 @@ use crate::{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub (crate) fn compile_bytecode_from_file (path: &str) -> Vec <u8> {
|
pub fn compile_bytecode_from_file (path: &str) -> Vec <u8> {
|
||||||
use std::{
|
use std::process::{
|
||||||
process::{
|
|
||||||
Command,
|
Command,
|
||||||
Stdio,
|
Stdio,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let child = Command::new ("luac5.4")
|
let child = Command::new ("luac5.4")
|
||||||
|
@ -36,7 +34,7 @@ pub (crate) fn compile_bytecode_from_file (path: &str) -> Vec <u8> {
|
||||||
///
|
///
|
||||||
/// `source` is a Vec because we move it to a worker thread
|
/// `source` is a Vec because we move it to a worker thread
|
||||||
|
|
||||||
pub (crate) fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> {
|
pub fn compile_bytecode (source: Vec <u8>) -> Vec <u8> {
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
process::{
|
process::{
|
||||||
|
@ -66,6 +64,19 @@ pub (crate) fn compile_bytecode_from_stdin (source: Vec <u8>) -> Vec <u8> {
|
||||||
output.stdout.as_slice ().to_vec ()
|
output.stdout.as_slice ().to_vec ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
let bytecode_header = &[0x1b, 0x4c, 0x75, 0x61, 0x54, 0x00, 0x19, 0x93];
|
||||||
|
if buffer.starts_with (bytecode_header) {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_bytecode (buffer)
|
||||||
|
}
|
||||||
|
|
||||||
fn i_sb (buf: [u8; 4]) -> Option <i8> {
|
fn i_sb (buf: [u8; 4]) -> Option <i8> {
|
||||||
let b = buf [2];
|
let b = buf [2];
|
||||||
i8::try_from (i32::try_from (b).ok ()? - 127).ok ()
|
i8::try_from (i32::try_from (b).ok ()? - 127).ok ()
|
|
@ -1,5 +1,3 @@
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
instruction::Instruction,
|
instruction::Instruction,
|
||||||
value::{
|
value::{
|
||||||
|
@ -8,21 +6,21 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive (Debug)]
|
#[derive (Clone, Debug)]
|
||||||
pub struct Upvalue {
|
pub struct Upvalue {
|
||||||
pub in_stack: bool,
|
pub in_stack: bool,
|
||||||
pub idx: u8,
|
pub idx: u8,
|
||||||
pub kind: u8,
|
pub kind: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Debug)]
|
#[derive (Clone, Debug)]
|
||||||
pub struct Block {
|
pub struct Block {
|
||||||
pub instructions: Vec <Instruction>,
|
pub instructions: Vec <Instruction>,
|
||||||
pub constants: Vec <Value>,
|
pub constants: Vec <Value>,
|
||||||
pub upvalues: Vec <Upvalue>,
|
pub upvalues: Vec <Upvalue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Debug)]
|
#[derive (Clone, Debug)]
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pub blocks: Vec <Block>,
|
pub blocks: Vec <Block>,
|
||||||
}
|
}
|
||||||
|
@ -47,7 +45,7 @@ pub struct Breakpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Debug)]
|
#[derive (Debug)]
|
||||||
pub struct State <'a> {
|
pub struct State {
|
||||||
pub registers: Vec <Value>,
|
pub registers: Vec <Value>,
|
||||||
// Currently only used for native function calls
|
// Currently only used for native function calls
|
||||||
top: usize,
|
top: usize,
|
||||||
|
@ -55,8 +53,8 @@ pub struct State <'a> {
|
||||||
|
|
||||||
pub debug_print: bool,
|
pub debug_print: bool,
|
||||||
step_count: u32,
|
step_count: u32,
|
||||||
chunk: &'a Chunk,
|
chunk: Chunk,
|
||||||
upvalues: &'a [Value],
|
upvalues: Vec <Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lw_io_write (l: &mut State, num_args: usize) -> usize {
|
fn lw_io_write (l: &mut State, num_args: usize) -> usize {
|
||||||
|
@ -142,8 +140,8 @@ pub struct StepError {
|
||||||
msg: &'static str,
|
msg: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a> State <'a> {
|
impl State {
|
||||||
pub fn new (chunk: &'a Chunk, upvalues: &'a [Value]) -> Self {
|
pub fn new (chunk: Chunk, upvalues: Vec <Value>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
// TODO: Stack is actually supposed to grow to a limit of
|
// TODO: Stack is actually supposed to grow to a limit of
|
||||||
// idk 10,000. I thought it was fixed at 256.
|
// idk 10,000. I thought it was fixed at 256.
|
||||||
|
@ -206,12 +204,12 @@ impl <'a> State <'a> {
|
||||||
|
|
||||||
/// Short form to get access to a register within our window
|
/// Short form to get access to a register within our window
|
||||||
|
|
||||||
fn reg (&self, i: u8) -> &Value {
|
pub fn reg (&self, i: u8) -> &Value {
|
||||||
let frame = self.stack.last ().unwrap ();
|
let frame = self.stack.last ().unwrap ();
|
||||||
&self.registers [frame.register_offset + i as usize]
|
&self.registers [frame.register_offset + i as usize]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reg_mut (&mut self, i: u8) -> &mut Value {
|
pub fn reg_mut (&mut self, i: u8) -> &mut Value {
|
||||||
let frame = self.stack.last ().unwrap ();
|
let frame = self.stack.last ().unwrap ();
|
||||||
&mut self.registers [frame.register_offset + i as usize]
|
&mut self.registers [frame.register_offset + i as usize]
|
||||||
}
|
}
|
||||||
|
@ -232,11 +230,10 @@ impl <'a> State <'a> {
|
||||||
|
|
||||||
pub fn step (&mut self) -> Result <Option <StepOutput>, StepError>
|
pub fn step (&mut self) -> Result <Option <StepOutput>, StepError>
|
||||||
{
|
{
|
||||||
let chunk = self.chunk;
|
|
||||||
self.step_count += 1;
|
self.step_count += 1;
|
||||||
|
|
||||||
let frame = self.stack.last_mut ().unwrap ().clone ();
|
let frame = self.stack.last_mut ().unwrap ().clone ();
|
||||||
let block = chunk.blocks.get (frame.block_idx).unwrap ();
|
let block = self.chunk.blocks.get (frame.block_idx).unwrap ();
|
||||||
|
|
||||||
let mut next_pc = frame.program_counter;
|
let mut next_pc = frame.program_counter;
|
||||||
|
|
||||||
|
@ -256,10 +253,10 @@ impl <'a> State <'a> {
|
||||||
self.make_step_error (msg, instruction)
|
self.make_step_error (msg, instruction)
|
||||||
};
|
};
|
||||||
|
|
||||||
match instruction {
|
match instruction.clone () {
|
||||||
Instruction::Add (a, b, c) => {
|
Instruction::Add (a, b, c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
let v_c = self.reg (*c);
|
let v_c = self.reg (c);
|
||||||
|
|
||||||
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
||||||
{
|
{
|
||||||
|
@ -271,24 +268,24 @@ impl <'a> State <'a> {
|
||||||
Value::from (v_b + v_c)
|
Value::from (v_b + v_c)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::AddI (a, b, s_c) => {
|
Instruction::AddI (a, b, s_c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
|
|
||||||
let x = if let Some (v_b) = v_b.as_int ()
|
let x = if let Some (v_b) = v_b.as_int ()
|
||||||
{
|
{
|
||||||
Value::from (v_b + *s_c as i64)
|
Value::from (v_b + s_c as i64)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let v_b = v_b.as_float ().unwrap_or_else (|| panic! ("{v_b}"));
|
let v_b = v_b.as_float ().unwrap_or_else (|| panic! ("{v_b}"));
|
||||||
Value::from (v_b + f64::from (*s_c))
|
Value::from (v_b + f64::from (s_c))
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::Call (a, b, c) => {
|
Instruction::Call (a, b, c) => {
|
||||||
let b = usize::from (*b);
|
let b = usize::from (b);
|
||||||
|
|
||||||
// Take arguments from registers [a + 1, a + b)
|
// Take arguments from registers [a + 1, a + b)
|
||||||
// Call the function in register [a]
|
// Call the function in register [a]
|
||||||
|
@ -301,7 +298,7 @@ impl <'a> State <'a> {
|
||||||
// Do a clone here to avoid a borow problem.
|
// Do a clone here to avoid a borow problem.
|
||||||
// Should be fixable with more clever code.
|
// Should be fixable with more clever code.
|
||||||
|
|
||||||
let v_a = self.reg (*a).clone ();
|
let v_a = self.reg (a).clone ();
|
||||||
|
|
||||||
match v_a {
|
match v_a {
|
||||||
Value::BogusClosure (rc) => {
|
Value::BogusClosure (rc) => {
|
||||||
|
@ -315,7 +312,7 @@ impl <'a> State <'a> {
|
||||||
self.stack.push (StackFrame {
|
self.stack.push (StackFrame {
|
||||||
program_counter: 0,
|
program_counter: 0,
|
||||||
block_idx: target_block,
|
block_idx: target_block,
|
||||||
register_offset: current_frame.register_offset + *a as usize + 1,
|
register_offset: current_frame.register_offset + a as usize + 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.debug_print {
|
if self.debug_print {
|
||||||
|
@ -329,23 +326,25 @@ impl <'a> State <'a> {
|
||||||
},
|
},
|
||||||
Value::RsFunc (x) => {
|
Value::RsFunc (x) => {
|
||||||
let current_frame = self.stack.last ().unwrap ();
|
let current_frame = self.stack.last ().unwrap ();
|
||||||
let new_offset = current_frame.register_offset + usize::from (*a) + 1;
|
let new_offset = current_frame.register_offset + usize::from (a) + 1;
|
||||||
|
|
||||||
|
// Trash the stack frame so it doesn't point to a
|
||||||
|
// valid Lua function
|
||||||
self.stack.push (StackFrame {
|
self.stack.push (StackFrame {
|
||||||
program_counter: 65535, // Bogus for native functions
|
program_counter: 65535, // Bogus for native functions
|
||||||
block_idx: 65535, // Bogus
|
block_idx: 65535, // Bogus
|
||||||
register_offset: new_offset,
|
register_offset: new_offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
// No clue what the '1' is doing here
|
let num_args = if b == 0 {
|
||||||
let b = if b == 0 {
|
self.top - a as usize
|
||||||
self.top - *a as usize
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
b
|
b - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call
|
// Call
|
||||||
let num_results = x (self, b - 1);
|
let num_results = x (self, num_args);
|
||||||
|
|
||||||
let popped_frame = self.stack.pop ().unwrap ();
|
let popped_frame = self.stack.pop ().unwrap ();
|
||||||
let offset = popped_frame.register_offset - 1;
|
let offset = popped_frame.register_offset - 1;
|
||||||
|
@ -355,7 +354,7 @@ impl <'a> State <'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up top for the next call
|
// Set up top for the next call
|
||||||
if *c == 0 {
|
if c == 0 {
|
||||||
self.top = popped_frame.register_offset - 1 + num_results;
|
self.top = popped_frame.register_offset - 1 + num_results;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -365,10 +364,10 @@ impl <'a> State <'a> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::Closure (a, b) => {
|
Instruction::Closure (a, b) => {
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (b).unwrap ();
|
||||||
|
|
||||||
let idx = frame.block_idx + b + 1;
|
let idx = frame.block_idx + b + 1;
|
||||||
let block = &chunk.blocks [idx];
|
let block = &self.chunk.blocks [idx];
|
||||||
|
|
||||||
let mut new_upvalues = Vec::with_capacity (block.upvalues.len ());
|
let mut new_upvalues = Vec::with_capacity (block.upvalues.len ());
|
||||||
for uv in &block.upvalues {
|
for uv in &block.upvalues {
|
||||||
|
@ -382,31 +381,31 @@ impl <'a> State <'a> {
|
||||||
new_upvalues.push (val);
|
new_upvalues.push (val);
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.reg_mut (*a) = Value::from (BogusClosure {
|
*self.reg_mut (a) = Value::from (BogusClosure {
|
||||||
idx,
|
idx,
|
||||||
upvalues: new_upvalues,
|
upvalues: new_upvalues,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
Instruction::Div (a, b, c) => {
|
Instruction::Div (a, b, c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
let v_c = self.reg (*c);
|
let v_c = self.reg (c);
|
||||||
|
|
||||||
let v_b = v_b.as_float ().unwrap_or_else (|| panic! ("{v_b}"));
|
let v_b = v_b.as_float ().unwrap_or_else (|| panic! ("{v_b}"));
|
||||||
|
|
||||||
let v_c = v_c.as_float ().ok_or_else (|| make_step_error ("C must be a number"))?;
|
let v_c = v_c.as_float ().ok_or_else (|| make_step_error ("C must be a number"))?;
|
||||||
|
|
||||||
*self.reg_mut (*a) = Value::from (v_b / v_c);
|
*self.reg_mut (a) = Value::from (v_b / v_c);
|
||||||
},
|
},
|
||||||
Instruction::EqI (a, sb, k_flag) => {
|
Instruction::EqI (a, sb, k_flag) => {
|
||||||
if (self.reg (*a).as_int ().unwrap () == *sb as i64) != *k_flag
|
if (self.reg (a).as_int ().unwrap () == sb as i64) != k_flag
|
||||||
{
|
{
|
||||||
next_pc += 1;
|
next_pc += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::EqK (a, b, k_flag) => {
|
Instruction::EqK (a, b, k_flag) => {
|
||||||
let b = usize::from (*b);
|
let b = usize::from (b);
|
||||||
|
|
||||||
if (*self.reg (*a) == k [b]) != *k_flag {
|
if (*self.reg (a) == k [b]) != k_flag {
|
||||||
next_pc += 1;
|
next_pc += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -414,60 +413,60 @@ impl <'a> State <'a> {
|
||||||
// This is used for NewTable. Maybe it's for reserving
|
// This is used for NewTable. Maybe it's for reserving
|
||||||
// capacity in the array or something?
|
// capacity in the array or something?
|
||||||
|
|
||||||
assert_eq! (*ax, 0, "implemented only for ax == 0");
|
assert_eq! (ax, 0, "implemented only for ax == 0");
|
||||||
},
|
},
|
||||||
Instruction::ForLoop (a, bx) => {
|
Instruction::ForLoop (a, bx) => {
|
||||||
let mut iter = self.reg (*a + 3).as_int ().unwrap ();
|
let mut iter = self.reg (a + 3).as_int ().unwrap ();
|
||||||
iter += 1;
|
iter += 1;
|
||||||
*self.reg_mut (*a + 3) = iter.into ();
|
*self.reg_mut (a + 3) = iter.into ();
|
||||||
|
|
||||||
let stop = self.reg (*a + 1).as_int ().unwrap ();
|
let stop = self.reg (a + 1).as_int ().unwrap ();
|
||||||
|
|
||||||
if iter <= stop {
|
if iter <= stop {
|
||||||
next_pc -= i32::try_from (*bx).unwrap ();
|
next_pc -= i32::try_from (bx).unwrap ();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::ForPrep (a, bx) => {
|
Instruction::ForPrep (a, bx) => {
|
||||||
let start = self.reg (*a).as_int ().unwrap ();
|
let start = self.reg (a).as_int ().unwrap ();
|
||||||
let stop = self.reg (*a + 1).as_int ().unwrap ();
|
let stop = self.reg (a + 1).as_int ().unwrap ();
|
||||||
|
|
||||||
if start > stop {
|
if start > stop {
|
||||||
next_pc += i32::try_from (*bx).unwrap () + 1;
|
next_pc += i32::try_from (bx).unwrap () + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
*self.reg_mut (*a + 3) = start.into ();
|
*self.reg_mut (a + 3) = start.into ();
|
||||||
},
|
},
|
||||||
Instruction::GetField (a, b, c) => {
|
Instruction::GetField (a, b, c) => {
|
||||||
let t = match self.reg (*b) {
|
let t = match self.reg (b) {
|
||||||
Value::Nil => Err (make_step_error ("R[B] must not be nil"))?,
|
Value::Nil => Err (make_step_error ("R[B] must not be nil"))?,
|
||||||
Value::Table (t) => t,
|
Value::Table (t) => t,
|
||||||
_ => Err (make_step_error ("R[B] must be a table"))?,
|
_ => Err (make_step_error ("R[B] must be a table"))?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = match &k [usize::from (*c)] {
|
let key = match &k [usize::from (c)] {
|
||||||
Value::String (s) => s,
|
Value::String (s) => s,
|
||||||
_ => panic! ("K[C] must be a string"),
|
_ => panic! ("K[C] must be a string"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let val = t.borrow ().get_str (key.as_str ()).clone ();
|
let val = t.borrow ().get_str (key.as_str ()).clone ();
|
||||||
|
|
||||||
*self.reg_mut (*a) = val;
|
*self.reg_mut (a) = val;
|
||||||
},
|
},
|
||||||
Instruction::GetTable (a, b, c) => {
|
Instruction::GetTable (a, b, c) => {
|
||||||
let t = match self.reg (*b) {
|
let t = match self.reg (b) {
|
||||||
Value::Table (t) => t,
|
Value::Table (t) => t,
|
||||||
_ => panic! ("R[B] must be a table"),
|
_ => panic! ("R[B] must be a table"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let key = self.reg (*c);
|
let key = self.reg (c);
|
||||||
|
|
||||||
let val = t.borrow ().get (key.clone ());
|
let val = t.borrow ().get (key.clone ());
|
||||||
|
|
||||||
*self.reg_mut (*a) = val;
|
*self.reg_mut (a) = val;
|
||||||
},
|
},
|
||||||
Instruction::GetTabUp (a, b, c) => {
|
Instruction::GetTabUp (a, b, c) => {
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (b).unwrap ();
|
||||||
let c = usize::try_from (*c).unwrap ();
|
let c = usize::try_from (c).unwrap ();
|
||||||
|
|
||||||
// If we're inside a closure, use its upvalues
|
// If we're inside a closure, use its upvalues
|
||||||
// instead of the chunk's upvalues
|
// instead of the chunk's upvalues
|
||||||
|
@ -493,17 +492,17 @@ impl <'a> State <'a> {
|
||||||
_ => panic! ("GetTabUp only supports string keys"),
|
_ => panic! ("GetTabUp only supports string keys"),
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = table.get (key);
|
*self.reg_mut (a) = table.get (key);
|
||||||
},
|
},
|
||||||
Instruction::GetI (a, b, c) => {
|
Instruction::GetI (a, b, c) => {
|
||||||
let key = i64::try_from (*c).unwrap ();
|
let key = i64::try_from (c).unwrap ();
|
||||||
|
|
||||||
let value = {
|
let value = {
|
||||||
let table = self.reg (*b).as_table ().expect ("GetI only works on tables").borrow ();
|
let table = self.reg (b).as_table ().expect ("GetI only works on tables").borrow ();
|
||||||
table.get_int (key)
|
table.get_int (key)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = value;
|
*self.reg_mut (a) = value;
|
||||||
},
|
},
|
||||||
Instruction::GetUpVal (a, b) => {
|
Instruction::GetUpVal (a, b) => {
|
||||||
let this_func = self.stack.last ().unwrap ().register_offset - 1;
|
let this_func = self.stack.last ().unwrap ().register_offset - 1;
|
||||||
|
@ -512,20 +511,20 @@ impl <'a> State <'a> {
|
||||||
_ => panic! ("Can't do GetUpVal outside a closure"),
|
_ => panic! ("Can't do GetUpVal outside a closure"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (b).unwrap ();
|
||||||
|
|
||||||
let upvalue = match closure.borrow ().upvalues.get (b) {
|
let upvalue = match closure.borrow ().upvalues.get (b) {
|
||||||
Some (x) => x.clone (),
|
Some (x) => x.clone (),
|
||||||
None => {
|
None => {
|
||||||
dbg! (chunk, &self);
|
dbg! (&self);
|
||||||
panic! ("Missing upvalue");
|
panic! ("Missing upvalue");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*self.reg_mut (*a) = upvalue;
|
*self.reg_mut (a) = upvalue;
|
||||||
},
|
},
|
||||||
Instruction::Jmp (s_j) => next_pc += s_j,
|
Instruction::Jmp (s_j) => next_pc += s_j,
|
||||||
Instruction::Len (a, b) => {
|
Instruction::Len (a, b) => {
|
||||||
let len = match self.reg (*b) {
|
let len = match self.reg (b) {
|
||||||
Value::BogusClosure (_) => Err (make_step_error ("attempt to get length of a function value"))?,
|
Value::BogusClosure (_) => Err (make_step_error ("attempt to get length of a function value"))?,
|
||||||
Value::Boolean (_) => Err (make_step_error ("attempt to get length of a boolean value"))?,
|
Value::Boolean (_) => Err (make_step_error ("attempt to get length of a boolean value"))?,
|
||||||
Value::Float (_) => Err (make_step_error ("attempt to get length of a number value"))?,
|
Value::Float (_) => Err (make_step_error ("attempt to get length of a number value"))?,
|
||||||
|
@ -536,31 +535,31 @@ impl <'a> State <'a> {
|
||||||
Value::Table (t) => t.borrow ().length ().into (),
|
Value::Table (t) => t.borrow ().length ().into (),
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = len;
|
*self.reg_mut (a) = len;
|
||||||
}
|
}
|
||||||
Instruction::LoadF (a, sbx) => {
|
Instruction::LoadF (a, sbx) => {
|
||||||
*self.reg_mut (*a) = Value::Float (*sbx as f64);
|
*self.reg_mut (a) = Value::Float (sbx as f64);
|
||||||
}
|
}
|
||||||
Instruction::LoadFalse (a) => {
|
Instruction::LoadFalse (a) => {
|
||||||
*self.reg_mut (*a) = false.into ();
|
*self.reg_mut (a) = false.into ();
|
||||||
},
|
},
|
||||||
Instruction::LoadI (a, sbx) => {
|
Instruction::LoadI (a, sbx) => {
|
||||||
*self.reg_mut (*a) = Value::Integer (*sbx as i64);
|
*self.reg_mut (a) = Value::Integer (sbx as i64);
|
||||||
},
|
},
|
||||||
Instruction::LoadK (a, bx) => {
|
Instruction::LoadK (a, bx) => {
|
||||||
let bx = usize::try_from (*bx).unwrap ();
|
let bx = usize::try_from (bx).unwrap ();
|
||||||
|
|
||||||
*self.reg_mut (*a) = k [bx].clone ();
|
*self.reg_mut (a) = k [bx].clone ();
|
||||||
},
|
},
|
||||||
Instruction::LoadNil (a) => {
|
Instruction::LoadNil (a) => {
|
||||||
*self.reg_mut (*a) = Value::Nil;
|
*self.reg_mut (a) = Value::Nil;
|
||||||
},
|
},
|
||||||
Instruction::LoadTrue (a) => {
|
Instruction::LoadTrue (a) => {
|
||||||
*self.reg_mut (*a) = true.into ();
|
*self.reg_mut (a) = true.into ();
|
||||||
},
|
},
|
||||||
Instruction::MmBin (a, b, _c) => {
|
Instruction::MmBin (a, b, _c) => {
|
||||||
let a = self.reg (*a);
|
let a = self.reg (a);
|
||||||
let b = self.reg (*b);
|
let b = self.reg (b);
|
||||||
|
|
||||||
if a.as_float().is_some() && b.as_float().is_some () {
|
if a.as_float().is_some() && b.as_float().is_some () {
|
||||||
// No need for metamethods
|
// No need for metamethods
|
||||||
|
@ -576,20 +575,20 @@ impl <'a> State <'a> {
|
||||||
// Ignore
|
// Ignore
|
||||||
},
|
},
|
||||||
Instruction::ModK (a, b, c) => {
|
Instruction::ModK (a, b, c) => {
|
||||||
let b = self.reg (*b).as_int().unwrap ();
|
let b = self.reg (b).as_int().unwrap ();
|
||||||
let c = k [usize::from (*c)].as_int ().unwrap ();
|
let c = k [usize::from (c)].as_int ().unwrap ();
|
||||||
|
|
||||||
*self.reg_mut (*a) = (b % c).into ();
|
*self.reg_mut (a) = (b % c).into ();
|
||||||
},
|
},
|
||||||
Instruction::Move (a, b) => {
|
Instruction::Move (a, b) => {
|
||||||
// If the value in b is deleted instead of duplicated,
|
// If the value in b is deleted instead of duplicated,
|
||||||
// a bunch of tests fail
|
// a bunch of tests fail
|
||||||
|
|
||||||
*self.reg_mut (*a) = self.reg (*b).clone ();
|
*self.reg_mut (a) = self.reg (b).clone ();
|
||||||
},
|
},
|
||||||
Instruction::Mul (a, b, c) => {
|
Instruction::Mul (a, b, c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
let v_c = self.reg (*c);
|
let v_c = self.reg (c);
|
||||||
|
|
||||||
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
||||||
{
|
{
|
||||||
|
@ -601,11 +600,11 @@ impl <'a> State <'a> {
|
||||||
Value::from (v_b * v_c)
|
Value::from (v_b * v_c)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::MulK (a, b, c) => {
|
Instruction::MulK (a, b, c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
let v_c = &k [usize::from (*c)];
|
let v_c = &k [usize::from (c)];
|
||||||
|
|
||||||
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
||||||
{
|
{
|
||||||
|
@ -617,30 +616,30 @@ impl <'a> State <'a> {
|
||||||
Value::from (v_b * v_c)
|
Value::from (v_b * v_c)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::NewTable (a) => {
|
Instruction::NewTable (a) => {
|
||||||
*self.reg_mut (*a) = Value::Table (Default::default ());
|
*self.reg_mut (a) = Value::Table (Default::default ());
|
||||||
},
|
},
|
||||||
Instruction::Not (a, b) => {
|
Instruction::Not (a, b) => {
|
||||||
*self.reg_mut (*a) = Value::Boolean (! self.reg (*b).is_truthy());
|
*self.reg_mut (a) = Value::Boolean (! self.reg (b).is_truthy());
|
||||||
}
|
}
|
||||||
Instruction::Return (a, b, _c, k) => {
|
Instruction::Return (a, b, _c, k_flag) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (a).unwrap ();
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (b).unwrap ();
|
||||||
|
|
||||||
let popped_frame = self.stack.pop ().unwrap ();
|
let popped_frame = self.stack.pop ().unwrap ();
|
||||||
|
|
||||||
// Build closure if needed. No point building if we're
|
// Build closure if needed. No point building if we're
|
||||||
// popping the last frame and exiting the program.
|
// popping the last frame and exiting the program.
|
||||||
|
|
||||||
if *k && ! self.stack.is_empty () {
|
if k_flag && ! self.stack.is_empty () {
|
||||||
let closure_idx = match &self.registers [popped_frame.register_offset + a] {
|
let closure_idx = match &self.registers [popped_frame.register_offset + a] {
|
||||||
Value::BogusClosure (rc) => rc.borrow ().idx,
|
Value::BogusClosure (rc) => rc.borrow ().idx,
|
||||||
_ => panic! ("Impossible"),
|
_ => panic! ("Impossible"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let upvalue_count = chunk.blocks [closure_idx].upvalues.len ();
|
let upvalue_count = self.chunk.blocks [closure_idx].upvalues.len ();
|
||||||
|
|
||||||
let start_reg = a + popped_frame.register_offset - upvalue_count;
|
let start_reg = a + popped_frame.register_offset - upvalue_count;
|
||||||
|
|
||||||
|
@ -678,7 +677,7 @@ impl <'a> State <'a> {
|
||||||
self.top = popped_frame.register_offset - 1 + b - 1;
|
self.top = popped_frame.register_offset - 1 + b - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Return from the entire program
|
// Return from the entire chunk
|
||||||
return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + b - 1)].to_vec())));
|
return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + b - 1)].to_vec())));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -688,7 +687,7 @@ impl <'a> State <'a> {
|
||||||
self.top = popped_frame.register_offset - 1 + 0;
|
self.top = popped_frame.register_offset - 1 + 0;
|
||||||
},
|
},
|
||||||
Instruction::Return1 (a) => {
|
Instruction::Return1 (a) => {
|
||||||
let a = usize::try_from (*a).unwrap ();
|
let a = usize::try_from (a).unwrap ();
|
||||||
let popped_frame = self.stack.pop ().unwrap ();
|
let popped_frame = self.stack.pop ().unwrap ();
|
||||||
|
|
||||||
|
|
||||||
|
@ -713,58 +712,58 @@ impl <'a> State <'a> {
|
||||||
self.top = popped_frame.register_offset - 1 + 1;
|
self.top = popped_frame.register_offset - 1 + 1;
|
||||||
},
|
},
|
||||||
Instruction::SetField (a, b, c, k_flag) => {
|
Instruction::SetField (a, b, c, k_flag) => {
|
||||||
let value = if *k_flag {
|
let value = if k_flag {
|
||||||
&k [usize::from (*c)]
|
&k [usize::from (c)]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.reg (*c)
|
self.reg (c)
|
||||||
}
|
}
|
||||||
.clone ();
|
.clone ();
|
||||||
|
|
||||||
let b = usize::try_from (*b).unwrap ();
|
let b = usize::try_from (b).unwrap ();
|
||||||
|
|
||||||
let key = match k.get (b).unwrap () {
|
let key = match k.get (b).unwrap () {
|
||||||
Value::String (s) => s.as_ref (),
|
Value::String (s) => s.as_ref (),
|
||||||
_ => panic! ("SetField only supports string keys"),
|
_ => panic! ("SetField only supports string keys"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut dst = self.reg (*a).as_table ()
|
let mut dst = self.reg (a).as_table ()
|
||||||
.expect ("SetField only works on tables").borrow_mut ();
|
.expect ("SetField only works on tables").borrow_mut ();
|
||||||
|
|
||||||
dst.insert_str (key.as_str (), value);
|
dst.insert_str (key.as_str (), value);
|
||||||
},
|
},
|
||||||
Instruction::SetI (a, b, c, k_flag) => {
|
Instruction::SetI (a, b, c, k_flag) => {
|
||||||
let value = if *k_flag {
|
let value = if k_flag {
|
||||||
&k [usize::from (*c)]
|
&k [usize::from (c)]
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
self.reg (*c)
|
self.reg (c)
|
||||||
}
|
}
|
||||||
.clone ();
|
.clone ();
|
||||||
|
|
||||||
let mut dst = self.reg_mut (*a).as_table ().expect ("SetI only works on tables").borrow_mut ();
|
let mut dst = self.reg_mut (a).as_table ().expect ("SetI only works on tables").borrow_mut ();
|
||||||
|
|
||||||
dst.insert_int (i64::from (*b), value);
|
dst.insert_int (i64::from (b), value);
|
||||||
},
|
},
|
||||||
Instruction::SetList (a, b, c, k) => {
|
Instruction::SetList (a, b, c, k_flag) => {
|
||||||
if *b == 0 {
|
if b == 0 {
|
||||||
panic! ("SetList with b == 0 not implemented");
|
panic! ("SetList with b == 0 not implemented");
|
||||||
}
|
}
|
||||||
if *k {
|
if k_flag {
|
||||||
panic! ("SetList with k = true not implemented");
|
panic! ("SetList with k = true not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dst = self.reg (*a).as_table ().expect ("SetList only works on tables").borrow_mut ();
|
let mut dst = self.reg (a).as_table ().expect ("SetList only works on tables").borrow_mut ();
|
||||||
|
|
||||||
for i in 1..=*b {
|
for i in 1..=b {
|
||||||
let src = self.reg (*a + i);
|
let src = self.reg (a + i);
|
||||||
dst.insert_int (i64::from (*c + i), src.clone ());
|
dst.insert_int (i64::from (c + i), src.clone ());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::SetTabUp (_a, _b, _c) => unimplemented! (),
|
Instruction::SetTabUp (_a, _b, _c) => unimplemented! (),
|
||||||
Instruction::Sub (a, b, c) => {
|
Instruction::Sub (a, b, c) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
let v_c = self.reg (*c);
|
let v_c = self.reg (c);
|
||||||
|
|
||||||
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
let x = if let (Some (v_b), Some (v_c)) = (v_b.as_int (), v_c.as_int ())
|
||||||
{
|
{
|
||||||
|
@ -776,28 +775,32 @@ impl <'a> State <'a> {
|
||||||
Value::from (v_b - v_c)
|
Value::from (v_b - v_c)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::TailCall (a, b, _c, k) => {
|
Instruction::TailCall (a, b, c, k) => {
|
||||||
|
let a = usize::from (a);
|
||||||
assert! (!k, "closing over values in tail calls not implemented");
|
assert! (!k, "closing over values in tail calls not implemented");
|
||||||
|
|
||||||
// Shift closure and inputs into place
|
let offset = frame.register_offset;
|
||||||
let a = usize::from (*a);
|
let value = self.registers [offset + a].take ();
|
||||||
let b = usize::from (*b);
|
match value {
|
||||||
let offset = frame.register_offset - 1;
|
Value::BogusClosure (closure) => {
|
||||||
for i in (offset)..(offset + b) {
|
let closure = closure.borrow ();
|
||||||
self.registers [i] = self.registers [i + a + 1].take ();
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = &self.registers [offset];
|
// Shift inputs into place
|
||||||
let closure = if let Some (x) = value.as_closure () {
|
|
||||||
x
|
let b = usize::from (b);
|
||||||
|
|
||||||
|
let num_args = if b == 0 {
|
||||||
|
self.top - a
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
dbg! (self);
|
b - 1
|
||||||
panic! ("OP_TAILCALL only implemented for closures");
|
|
||||||
};
|
};
|
||||||
let closure = closure.borrow ();
|
|
||||||
|
for i in (offset)..(offset + num_args) {
|
||||||
|
self.registers [i] = self.registers [i + a + 1].take ();
|
||||||
|
}
|
||||||
|
|
||||||
// Jump into the other function
|
// Jump into the other function
|
||||||
|
|
||||||
|
@ -808,13 +811,57 @@ impl <'a> State <'a> {
|
||||||
// Skip the PC increment
|
// Skip the PC increment
|
||||||
return Ok (None);
|
return Ok (None);
|
||||||
},
|
},
|
||||||
Instruction::Test (a, k) => {
|
Value::RsFunc (x) => {
|
||||||
if self.reg (*a).is_truthy() != *k {
|
// Shift inputs into place
|
||||||
|
let b = usize::from (b);
|
||||||
|
for i in (offset)..(offset + b) {
|
||||||
|
self.registers [i] = self.registers [i + a + 1].take ();
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = self.stack.last_mut ().unwrap ();
|
||||||
|
// Trash the stack frame so it doesn't point
|
||||||
|
// to any valid Lua function
|
||||||
|
*frame = StackFrame {
|
||||||
|
block_idx: 65535,
|
||||||
|
program_counter: 65535,
|
||||||
|
register_offset: offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
let num_args = if b == 0 {
|
||||||
|
self.top - a
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call
|
||||||
|
let num_results = x (self, num_args);
|
||||||
|
let popped_frame = self.stack.pop ().unwrap ();
|
||||||
|
|
||||||
|
if self.stack.is_empty () {
|
||||||
|
// The whole chunk is exiting
|
||||||
|
return Ok (Some (StepOutput::ChunkReturned (self.registers [a..(a + num_results)].to_vec())));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Set up top for the next call
|
||||||
|
if c == 0 {
|
||||||
|
self.top = popped_frame.register_offset - 1 + num_results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
dbg! (&self.stack);
|
||||||
|
panic! ("OP_TAILCALL argument must be a function");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Test (a, k_flag) => {
|
||||||
|
if self.reg (a).is_truthy() != k_flag {
|
||||||
next_pc += 1;
|
next_pc += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Instruction::UnM (a, b) => {
|
Instruction::UnM (a, b) => {
|
||||||
let v_b = self.reg (*b);
|
let v_b = self.reg (b);
|
||||||
|
|
||||||
let x = if let Some (v_b) = v_b.as_int ()
|
let x = if let Some (v_b) = v_b.as_int ()
|
||||||
{
|
{
|
||||||
|
@ -825,7 +872,7 @@ impl <'a> State <'a> {
|
||||||
Value::from (-v_b)
|
Value::from (-v_b)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.reg_mut (*a) = x;
|
*self.reg_mut (a) = x;
|
||||||
},
|
},
|
||||||
Instruction::VarArgPrep (_) => (),
|
Instruction::VarArgPrep (_) => (),
|
||||||
}
|
}
|
|
@ -22,9 +22,9 @@ fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
||||||
/// Takes arguments and a parsed Lua chunk, runs its,
|
/// Takes arguments and a parsed Lua chunk, runs its,
|
||||||
/// and returns the output
|
/// and returns the output
|
||||||
|
|
||||||
fn run_chunk (args: &[&str], chunk: &Chunk) -> Vec <Value> {
|
fn run_chunk (args: &[&str], chunk: Chunk) -> Vec <Value> {
|
||||||
let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ()));
|
let upvalues = State::upvalues_from_args (args.into_iter ().map (|s| s.to_string ()));
|
||||||
let mut vm = State::new (chunk, &upvalues);
|
let mut vm = State::new (chunk, upvalues);
|
||||||
vm.execute_chunk (&[]).unwrap ()
|
vm.execute_chunk (&[]).unwrap ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ fn run_chunk (args: &[&str], chunk: &Chunk) -> Vec <Value> {
|
||||||
|
|
||||||
fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
|
fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
|
||||||
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
||||||
run_chunk (args, &chunk)
|
run_chunk (args, chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes arguments and Lua source code,
|
/// Takes arguments and Lua source code,
|
||||||
|
@ -42,7 +42,7 @@ fn run_bytecode (args: &[&str], bc: &[u8]) -> Vec <Value> {
|
||||||
/// and returns the output
|
/// and returns the output
|
||||||
|
|
||||||
fn run_source (args: &[&str], s: &str) -> Vec <Value> {
|
fn run_source (args: &[&str], s: &str) -> Vec <Value> {
|
||||||
let bc = loader::compile_bytecode_from_stdin (s.as_bytes ().to_vec ());
|
let bc = loader::compile_bytecode (s.as_bytes ().to_vec ());
|
||||||
run_bytecode (args, &bc)
|
run_bytecode (args, &bc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ fn bools () {
|
||||||
let expected: Vec <Value> = expected;
|
let expected: Vec <Value> = expected;
|
||||||
|
|
||||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||||
let mut vm = State::new (&chunk, &upvalues);
|
let mut vm = State::new (chunk.clone (), upvalues);
|
||||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||||
assert_eq! (actual, expected);
|
assert_eq! (actual, expected);
|
||||||
}
|
}
|
||||||
|
@ -128,10 +128,10 @@ fn bools () {
|
||||||
#[test]
|
#[test]
|
||||||
fn closure () {
|
fn closure () {
|
||||||
let source = include_bytes! ("../test_vectors/closure.lua");
|
let source = include_bytes! ("../test_vectors/closure.lua");
|
||||||
let bytecode = &crate::loader::compile_bytecode_from_stdin (source.to_vec ());
|
let bytecode = &crate::loader::compile_bytecode (source.to_vec ());
|
||||||
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
||||||
|
|
||||||
assert_eq! (run_chunk (&["_exe_name"], &chunk), vec! [Value::from (23i64)]);
|
assert_eq! (run_chunk (&["_exe_name"], chunk), vec! [Value::from (23i64)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -175,7 +175,7 @@ fn floats () {
|
||||||
] {
|
] {
|
||||||
let expected: Vec <Value> = expected;
|
let expected: Vec <Value> = expected;
|
||||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||||
let mut vm = State::new (&chunk, &upvalues);
|
let mut vm = State::new (chunk.clone (), upvalues);
|
||||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||||
|
|
||||||
assert_eq! (actual, expected);
|
assert_eq! (actual, expected);
|
||||||
|
@ -185,24 +185,20 @@ fn floats () {
|
||||||
#[test]
|
#[test]
|
||||||
fn fma () {
|
fn fma () {
|
||||||
let source = include_bytes! ("../test_vectors/fma.lua");
|
let source = include_bytes! ("../test_vectors/fma.lua");
|
||||||
let bytecode = &crate::loader::compile_bytecode_from_stdin (source.to_vec ());
|
let bytecode = &crate::loader::compile_bytecode (source.to_vec ());
|
||||||
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
let chunk = crate::loader::parse_chunk_from_bytes (bytecode).unwrap ();
|
||||||
assert_eq! (chunk.blocks.len (), 5);
|
assert_eq! (chunk.blocks.len (), 5);
|
||||||
|
|
||||||
assert_eq! (chunk.blocks [3].upvalues.len (), 2);
|
assert_eq! (chunk.blocks [3].upvalues.len (), 2);
|
||||||
|
|
||||||
for (arg, expected) in [
|
let arg = vec! ["_exe_name"];
|
||||||
(vec! ["_exe_name"], vec! [122.into ()]),
|
|
||||||
(vec! ["_exe_name"], vec! [122.into ()]),
|
|
||||||
] {
|
|
||||||
let expected: Vec <Value> = expected;
|
|
||||||
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
let upvalues = State::upvalues_from_args (arg.into_iter ().map (|s| s.to_string ()));
|
||||||
let mut vm = State::new (&chunk, &upvalues);
|
let mut vm = State::new (chunk, upvalues);
|
||||||
let actual = vm.execute_chunk (&[]).unwrap ();
|
let actual = vm.execute_chunk (&[]).unwrap ();
|
||||||
|
let expected = vec! [Value::from (122)];
|
||||||
|
|
||||||
assert_eq! (actual, expected);
|
assert_eq! (actual, expected);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn heap () {
|
fn heap () {
|
||||||
|
@ -279,29 +275,29 @@ fn is_93 () {
|
||||||
end
|
end
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let bc = loader::compile_bytecode_from_stdin (src.as_bytes ().to_vec ());
|
let bc = loader::compile_bytecode (src.as_bytes ().to_vec ());
|
||||||
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
let chunk = loader::parse_chunk_from_bytes (&bc).unwrap ();
|
||||||
|
|
||||||
assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false));
|
assert_eq! (chunk.blocks [0].instructions [3], Inst::EqK (0, 1, false));
|
||||||
|
|
||||||
let run = run_chunk;
|
let run = run_chunk;
|
||||||
|
|
||||||
assert_eq! (run (&[""], &chunk), vec! [Value::from (1)]);
|
assert_eq! (run (&[""], chunk.clone ()), vec! [Value::from (1)]);
|
||||||
assert_eq! (run (&["", "93"], &chunk), vec! [Value::from (0)]);
|
assert_eq! (run (&["", "93"], chunk.clone ()), vec! [Value::from (0)]);
|
||||||
assert_eq! (run (&["", "94"], &chunk), vec! [Value::from (1)]);
|
assert_eq! (run (&["", "94"], chunk.clone ()), vec! [Value::from (1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn native_functions () {
|
fn native_functions () {
|
||||||
fn add (l: &mut State) -> i32 {
|
fn add (_: &mut State) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn multiply (l: &mut State) -> i32 {
|
fn multiply (_: &mut State) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn greet (l: &mut State) -> i32 {
|
fn greet (_: &mut State) -> i32 {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,6 +355,25 @@ fn tables_2 () {
|
||||||
run_source (&[], src);
|
run_source (&[], src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tailcall () {
|
||||||
|
use crate::instruction::Instruction;
|
||||||
|
|
||||||
|
let src = r#"
|
||||||
|
return tonumber ("5")
|
||||||
|
"#;
|
||||||
|
|
||||||
|
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], Instruction::TailCall (0, 2, 1, false));
|
||||||
|
|
||||||
|
let actual = run_chunk (&[], chunk);
|
||||||
|
let expected = vec! [Value::from (5)];
|
||||||
|
|
||||||
|
assert_eq! (actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn value_size () {
|
fn value_size () {
|
||||||
// Per https://www.lua.org/doc/jucs05.pdf,
|
// Per https://www.lua.org/doc/jucs05.pdf,
|
|
@ -0,0 +1,2 @@
|
||||||
|
print (arg [0])
|
||||||
|
print (arg [1])
|
|
@ -0,0 +1,42 @@
|
||||||
|
use lunar_wave_vm as lwvm;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn embedding () {
|
||||||
|
use lwvm::{
|
||||||
|
State,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let src = br#"
|
||||||
|
return host_lib.add (14, 12)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fn host_add (l: &mut State, num_args: usize) -> usize {
|
||||||
|
assert_eq! (num_args, 2);
|
||||||
|
let a = l.reg (0).as_int ().unwrap ();
|
||||||
|
let b = l.reg (1).as_int ().unwrap ();
|
||||||
|
*l.reg_mut (0) = Value::from (a + b + 1993);
|
||||||
|
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 host_lib = [
|
||||||
|
("add", Value::RsFunc (host_add)),
|
||||||
|
].into_iter ().map (|(k, v)| (k.to_string (), v));
|
||||||
|
|
||||||
|
let env = [
|
||||||
|
("host_lib", Value::from_iter (host_lib.into_iter ())),
|
||||||
|
].into_iter ().map (|(k, v)| (k.to_string (), v));
|
||||||
|
|
||||||
|
let upvalues = vec! [
|
||||||
|
Value::from_iter (env.into_iter ()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut vm = State::new (chunk, upvalues);
|
||||||
|
let output = vm.execute_chunk (&vec! []).unwrap ();
|
||||||
|
|
||||||
|
assert_eq! (output, vec! [Value::from (2019)]);
|
||||||
|
}
|
118
src/main.rs
118
src/main.rs
|
@ -1,118 +0,0 @@
|
||||||
// cargo run -- --script test_vectors/fizz_buzz.lua
|
|
||||||
|
|
||||||
mod instruction;
|
|
||||||
mod loader;
|
|
||||||
mod state;
|
|
||||||
mod value;
|
|
||||||
|
|
||||||
#[cfg (test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
fn main () -> Result <(), state::StepError> {
|
|
||||||
use state::State;
|
|
||||||
|
|
||||||
let mut list_bytecode = false;
|
|
||||||
let mut pipe_bytecode = false;
|
|
||||||
let mut script = None;
|
|
||||||
let mut breakpoints = vec![];
|
|
||||||
|
|
||||||
let mut args = std::env::args ();
|
|
||||||
let exe_name = args.next ().unwrap ();
|
|
||||||
|
|
||||||
while let Some (arg) = args.next () {
|
|
||||||
match arg.as_str () {
|
|
||||||
"--break" => {
|
|
||||||
let s = args.next ().unwrap ();
|
|
||||||
let (block_idx, program_counter) = s.split_once (":").unwrap ();
|
|
||||||
let block_idx = str::parse (block_idx).unwrap ();
|
|
||||||
let program_counter = str::parse (program_counter).unwrap ();
|
|
||||||
|
|
||||||
breakpoints.push (state::Breakpoint {
|
|
||||||
block_idx,
|
|
||||||
program_counter,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"--list-bytecode" => list_bytecode = true,
|
|
||||||
"--pipe-bytecode" => pipe_bytecode = true,
|
|
||||||
"--script" => script = Some (args.next ().unwrap ()),
|
|
||||||
"--" => break,
|
|
||||||
_ => panic! ("can't parse args"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunk = if let Some (script) = script {
|
|
||||||
let bytecode = loader::compile_bytecode_from_file (&script);
|
|
||||||
let mut rdr = std::io::Cursor::new (bytecode);
|
|
||||||
loader::parse_chunk (&mut rdr).unwrap ()
|
|
||||||
}
|
|
||||||
else if pipe_bytecode {
|
|
||||||
let mut stdin = std::io::stdin ().lock ();
|
|
||||||
loader::parse_chunk (&mut stdin).unwrap ()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unimplemented!();
|
|
||||||
};
|
|
||||||
|
|
||||||
if list_bytecode {
|
|
||||||
dbg! (&chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
let upvalues = State::upvalues_from_args ([exe_name].into_iter ().chain (args));
|
|
||||||
|
|
||||||
let mut vm = State::new (&chunk, &upvalues);
|
|
||||||
if std::env::var("LWVM_DEBUG").is_ok() {
|
|
||||||
vm.debug_print = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut in_break = false;
|
|
||||||
let mut last_input = String::new ();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if in_break || breakpoints.iter ().any (|bp| vm.at_breakpoint (bp)) {
|
|
||||||
in_break = true;
|
|
||||||
dbg! (&vm.stack);
|
|
||||||
|
|
||||||
let mut input = Default::default ();
|
|
||||||
std::io::stdin ().read_line (&mut input).unwrap ();
|
|
||||||
|
|
||||||
let input = if input == "" {
|
|
||||||
&last_input
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
last_input = input;
|
|
||||||
&last_input
|
|
||||||
};
|
|
||||||
|
|
||||||
match input.as_str ().trim_end () {
|
|
||||||
"c" => in_break = false,
|
|
||||||
"q" => return Ok (()),
|
|
||||||
"registers" => {
|
|
||||||
dbg! (&vm.registers);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
"s" => {
|
|
||||||
match vm.step ()? {
|
|
||||||
None => (),
|
|
||||||
Some (state::StepOutput::ChunkReturned (x)) => {
|
|
||||||
dbg! (x);
|
|
||||||
return Ok (());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
x => { dbg! (x); },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match vm.step ()? {
|
|
||||||
None => (),
|
|
||||||
Some (state::StepOutput::ChunkReturned (x)) => {
|
|
||||||
dbg! (x);
|
|
||||||
return Ok (());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dbg! (vm);
|
|
||||||
panic! ("Hit max iterations before block returned");
|
|
||||||
}
|
|
Loading…
Reference in New Issue