use std::fmt::{self, Debug, Display}; use std::ptr; use std::rc::Rc; use gc::{Finalize, Trace}; use crate::obj::macros::*; use crate::obj::{make_ptr, BaseObjInst, Obj, ObjP}; 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, function_state: FunctionState) -> FunctionResult; #[derive(Debug, Trace)] pub struct BuiltinFunctionInst { base: BaseObjInst, #[unsafe_ignore_trace] name: Rc, #[unsafe_ignore_trace] function: BuiltinFunctionPtr, arity: Argc, } impl BuiltinFunctionInst { pub fn new(name: impl ToString, function: BuiltinFunctionPtr, arity: Argc) -> Self { Self { base: Default::default(), name: Rc::new(name.to_string()), function, arity, } } impl_create!( name: impl ToString, function: BuiltinFunctionPtr, arity: Argc, ); pub fn name(&self) -> &String { &self.name } } impl Finalize for BuiltinFunctionInst { fn finalize(&self) {} } impl Display for BuiltinFunctionInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, "", self.name(), self.arity().unwrap(), self.function as *const BuiltinFunctionPtr as usize ) } } impl Obj for BuiltinFunctionInst { fn arity(&self) -> Option { Some(self.arity) } fn call(&self, vm: &mut Vm, argc: Argc) { 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 { // TODO BuiltinFunctionInst::equals : need something more robust than checking addr_eq, // maybe check the self_binding pointer too? if let Some(other) = other.as_any().downcast_ref::() { ptr::addr_eq(self, other) } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // UserFunctionInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Clone, Trace)] pub struct UserFunctionInst { base: BaseObjInst, #[unsafe_ignore_trace] name: Rc, #[unsafe_ignore_trace] chunk: Rc, arity: Argc, captures: Vec, } impl UserFunctionInst { pub fn new(chunk: Chunk, arity: Argc) -> Self { Self { base: Default::default(), name: Rc::new("(anonymous)".to_string()), chunk: Rc::new(chunk), arity, captures: Default::default(), } } impl_create!(chunk: Chunk, arity: Argc); pub fn name(&self) -> &String { &self.name } pub fn set_name(&mut self, name: Rc) { self.name = name; } pub fn chunk(&self) -> &Chunk { &self.chunk } pub fn push_capture(&mut self, value: ObjP) { self.captures.push(value); } } impl Finalize for UserFunctionInst { fn finalize(&self) {} } impl Display for UserFunctionInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, "", self.name(), self.arity().unwrap(), self as *const _ as usize ) } } impl Obj for UserFunctionInst { fn arity(&self) -> Option { Some(self.arity) } fn call(&self, vm: &mut Vm, argc: Argc) { assert_eq!(argc, self.arity, "argc must match arity"); 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()); } } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { // TODO UserFunctionInst::equals : need something more robust than checking addr_eq. ptr::addr_eq(self, other) } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // MethodInst //////////////////////////////////////////////////////////////////////////////// #[derive(Trace)] pub struct MethodInst { base: BaseObjInst, self_binding: ObjP, function: ObjP, } impl Debug for MethodInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.debug_struct("MethodInst") .field("base", &self.base) .field("self_binding", &format!("{}", self.self_binding.borrow())) .field("function", &self.function) .finish() } } impl MethodInst { pub fn new(self_binding: ObjP, function: ObjP) -> Self { Self { base: Default::default(), self_binding, function, } } pub fn create(ty: ObjP, self_binding: ObjP, function: ObjP) -> ObjP { let ptr = make_ptr(Self::new(self_binding, function)); ptr.borrow_mut().instantiate(ty.clone()); ptr } pub fn self_binding(&self) -> &ObjP { &self.self_binding } } impl Finalize for MethodInst { fn finalize(&self) {} } impl Display for MethodInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.function.borrow()) } } impl Obj for MethodInst { fn arity(&self) -> Option { // Subtract one from the arity - this is because the VM uses arity() to check against the // number of arguments passed. self.function.borrow().arity().map(|arity| arity - 1) } fn call(&self, vm: &mut Vm, mut argc: Argc) { let self_pos = vm.stack().len() - (argc as usize); vm.stack_mut().insert(self_pos, self.self_binding.clone()); argc += 1; self.function.borrow().call(vm, argc) } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { ptr::addr_eq(&*self.self_binding, &*other.self_binding) && ptr::addr_eq(&*self.function, &*other.function) } else { false } } impl_base_obj!(); }