allow calling closures

main
_ 2023-09-24 19:47:17 -05:00
parent a2c91757cf
commit 81efebdda2
4 changed files with 334 additions and 40 deletions

View File

@ -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));
}

View File

@ -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");
}
}

View File

@ -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 ()]),

14
test_vectors/bools.lua Normal file
View File

@ -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