diff --git a/src/builtins.rs b/src/builtins.rs index 6d32d09..51534d0 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -1,17 +1,58 @@ //! Builtin functions. use std::collections::HashMap; +use crate::obj::function::{FunctionResult, FunctionState}; use crate::obj::*; use crate::vm::Vm; -pub(crate) fn println(vm: &mut Vm, args: Vec) -> ObjP { - println!("{}", args[0].borrow()); - vm.create_nil() +// TODO builtins.rs - need a good macro to help reduce this repetition. +// The main problem is that "macros cannot expand to match arms". +// Thus, if we try to do something like this: +// +// ( ($n:expr, $block:block),* ) => { FunctionState::Resume($n) => $block } +// +// Is not allowed. +// +// This would probably be doable in a procedural macro. + +pub(crate) fn println(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + FunctionState::Begin => { + let obj = vm.peek(); + let method_type = vm.builtins().get("Method").unwrap().clone(); + let to_repr = obj + .borrow_mut() + .get_attr_lazy(obj.clone(), method_type, "to_repr") + .expect("no to_repr"); + to_repr.borrow().call(vm, 0); + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => { + println!("{}", vm.frame_stack()[0].borrow()); + vm.create_nil().into() + } + _ => unreachable!(), + } } -pub(crate) fn print(vm: &mut Vm, args: Vec) -> ObjP { - print!("{}", args[0].borrow()); - vm.create_nil() +pub(crate) fn print(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + FunctionState::Begin => { + let obj = vm.peek(); + let method_type = vm.builtins().get("Method").unwrap().clone(); + let to_repr = obj + .borrow_mut() + .get_attr_lazy(obj.clone(), method_type, "to_repr") + .expect("no to_repr"); + to_repr.borrow().call(vm, 0); + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => { + print!("{}", vm.frame_stack()[0].borrow()); + vm.create_nil().into() + } + _ => unreachable!(), + } } pub fn init_builtins(builtins: &mut HashMap) { diff --git a/src/disassemble.rs b/src/disassemble.rs index 76e0984..191a773 100644 --- a/src/disassemble.rs +++ b/src/disassemble.rs @@ -99,6 +99,11 @@ fn disassemble_chunk(chunk: &Chunk, constants: &Vec, globals: &Vec arg = format!("{depth}"); info = format!("slot {slot} (name unknown)"); } + Op::Nop => { + op_str = "NOP"; + arg = String::new(); + info = String::new(); + } Op::Halt => { op_str = "HALT"; arg = String::new(); diff --git a/src/obj.rs b/src/obj.rs index 965db5a..888344d 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -116,7 +116,8 @@ pub fn init_types(builtins: &mut HashMap) { // type definitions Type { // Method conversion - to_string => builtins.create_builtin_function("to_string", BaseObjInst::to_string, 1), + to_string => builtins.create_builtin_function("to_string", BaseObjInst::to_repr, 1), + to_repr => builtins.create_builtin_function("to_repr", BaseObjInst::to_repr, 1), to_bool => builtins.create_builtin_function("to_bool", BaseObjInst::to_bool, 1), // Operators __add__ => builtins.create_builtin_function("__add__", BaseObjInst::add, 2), @@ -333,73 +334,74 @@ struct BaseObjInst { // impl BaseObjInst { - fn add(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __add__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn add(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __add__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn sub(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __sub__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn sub(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __sub__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn mul(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __mul__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn mul(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __mul__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn div(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __div__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn div(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __div__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn and(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __and__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn and(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __and__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn or(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __or__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn or(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __or__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn ne(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __ne__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn ne(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __ne__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn eq(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __eq__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn eq(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __eq__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn gt(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __gt__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn gt(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __gt__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn ge(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __ge__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn ge(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __ge__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn lt(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __lt__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn lt(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __lt__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn le(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __le__ function (self: {:?}, rhs: {:?})", args[0].borrow(), args[1].borrow()) + fn le(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __le__ function (self: {:?}, rhs: {:?})", vm.frame_stack()[0].borrow(), vm.frame_stack()[1].borrow()) } - fn pos(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __pos__ function (self: {:?})", args[0].borrow()) + fn pos(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __pos__ function (self: {:?})", vm.frame_stack()[0].borrow()) } - fn neg(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __neg__ function (self: {:?})", args[0].borrow()) + fn neg(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __neg__ function (self: {:?})", vm.frame_stack()[0].borrow()) } - fn not(_vm: &mut Vm, args: Vec) -> ObjP { - todo!("Raise some kind of not implemented/not callable error for __not__ function (self: {:?})", args[0].borrow()) + fn not(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + todo!("Raise some kind of not implemented/not callable error for __not__ function (self: {:?})", vm.frame_stack()[0].borrow()) } - fn to_bool(vm: &mut Vm, args: Vec) -> ObjP { - vm.create_bool(args[0].borrow().is_truthy()) + fn to_bool(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + vm.create_bool(vm.frame_stack()[0].borrow().is_truthy()) + .into() } - fn to_string(vm: &mut Vm, args: Vec) -> ObjP { - let str_value = format!("{}", &args[0].borrow()); - vm.create_str(str_value) + fn to_repr(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + let str_value = format!("{}", vm.frame_stack()[0].borrow()); + vm.create_str(str_value).into() } } diff --git a/src/obj/function.rs b/src/obj/function.rs index 92981c6..556a4ec 100644 --- a/src/obj/function.rs +++ b/src/obj/function.rs @@ -6,18 +6,59 @@ use gc::{Finalize, Trace}; use crate::obj::macros::*; use crate::obj::{make_ptr, BaseObjInst, Obj, ObjP}; -use crate::vm::{Argc, Chunk, Frame, Vm}; +use crate::vm::{Argc, Chunk, Frame, Function, Vm}; + +//////////////////////////////////////////////////////////////////////////////// +// FunctionResult, FunctionState +//////////////////////////////////////////////////////////////////////////////// + +/// A result that instructs the VM what to do after a function finishes its execution. +#[derive(Debug)] +pub enum FunctionResult { + /// Take this value and push it on the stack as its return value. + ReturnPush(ObjP), + + /// Return value has already been pushed to the stack. + Return, + + /// Yield control to the VM with a state marker, and resume execution. + /// + /// This means that a new stack frame should have been pushed to the VM and resume execution + /// starting from there. + Yield(usize), +} + +impl From for FunctionResult { + fn from(other: ObjP) -> Self { + FunctionResult::ReturnPush(other) + } +} + +/// A function's resume state. +/// +/// When a builtin function is called, it may need to set up a stack frame for a new function, +/// yielding its control back to the VM. If it does this, then presumably, that function would like +/// to resume its execution where it left off. There's not really a way to capture a native +/// function's execution state in Rust (without tons of unsafe and not-really-worth-it black +/// magic), so instead a builtin function will accept a `FunctionState` value to give it a hint of +/// where it left off. +#[derive(Debug, Clone, Copy)] +pub enum FunctionState { + Begin, + Resume(usize), +} //////////////////////////////////////////////////////////////////////////////// // BuiltinFunctionInst //////////////////////////////////////////////////////////////////////////////// -pub type BuiltinFunctionPtr = fn(vm: &mut Vm, args: Vec) -> ObjP; +pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult; #[derive(Debug, Trace)] pub struct BuiltinFunctionInst { base: BaseObjInst, - name: String, + #[unsafe_ignore_trace] + name: Rc, #[unsafe_ignore_trace] function: BuiltinFunctionPtr, arity: Argc, @@ -27,7 +68,7 @@ impl BuiltinFunctionInst { pub fn new(name: impl ToString, function: BuiltinFunctionPtr, arity: Argc) -> Self { Self { base: Default::default(), - name: name.to_string(), + name: Rc::new(name.to_string()), function, arity, } @@ -66,16 +107,12 @@ impl Obj for BuiltinFunctionInst { } fn call(&self, vm: &mut Vm, argc: Argc) { - // args - let mut args = Vec::with_capacity(argc as usize); - for _ in 0..argc { - args.push(vm.pop()); - } - args.reverse(); - // callee (self) - vm.pop(); - let result = (self.function)(vm, args); - vm.push(result); + let new_frame = Frame::new( + Rc::clone(&self.name), + Function::Builtin(self.function, FunctionState::Begin), + vm.stack().len() - (argc as usize), + ); + vm.push_frame(new_frame); } fn equals(&self, other: &dyn Obj) -> bool { @@ -159,12 +196,11 @@ impl Obj for UserFunctionInst { fn call(&self, vm: &mut Vm, argc: Argc) { assert_eq!(argc, self.arity, "argc must match arity"); - let new_frame = Frame { - name: Rc::clone(&self.name), - chunk: Rc::clone(&self.chunk), - ip: 0, - stack_base: vm.stack().len() - (argc as usize), - }; + let new_frame = Frame::new( + Rc::clone(&self.name), + Function::Chunk(Rc::clone(&self.chunk)), + vm.stack().len() - (argc as usize), + ); vm.push_frame(new_frame); for capture in &self.captures { vm.push(capture.clone()); diff --git a/src/vm.rs b/src/vm.rs index 9e0f9b2..43e9a6c 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -31,6 +31,7 @@ pub enum Op { CloseOver { depth: ShortOpArg, slot: ShortOpArg }, // VM control + Nop, Halt, } @@ -61,19 +62,25 @@ pub struct Chunk { pub(crate) locals: Vec, } +#[derive(Debug)] +pub(crate) enum Function { + Chunk(Rc), + Builtin(BuiltinFunctionPtr, FunctionState), +} + #[derive(Debug)] pub struct Frame { pub(crate) name: Rc, - pub(crate) chunk: Rc, + pub(crate) function: Function, pub(crate) ip: usize, pub(crate) stack_base: usize, } impl Frame { - pub fn new(name: Rc, chunk: Rc, stack_base: usize) -> Self { + pub fn new(name: Rc, function: Function, stack_base: usize) -> Self { Self { name, - chunk, + function, ip: 0, stack_base, } @@ -115,7 +122,11 @@ impl Vm { // stack and frames let stack = Vec::new(); - let frames = vec![Frame::new("__main__".to_string().into(), chunk, 0)]; + let frames = vec![Frame::new( + "__main__".to_string().into(), + Function::Chunk(chunk), + 0, + )]; Vm { constants, @@ -137,6 +148,11 @@ impl Vm { &mut self.stack } + /// Gets the current stack, starting at the frame's stack base. + pub fn frame_stack(&self) -> &[ObjP] { + &self.stack()[self.frame().stack_base..] + } + /// Current stack frame. pub fn frame(&self) -> &Frame { self.frames.last().unwrap() @@ -158,8 +174,12 @@ impl Vm { } /// Gets the chunk of the currently executing frame. - pub fn chunk(&self) -> &Chunk { - &self.frame().chunk + pub fn chunk(&self) -> Option<&Chunk> { + if let Function::Chunk(chunk) = &self.frame().function { + Some(chunk) + } else { + None + } } /// Instruction pointer of the current frame. @@ -181,10 +201,48 @@ impl Vm { */ /// Get the current instruction and advance the IP. - fn next(&mut self) -> Op { - let ip = self.ip(); - self.set_ip(ip + 1); - self.chunk().code[ip] + /// + /// This may actually end up calling a function on top of the stack, if it's a builtin function. + fn dispatch(&mut self) -> Op { + let mut ip = self.ip(); + let op = match &self.frame().function { + Function::Chunk(chunk) => { + let op = chunk.code[ip]; + ip += 1; + op + } + Function::Builtin(function, state) => { + // keep track of where the current frame index is in case we need to yield + let frame_index = self.frames.len() - 1; + let result = (function)(self, *state); + match result { + FunctionResult::ReturnPush(value) => { + // push value to the stack and let the VM handle return protocols + self.push(value); + Op::Return + } + // value is already on top of the stack, let the VM handle return protocols + FunctionResult::Return => Op::Return, + // new stack frame has been pushed, yield control while keeping track of the + // old state + FunctionResult::Yield(resume_state) => { + // update the current state + if let Function::Builtin(_function, ref mut state) = + &mut self.frames[frame_index].function + { + *state = FunctionState::Resume(resume_state) + } else { + panic!("function stack got really messed up - function stack was changed under us"); + } + // inject a no-op so the VM can load a new instruction or dispatch a new + // instruction. + Op::Nop + } + } + } + }; + self.set_ip(ip); + op } /// Pop a value from the stack. @@ -207,7 +265,7 @@ impl Vm { let method_type = self.builtins().get("Method").unwrap().clone(); loop { - match self.next() { + match self.dispatch() { Op::Pop => { self.pop(); } @@ -216,14 +274,14 @@ impl Vm { self.push(constant); } Op::GetLocal(local_index) => { - let local = &self.chunk().locals[local_index as usize]; + let local = &self.chunk().expect("no chunk").locals[local_index as usize]; let value = Ptr::clone(&self.stack[self.frame().stack_base + local.slot as usize]); self.push(value); } Op::SetLocal(local_index) => { let value = self.pop(); - let local = &self.chunk().locals[local_index as usize]; + let local = &self.chunk().expect("no chunk").locals[local_index as usize]; let index = self.frame().stack_base + local.slot as usize; self.stack[index] = value; } @@ -340,6 +398,9 @@ impl Vm { fun.push_capture(value); self.push(make_ptr(fun)); } + Op::Nop => { + continue; + } Op::Halt => { break; }