⭐ allow calling closures
parent
a2c91757cf
commit
81efebdda2
88
src/main.rs
88
src/main.rs
|
@ -4,29 +4,81 @@ mod state;
|
|||
mod tests;
|
||||
|
||||
fn main() {
|
||||
use state::Instruction as Inst;
|
||||
use state::State;
|
||||
use state::{
|
||||
Block,
|
||||
Chunk,
|
||||
Instruction as Inst,
|
||||
State,
|
||||
};
|
||||
|
||||
let chunk = state::Chunk {
|
||||
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),
|
||||
Inst::Return (3, 1, 1),
|
||||
],
|
||||
constants: vec! [
|
||||
0.5.into (),
|
||||
"print".into (),
|
||||
/*
|
||||
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::GetTabUp (1, 0, 0),
|
||||
Inst::Move (2, 0),
|
||||
Inst::LoadFalse (3),
|
||||
Inst::Call (2, 2, 0),
|
||||
Inst::Call (1, 0, 1),
|
||||
Inst::GetTabUp (1, 0, 0),
|
||||
Inst::Move (2, 0),
|
||||
Inst::LoadTrue (3),
|
||||
Inst::Call (2, 2, 0),
|
||||
Inst::Call (1, 0, 1),
|
||||
Inst::Move (1, 0),
|
||||
Inst::GetTabUp (2, 0, 1),
|
||||
Inst::GetI (2, 2, 1),
|
||||
Inst::Not (2, 2),
|
||||
Inst::Not (2, 2),
|
||||
Inst::Call (1, 2, 2),
|
||||
Inst::GetTabUp (2, 0, 0),
|
||||
Inst::Move (3, 1),
|
||||
Inst::Call (2, 2, 1),
|
||||
Inst::Return (1, 2, 1),
|
||||
Inst::Return (2, 1, 1),
|
||||
],
|
||||
constants: vec! [
|
||||
"print".into (),
|
||||
"arg".into (),
|
||||
],
|
||||
},
|
||||
Block {
|
||||
instructions: vec! [
|
||||
Inst::Test (0, 0),
|
||||
Inst::Jmp (3),
|
||||
Inst::LoadI (1, 99),
|
||||
Inst::Return1 (1),
|
||||
Inst::Jmp (2),
|
||||
Inst::LoadI (1, 98),
|
||||
Inst::Return1 (1),
|
||||
Inst::Return0,
|
||||
],
|
||||
constants: vec! [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let mut vm = State::default ();
|
||||
if std::env::var("LUA_DEBUG").is_ok() {
|
||||
vm.debug_print = true;
|
||||
}
|
||||
|
||||
let upvalues = State::upvalues_from_args (std::env::args ());
|
||||
println! ("Returned: {:?}", vm.execute_chunk (&chunk, &upvalues));
|
||||
}
|
||||
|
|
185
src/state.rs
185
src/state.rs
|
@ -19,28 +19,40 @@ pub enum Instruction {
|
|||
// Jump
|
||||
Jmp (i32),
|
||||
|
||||
LoadFalse (u8),
|
||||
|
||||
// Load Integer?
|
||||
LoadI (u8, i32),
|
||||
|
||||
// Load Constant
|
||||
LoadK (u8, i32),
|
||||
|
||||
LoadTrue (u8),
|
||||
|
||||
// MetaMethod, Binary
|
||||
MmBin (u8, u8, u8),
|
||||
|
||||
Move (u8, u8),
|
||||
|
||||
Not (u8, u8),
|
||||
|
||||
// (A, B, _C) Return B - 1 registers starting with A
|
||||
Return (u8, u8, u8),
|
||||
|
||||
Return0,
|
||||
|
||||
// Return just one register
|
||||
Return1 (u8),
|
||||
|
||||
Test (u8, i32),
|
||||
|
||||
VarArgPrep (i32),
|
||||
}
|
||||
|
||||
#[derive (Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
Nil,
|
||||
False,
|
||||
True,
|
||||
Boolean (bool),
|
||||
Float (f64),
|
||||
String (String),
|
||||
|
||||
|
@ -48,6 +60,7 @@ pub enum Value {
|
|||
// tables and function pointers yet
|
||||
|
||||
BogusArg (Vec <String>),
|
||||
BogusClosure (usize),
|
||||
BogusEnv (BTreeMap <String, Value>),
|
||||
BogusPrint,
|
||||
}
|
||||
|
@ -89,25 +102,50 @@ impl Value {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_truthy (&self) -> bool {
|
||||
// And this is something Lua does better than JS and Python.
|
||||
|
||||
match self {
|
||||
Value::Nil => false,
|
||||
Value::Boolean (false) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub struct Block {
|
||||
pub instructions: Vec <Instruction>,
|
||||
pub constants: Vec <Value>,
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub blocks: Vec <Block>,
|
||||
}
|
||||
|
||||
#[derive (Debug)]
|
||||
struct StackFrame {
|
||||
// i32 makes it a little easier to implement jumps
|
||||
// Starts at 0 right after OP_CALL
|
||||
|
||||
program_counter: i32,
|
||||
|
||||
// Starts from 0 for main and 1 for the first closure
|
||||
block_idx: usize,
|
||||
|
||||
register_offset: usize,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
registers: Vec <Value>,
|
||||
|
||||
// i32 makes it a little easier to implement jumps
|
||||
program_counter: i32,
|
||||
pub debug_print: bool,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
registers: vec! [Value::Nil; 256],
|
||||
program_counter: 0,
|
||||
debug_print: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -131,11 +169,32 @@ impl State {
|
|||
-> Vec <Value> {
|
||||
let max_iters = 2000;
|
||||
|
||||
let mut stack = vec! [
|
||||
StackFrame {
|
||||
program_counter: 0,
|
||||
block_idx: 0,
|
||||
register_offset: 0,
|
||||
},
|
||||
];
|
||||
|
||||
for _ in 0..max_iters {
|
||||
let instruction = chunk.instructions.get (usize::try_from (self.program_counter).unwrap ()).unwrap ();
|
||||
let stack_idx = stack.len () - 1;
|
||||
let frame = stack.get_mut (stack_idx).unwrap ();
|
||||
let block = chunk.blocks.get (frame.block_idx).unwrap ();
|
||||
|
||||
let r = &mut self.registers;
|
||||
let k = &chunk.constants;
|
||||
let mut next_pc = frame.program_counter;
|
||||
|
||||
let pc = usize::try_from (frame.program_counter).expect ("program_counter is not a valid usize");
|
||||
let instruction = match block.instructions.get (pc) {
|
||||
Some (x) => x,
|
||||
None => {
|
||||
dbg! (&stack);
|
||||
panic! ("program_counter went out of bounds");
|
||||
}
|
||||
};
|
||||
|
||||
let r = &mut self.registers [frame.register_offset..];
|
||||
let k = &block.constants;
|
||||
|
||||
match instruction {
|
||||
Instruction::Add (a, b, c) => {
|
||||
|
@ -160,12 +219,47 @@ impl State {
|
|||
// TODO: Only implement printing values for now
|
||||
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let v_a = r.get (a).unwrap ();
|
||||
|
||||
assert_eq! (r.get (a).unwrap (), &Value::BogusPrint);
|
||||
assert_eq! (*b, 2);
|
||||
assert_eq! (*c, 1);
|
||||
match v_a {
|
||||
Value::BogusClosure (idx) => {
|
||||
let block_idx = frame.block_idx;
|
||||
let target_block = idx + 1;
|
||||
|
||||
let current_frame = &stack [stack.len () - 1];
|
||||
|
||||
stack.push (StackFrame {
|
||||
program_counter: 0,
|
||||
block_idx: target_block,
|
||||
register_offset: current_frame.register_offset + a + 1,
|
||||
});
|
||||
|
||||
if self.debug_print {
|
||||
println! ("Inst {block_idx}:{pc} calls {target_block}:0");
|
||||
let stack_depth = stack.len ();
|
||||
println! ("stack_depth: {stack_depth}");
|
||||
}
|
||||
|
||||
// Skip the PC increment at the bottom of the loop
|
||||
continue;
|
||||
},
|
||||
Value::BogusPrint => {
|
||||
// In real Lua, print is a function inside
|
||||
// the runtime. Here it's bogus.
|
||||
|
||||
// assert_eq! (*b, 2);
|
||||
assert_eq! (*c, 1);
|
||||
|
||||
println! ("{:?}", r.get (a + 1).unwrap ());
|
||||
},
|
||||
_ => panic! ("Cannot call value {a:?}. backtrace: {stack:?}"),
|
||||
}
|
||||
},
|
||||
Instruction::Closure (a, b) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
||||
println! ("{:?}", r.get (a + 1).unwrap ());
|
||||
r [a] = Value::BogusClosure (b);
|
||||
},
|
||||
Instruction::EqK (a, b, c_k) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
|
@ -174,8 +268,8 @@ impl State {
|
|||
let equal = r [a] == k [b];
|
||||
|
||||
match (equal, c_k) {
|
||||
(true, 0) => self.program_counter += 1,
|
||||
(false, 1) => self.program_counter += 1,
|
||||
(true, 0) => next_pc += 1,
|
||||
(false, 1) => next_pc += 1,
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
|
@ -212,7 +306,11 @@ impl State {
|
|||
|
||||
r [a] = value;
|
||||
},
|
||||
Instruction::Jmp (s_j) => self.program_counter += s_j,
|
||||
Instruction::Jmp (s_j) => next_pc += s_j,
|
||||
Instruction::LoadFalse (a) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
r [a] = Value::Boolean (false);
|
||||
},
|
||||
Instruction::LoadI (a, sbx) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
|
||||
|
@ -224,6 +322,10 @@ impl State {
|
|||
|
||||
r [a] = k [bx].clone ();
|
||||
},
|
||||
Instruction::LoadTrue (a) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
r [a] = Value::Boolean (true);
|
||||
},
|
||||
Instruction::MmBin (a, b, _c) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
@ -244,19 +346,62 @@ impl State {
|
|||
|
||||
r [a] = r [b].clone ();
|
||||
},
|
||||
Instruction::Not (a, b) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
||||
r [a] = Value::Boolean (! r [b].is_truthy());
|
||||
}
|
||||
Instruction::Return (a, b, _c) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let b = usize::try_from (*b).unwrap ();
|
||||
|
||||
return self.registers [a..(a + b - 1)].to_vec();
|
||||
return r [a..(a + b - 1)].to_vec();
|
||||
},
|
||||
Instruction::Return1 (a) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
let popped_frame = stack.pop ().unwrap ();
|
||||
|
||||
self.registers [popped_frame.register_offset - 1] = r [a].clone ();
|
||||
|
||||
let stack_idx = stack.len () - 1;
|
||||
let frame = stack.get (stack_idx).unwrap ();
|
||||
let new_block = frame.block_idx;
|
||||
next_pc = frame.program_counter;
|
||||
|
||||
if self.debug_print {
|
||||
let old_block = popped_frame.block_idx;
|
||||
let old_pc = popped_frame.program_counter;
|
||||
println! ("Inst {old_block}:{old_pc} returns to inst {new_block}:{next_pc}");
|
||||
let stack_depth = stack.len ();
|
||||
println! ("stack_depth: {stack_depth}");
|
||||
}
|
||||
},
|
||||
Instruction::Test (a, _k) => {
|
||||
let a = usize::try_from (*a).unwrap ();
|
||||
|
||||
let a = r.get (a).unwrap ();
|
||||
|
||||
if self.debug_print {
|
||||
println! ("Test {a:?}");
|
||||
}
|
||||
|
||||
if a.is_truthy() {
|
||||
next_pc += 1;
|
||||
}
|
||||
},
|
||||
Instruction::VarArgPrep (_) => (),
|
||||
x => panic! ("Unimplemented instruction {x:?}"),
|
||||
}
|
||||
|
||||
self.program_counter += 1;
|
||||
next_pc += 1;
|
||||
{
|
||||
let stack_idx = stack.len () - 1;
|
||||
let frame = stack.get_mut (stack_idx).unwrap ();
|
||||
frame.program_counter = next_pc;
|
||||
}
|
||||
}
|
||||
|
||||
panic! ("Hit max iterations before chunk returned");
|
||||
panic! ("Hit max iterations before block returned");
|
||||
}
|
||||
}
|
||||
|
|
87
src/tests.rs
87
src/tests.rs
|
@ -1,10 +1,87 @@
|
|||
use crate::state::{
|
||||
Block,
|
||||
Chunk,
|
||||
Instruction as Inst,
|
||||
State,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[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),
|
||||
Inst::Return (2, 1, 1),
|
||||
],
|
||||
constants: vec! [
|
||||
"arg".into (),
|
||||
"print".into (),
|
||||
],
|
||||
},
|
||||
Block {
|
||||
instructions: vec! [
|
||||
Inst::Test (0, 0),
|
||||
Inst::Jmp (3),
|
||||
Inst::LoadI (1, 99),
|
||||
Inst::Return1 (1),
|
||||
Inst::Jmp (2),
|
||||
Inst::LoadI (1, 98),
|
||||
Inst::Return1 (1),
|
||||
Inst::Return0,
|
||||
],
|
||||
constants: vec! [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
for (arg, expected) in [
|
||||
(vec! ["_exe_name"], vec! [98.into ()]),
|
||||
(vec! ["_exe_name", "asdf"], vec! [99.into ()]),
|
||||
] {
|
||||
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 floats () {
|
||||
/*
|
||||
|
@ -17,7 +94,7 @@ fn floats () {
|
|||
return x
|
||||
*/
|
||||
|
||||
let chunk = Chunk {
|
||||
let block = Block {
|
||||
instructions: vec! [
|
||||
Inst::VarArgPrep (0),
|
||||
Inst::LoadK (0, 0),
|
||||
|
@ -35,6 +112,9 @@ fn floats () {
|
|||
"print".into (),
|
||||
],
|
||||
};
|
||||
let chunk = Chunk {
|
||||
blocks: vec! [block],
|
||||
};
|
||||
|
||||
for (arg, expected) in [
|
||||
(vec! ["_exe_name"], vec! [3.5.into ()]),
|
||||
|
@ -60,7 +140,7 @@ fn is_93 () {
|
|||
end
|
||||
*/
|
||||
|
||||
let chunk = Chunk {
|
||||
let block = Block {
|
||||
instructions: vec! [
|
||||
Inst::VarArgPrep (0),
|
||||
Inst::GetTabUp (1, 0, 0),
|
||||
|
@ -88,6 +168,9 @@ fn is_93 () {
|
|||
"it's not 93",
|
||||
].into_iter ().map (Value::from).collect (),
|
||||
};
|
||||
let chunk = Chunk {
|
||||
blocks: vec! [block],
|
||||
};
|
||||
|
||||
for (arg, expected) in [
|
||||
(vec! ["_exe_name"], vec! [1.into ()]),
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
local function bool_to_x (b)
|
||||
if b then
|
||||
return 99
|
||||
else
|
||||
return 98
|
||||
end
|
||||
end
|
||||
|
||||
print (bool_to_x (false))
|
||||
print (bool_to_x (true))
|
||||
|
||||
local x = bool_to_x (not not arg [1])
|
||||
print (x)
|
||||
return x
|
Loading…
Reference in New Issue