diff --git a/src/builtins.rs b/src/builtins.rs index f28ebf7..cc3d9b7 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -22,56 +22,28 @@ pub fn init_global_builtins() { } } -// 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. - //////////////////////////////////////////////////////////////////////////////// // Global functions //////////////////////////////////////////////////////////////////////////////// -pub(crate) fn println(vm: &mut Vm, state: FunctionState) -> FunctionResult { - match state { - FunctionState::Begin => { - let obj = vm.peek(); - let method = obj - .borrow() - .get_vtable_attr(obj.clone(), "to_str") - .expect("no to_str"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => { - println!("{}", vm.frame_stack().last().unwrap().borrow()); - Nil::create().into() - } - _ => unreachable!(), - } +pub(crate) fn println(vm: &mut Vm) -> ObjP { + let obj = vm.frame_stack()[0].clone(); + let method = obj + .borrow() + .get_vtable_attr(obj.clone(), "to_str") + .expect("no to_str"); + let result = vm.call(method, &[]); + println!("{}", result.borrow()); + Nil::create() } -pub(crate) fn print(vm: &mut Vm, state: FunctionState) -> FunctionResult { - match state { - FunctionState::Begin => { - let obj = vm.peek(); - let method = obj - .borrow() - .get_vtable_attr(obj.clone(), "to_str") - .expect("no to_str"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => { - print!("{}", vm.frame_stack().last().unwrap().borrow()); - Nil::create().into() - } - _ => unreachable!(), - } +pub(crate) fn print(vm: &mut Vm) -> ObjP { + let obj = vm.frame_stack()[0].clone(); + let method = obj + .borrow() + .get_vtable_attr(obj.clone(), "to_str") + .expect("no to_str"); + let result = vm.call(method, &[]); + print!("{}", result.borrow()); + Nil::create() } diff --git a/src/main.rs b/src/main.rs index 552f1ad..fa90a80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ fn main() { // VM needs the module to be on top of the stack when it executes vm.push(module.clone()); vm.enter_module(module); - vm.run(); + vm.resume(); Ok(()) } diff --git a/src/obj.rs b/src/obj.rs index 428c330..3f1e3b5 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -48,7 +48,7 @@ pub mod prelude { pub use crate::obj::{Nil, Obj}; // Other auxiliary types and functions - pub use crate::obj::function::{BuiltinFunctionPtr, FunctionResult, FunctionState}; + pub use crate::obj::function::BuiltinFunctionPtr; } // TODO obj::with_obj_downcast - optimize downcasts of "known" types with an unchecked downcast @@ -308,107 +308,77 @@ impl Obj { // Common functions // - pub(crate) fn to_str(vm: &mut Vm, state: FunctionState) -> FunctionResult { - match state { - FunctionState::Begin => { - let this = vm.frame_stack()[0].clone(); - let method = this - .borrow() - .get_vtable_attr(this.clone(), "to_repr") - .clone() - .expect("no to_repr"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => FunctionResult::Return, - _ => unreachable!(), - } + pub(crate) fn to_str(vm: &mut Vm) -> ObjP { + let this = vm.frame_stack()[0].clone(); + let method = this + .borrow() + .get_vtable_attr(this.clone(), "to_repr") + .clone() + .expect("no to_repr"); + vm.call(method, &[]) } - pub(crate) fn to_repr(vm: &mut Vm, _state: FunctionState) -> FunctionResult { - Str::create(format!("{:?}", vm.frame_stack()[0].borrow())).into() + pub(crate) fn to_repr(vm: &mut Vm) -> ObjP { + Str::create(format!("{:?}", vm.frame_stack()[0].borrow())) } - pub(crate) fn to_bool(vm: &mut Vm, _state: FunctionState) -> FunctionResult { - Bool::create(vm.frame_stack()[0].borrow().is_truthy()).into() + pub(crate) fn to_bool(vm: &mut Vm) -> ObjP { + Bool::create(vm.frame_stack()[0].borrow().is_truthy()) } // // Operators // - pub(crate) fn and(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn and(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].borrow(); let rhs = vm.frame_stack()[1].borrow(); let result = lhs.is_truthy() && rhs.is_truthy(); - Bool::create(result).into() + Bool::create(result) } - pub(crate) fn or(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn or(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].borrow(); let rhs = vm.frame_stack()[1].borrow(); let result = lhs.is_truthy() || rhs.is_truthy(); - Bool::create(result).into() + Bool::create(result) } - pub(crate) fn ne(vm: &mut Vm, state: FunctionState) -> FunctionResult { - match state { - FunctionState::Begin => { - // we actually want to be calling the lhs's eq function and then negate it - let lhs = vm.frame_stack()[0].clone(); - let method = lhs - .borrow() - .get_vtable_attr(lhs.clone(), "__eq__") - .expect("no __eq__"); - - let rhs = vm.frame_stack()[1].clone(); - vm.push(rhs); - method.borrow().call(vm, 1); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => { - let result = !vm.peek().borrow().is_truthy(); - Bool::create(result).into() - } - _ => { - unreachable!() - } - } + pub(crate) fn ne(vm: &mut Vm) -> ObjP { + let this = vm.frame_stack()[0].clone(); + let method = this + .borrow() + .get_vtable_attr(this.clone(), "__eq__") + .expect("no __eq__"); + let rhs = vm.frame_stack()[1].clone(); + let result = vm.call(method, &[rhs]); + let result_borrowed = result.borrow(); + Bool::create(!result_borrowed.is_truthy()) } - pub(crate) fn eq(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn eq(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].borrow(); let rhs = vm.frame_stack()[1].borrow(); let equals = lhs.equals(&*rhs); - Bool::create(equals).into() + Bool::create(equals) } - pub(crate) fn not(vm: &mut Vm, state: FunctionState) -> FunctionResult { - match state { - FunctionState::Begin => { - let obj = vm.peek(); - let method = obj - .borrow() - .get_vtable_attr(obj.clone(), "to_bool") - .expect("no to_bool"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => { - let value = vm.peek().borrow().is_truthy(); - Bool::create(!value).into() - } - _ => unreachable!(), - } + pub(crate) fn not(vm: &mut Vm) -> ObjP { + let obj = vm.peek(); + let method = obj + .borrow() + .get_vtable_attr(obj.clone(), "to_bool") + .expect("no to_bool"); + let result = vm.call(method, &[]); + let result_borrowed = result.borrow(); + Bool::create(!result_borrowed.is_truthy()) } // // Not implemented // - pub(crate) fn not_implemented_un(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn not_implemented_un(vm: &mut Vm) -> ObjP { let fname = &vm.frame().name; // TODO Obj::not_implemented_un - throw an exception of some kind for not // implemented/not available errors on unary operators @@ -420,7 +390,7 @@ impl Obj { ) } - pub(crate) fn not_implemented_bin(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn not_implemented_bin(vm: &mut Vm) -> ObjP { // TODO Obj::not_implemented_un - throw an exception of some kind for not // implemented/not available errors on unary operators // BLOCKED-ON: exceptions @@ -432,63 +402,38 @@ impl Obj { // Do call // - pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { + pub(crate) fn do_call(vm: &mut Vm) -> ObjP { let ty = vm.frame_stack()[0].clone(); - match state { - FunctionState::Begin => { - let argc = vm.frame_stack().len() - 1; + let argc = vm.frame_stack().len() - 1; + let mut obj = Obj::new(); + obj.set_attr("__ty__", ty.clone()); + obj.instantiate(); + let obj_ptr = make_ptr(obj); + let init = obj_ptr + .borrow() + .get_vtable_attr(obj_ptr.clone(), "__init__") + .expect("no __init__"); - let mut obj = Obj::new(); - obj.set_attr("__ty__", ty.clone()); - obj.instantiate(); - - let obj_ptr = make_ptr(obj); - let init = obj_ptr - .borrow() - .get_vtable_attr(obj_ptr.clone(), "__init__") - .expect("no __init__"); - - // check arity against __init__ function - // TODO Obj::do_call - need to throw an exception when __init__ arity does not - // match what was passed to __call__ - // Also need to throw an exception when __init__ is not a - // function or doesn't have an arity - // BLOCKED-ON: exceptions - if let Some(arity) = init.borrow().arity() { - if argc != arity as usize { - todo!( + if let Some(arity) = init.borrow().arity() { + if argc != arity as usize { + todo!( "TODO - throw an exception when {}.__call__ argc does not match the __init__ arity", obj_ptr.borrow().ty_name() ); - } - } else { - todo!("TODO - throw an exception when __init__ member does not have an arity or is not a function"); - } - - vm.push(obj_ptr); - vm.push(init.clone()); - // duplicate arguments that were pushed to the frame - for i in 0..argc { - let arg = vm.frame_stack()[i + 1].clone(); - vm.push(arg); - } - - init.borrow().call(vm, argc as Argc); - - FunctionResult::Yield(0) } - FunctionState::Resume(0) => { - vm.pop(); - FunctionResult::Return - } - _ => unreachable!(), + } else { + todo!("TODO - throw an exception when __init__ member does not have an arity or is not a function"); } + + let argv = Vec::from(&vm.frame_stack()[1..]); + vm.call(init, &argv); + obj_ptr } - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // no-op - Nil::create().into() + Nil::create() } } @@ -538,12 +483,12 @@ impl Object for Nil { // impl Nil { - pub(crate) fn do_call(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { - FunctionResult::ReturnPush(Nil::create()) + pub(crate) fn do_call(_vm: &mut Vm) -> ObjP { + Nil::create() } - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { - FunctionResult::ReturnPush(Nil::create()) + pub(crate) fn init(_vm: &mut Vm) -> ObjP { + Nil::create() } } diff --git a/src/obj/bool.rs b/src/obj/bool.rs index e87465d..71aa8be 100644 --- a/src/obj/bool.rs +++ b/src/obj/bool.rs @@ -58,19 +58,19 @@ impl Object for Bool { impl Bool { impl_do_call!(to_bool); - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // This is a no-op. We don't want the user-exposed `__init__` function to do anything, // instantiation is done in the `__call__` function. - FunctionResult::ReturnPush(Nil::create()) + Nil::create() } - pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_int(vm: &mut Vm) -> ObjP { let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value); - Int::create(bool_value as i64).into() + Int::create(bool_value as i64) } - pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_float(vm: &mut Vm) -> ObjP { let bool_value = with_obj_downcast(vm.frame_stack()[0].clone(), Bool::bool_value); - Float::create(bool_value as i64 as f64).into() + Float::create(bool_value as i64 as f64) } } diff --git a/src/obj/float.rs b/src/obj/float.rs index 384aedf..1a9db32 100644 --- a/src/obj/float.rs +++ b/src/obj/float.rs @@ -68,7 +68,7 @@ impl Object for Float { macro_rules! float_bin_op_math { ($function:ident, $op:tt) => { - pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn $function(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); @@ -86,14 +86,14 @@ macro_rules! float_bin_op_math { rhs.borrow().ty_name() ) }; - result.into() + result } } } macro_rules! float_bin_op_logical { ($function:ident, $op:tt) => { - pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn $function(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); @@ -111,7 +111,7 @@ macro_rules! float_bin_op_logical { rhs.borrow().ty_name() ) }; - result.into() + result } } } @@ -119,19 +119,19 @@ macro_rules! float_bin_op_logical { impl Float { impl_do_call!(to_float); - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // This is a no-op. We don't want the user-exposed `__init__` function to do anything, // instantiation is done in the `__call__` function. - FunctionResult::ReturnPush(Nil::create()) + Nil::create() } - pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_int(vm: &mut Vm) -> ObjP { let float_value = with_obj_downcast(vm.frame_stack()[0].clone(), Float::float_value); - Int::create(float_value as i64).into() + Int::create(float_value as i64) } - pub(crate) fn to_float(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { - FunctionResult::Return + pub(crate) fn to_float(vm: &mut Vm) -> ObjP { + vm.peek() } float_bin_op_math!(add, +); @@ -147,15 +147,15 @@ impl Float { float_bin_op_logical!(lt, <); float_bin_op_logical!(le, <=); - pub(crate) fn pos(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn pos(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let value = with_obj_downcast(lhs, Float::float_value); - Float::create(value.abs()).into() + Float::create(value.abs()) } - pub(crate) fn neg(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn neg(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let value = with_obj_downcast(lhs, Float::float_value); - Float::create(-value).into() + Float::create(-value) } } diff --git a/src/obj/function.rs b/src/obj/function.rs index 4159ea2..421d6f2 100644 --- a/src/obj/function.rs +++ b/src/obj/function.rs @@ -7,51 +7,11 @@ use crate::obj::macros::*; use crate::obj::prelude::*; 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), -} - //////////////////////////////////////////////////////////////////////////////// // BuiltinFunction //////////////////////////////////////////////////////////////////////////////// -pub type BuiltinFunctionPtr = fn(vm: &mut Vm, function_state: FunctionState) -> FunctionResult; +pub type BuiltinFunctionPtr = fn(vm: &mut Vm) -> ObjP; #[derive(Trace, Finalize)] pub struct BuiltinFunction { @@ -94,7 +54,7 @@ impl Debug for BuiltinFunction { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, - "", + "", self.name(), self.arity().unwrap(), self.function as *const BuiltinFunctionPtr as usize @@ -110,7 +70,7 @@ impl Object for BuiltinFunction { fn call(&self, vm: &mut Vm, argc: Argc) { let new_frame = Frame::new( Rc::clone(&self.name), - Function::Builtin(self.function, FunctionState::Begin), + Function::Builtin(self.function), vm.stack().len() - (argc as usize), ); vm.push_frame(new_frame); @@ -268,7 +228,7 @@ impl Debug for Method { }; write!( fmt, - "", + "", self.self_binding().borrow().ty_name(), function_name, self.function.borrow().arity().unwrap(), diff --git a/src/obj/int.rs b/src/obj/int.rs index d271b6c..f2db175 100644 --- a/src/obj/int.rs +++ b/src/obj/int.rs @@ -63,7 +63,7 @@ impl Object for Int { macro_rules! int_bin_op_math { ($function:ident, $op:tt) => { - pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn $function(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); @@ -81,14 +81,14 @@ macro_rules! int_bin_op_math { rhs.borrow().ty_name() ) }; - result.into() + result } } } macro_rules! int_bin_op_logical { ($function:ident, $op:tt) => { - pub(crate) fn $function(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn $function(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); @@ -106,33 +106,33 @@ macro_rules! int_bin_op_logical { rhs.borrow().ty_name() ) }; - result.into() + result } } } impl Int { - pub(crate) fn to_int(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { - FunctionResult::Return + pub(crate) fn to_int(vm: &mut Vm) -> ObjP { + vm.peek() } - pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_float(vm: &mut Vm) -> ObjP { let int_value = with_obj_downcast(vm.frame_stack()[0].clone(), Int::int_value); - Float::create(int_value as f64).into() + Float::create(int_value as f64) } impl_do_call!(to_int); - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // This is a no-op. We don't want the user-exposed `__init__` function to do anything, // instantiation is done in the `__call__` function. - FunctionResult::ReturnPush(Nil::create()) + Nil::create() } int_bin_op_math!(add, +); int_bin_op_math!(sub, -); - pub(crate) fn mul(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn mul(vm: &mut Vm) -> ObjP { // can't bin_op_math this one because it needs the string case let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); @@ -155,7 +155,7 @@ impl Int { rhs.borrow().ty_name() ) }; - result.into() + result } // TODO Int::div - handle divide by zero @@ -171,15 +171,15 @@ impl Int { int_bin_op_logical!(lt, <); int_bin_op_logical!(le, <=); - pub(crate) fn pos(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn pos(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let value = with_obj_downcast(lhs, Int::int_value); - Int::create(value.abs()).into() + Int::create(value.abs()) } - pub(crate) fn neg(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn neg(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let value = with_obj_downcast(lhs, Int::int_value); - Int::create(-value).into() + Int::create(-value) } } diff --git a/src/obj/list.rs b/src/obj/list.rs index d05ef10..aaa0402 100644 --- a/src/obj/list.rs +++ b/src/obj/list.rs @@ -61,77 +61,44 @@ impl Object for List { // List function implementations //////////////////////////////////////////////////////////////////////////////// impl List { - pub(crate) fn to_repr(vm: &mut Vm, state: FunctionState) -> FunctionResult { - // This function is a bit more complicated than the rest because it needs to effectively - // loop over the elements and call them, without using a loop. Thus, we use a sort-of state - // machine with the function state. - // - // When we begin, we check if the list is empty. If that's the case, then we just return - // the "empty list" string. Otherwise, we push two "locals", the string we're building, and - // the current list index, to the stack. Then, we call `to_repr` on the first item in the - // list, and yield execution to the VM with state 0. - // - // When function resumes, we get the return value off the top of the stack, append it to - // the current string, increment the index, and continue, until the string is fully built. - - // This function needs to keep track of the string that we're building, plus the current - // index, on the VM stack. - let this_ptr = vm.frame_stack()[0].clone(); - let this = this_ptr.borrow(); - let this = this.as_any().downcast_ref::().unwrap(); - - match state { - FunctionState::Begin => { - // empty list, exit early - if this.list().len() == 0 { - return FunctionResult::ReturnPush(Str::create("[]")); - } - - let string = Str::create("["); - vm.push(string); - let index = Int::create(0); - vm.push(index); - - let item = this.list()[0].clone(); - let method = item - .borrow() - .get_vtable_attr(item.clone(), "to_repr") - .expect("no to_repr"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - FunctionState::Resume(0) => { - let build_str = vm.frame_stack()[1].clone(); - // putting the "1 +" in front so we don't forget that it's there - let index = - 1 + with_obj_downcast(vm.frame_stack()[2].clone(), Int::int_value) as usize; - let repr_str = vm.pop(); - - if index == this.list().len() { - // if this is the last item in the list, then we're done - let new_str = format!("{}{}]", build_str.borrow(), repr_str.borrow()); - Str::create(new_str).into() - } else { - // otherwise, continue building the string and calling to_repr - let new_str = format!("{}{}, ", build_str.borrow(), repr_str.borrow()); - vm.frame_stack_mut()[1] = Str::create(new_str); - vm.frame_stack_mut()[2] = Int::create(index as i64); - let item = this.list()[index].clone(); - let method = item - .borrow() - .get_vtable_attr(item.clone(), "to_repr") - .expect("no to_repr"); - vm.push(method.clone()); - method.borrow().call(vm, 0); - FunctionResult::Yield(0) - } - } - _ => unreachable!(), + pub(crate) fn to_repr(vm: &mut Vm) -> ObjP { + let this = vm.frame_stack()[0].clone(); + let this_borrowed = this.borrow(); + let list_obj = this_borrowed.as_any().downcast_ref::().unwrap(); + if list_obj.list().len() == 0 { + return Str::create("[]"); } + + let mut repr = "[".to_string(); + + // first item + { + let first = list_obj.list()[0].clone(); + let method = first + .borrow() + .get_vtable_attr(first.clone(), "to_repr") + .expect("no to_repr"); + let result = vm.call(method, &[]); + repr += &format!("{}", result.borrow()); + } + + // remaining items + for obj in &list_obj.list()[1..] { + repr += ", "; + let method = obj + .borrow() + .get_vtable_attr(obj.clone(), "to_repr") + .expect("no to_repr"); + let result = vm.call(method, &[]); + repr += &format!("{}", result.borrow()); + } + + repr += "]"; + + return Str::create(repr); } - pub(crate) fn to_list(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_list(vm: &mut Vm) -> ObjP { // create a clone of this list let this = vm.frame_stack()[0].clone(); let list_items = with_obj_downcast(this, |list: &List| list.list().clone()); @@ -140,13 +107,13 @@ impl List { impl_do_call!(to_list); - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // This is a no-op. We don't want the user-exposed `__init__` function to do anything, // instantiation is done in the `__call__` function. - FunctionResult::ReturnPush(Nil::create()) + Nil::create() } - pub(crate) fn index(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn index(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let index_obj = vm.frame_stack()[1].clone(); @@ -175,23 +142,23 @@ impl List { } }); - item.into() + item } - pub(crate) fn len(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn len(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let len = with_obj_downcast(this, |list: &List| list.list().len()); - Int::create(len as i64).into() + Int::create(len as i64) } - pub(crate) fn push(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn push(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let arg = vm.frame_stack()[1].clone(); with_obj_downcast_mut(this, |list: &mut List| list.list_mut().push(arg)); - Nil::create().into() + Nil::create() } - pub(crate) fn pop(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn pop(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let last = if let Some(last) = with_obj_downcast_mut(this, |list: &mut List| list.list_mut().pop()) @@ -203,10 +170,10 @@ impl List { todo!("throw an exception when the list is empty and there is nothing to pop") }; - last.into() + last } - pub(crate) fn extend(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn extend(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let arg = vm.frame_stack()[1].clone(); @@ -224,6 +191,6 @@ impl List { list.list_mut().extend(list_extension); }); - Nil::create().into() + Nil::create() } } diff --git a/src/obj/macros.rs b/src/obj/macros.rs index 9c6946a..8d30fb0 100644 --- a/src/obj/macros.rs +++ b/src/obj/macros.rs @@ -52,38 +52,23 @@ macro_rules! impl_create { macro_rules! impl_do_call { ($name:ident) => { - pub(crate) fn do_call( - vm: &mut $crate::vm::Vm, - state: $crate::obj::function::FunctionState, - ) -> $crate::obj::function::FunctionResult { - match state { - $crate::obj::function::FunctionState::Begin => { - // get the top item off the stack and call to_float on it - let arg = vm.peek(); - let method = if let Some(method) = - arg.borrow().get_vtable_attr(arg.clone(), stringify!($name)) - { - method - } else { - // TODO builtins::do_call - throw exception when target doesn't have a - // to_$name method - // BLOCKED-ON: exceptions - todo!( - concat!("{} does not have a ", stringify!($name), " method"), - arg.borrow().ty_name() - ); - }; - vm.push(method.clone()); - method.borrow().call(vm, 0); + pub(crate) fn do_call(vm: &mut $crate::vm::Vm) -> ObjP { + let arg = vm.frame_stack().last().unwrap().clone(); + let method = if let Some(method) = + arg.borrow().get_vtable_attr(arg.clone(), stringify!($name)) + { + method + } else { + // TODO builtins::do_call - throw exception when target doesn't have a + // to_$name method + // BLOCKED-ON: exceptions + todo!( + concat!("{} does not have a ", stringify!($name), " method"), + arg.borrow().ty_name() + ); + }; - // resume execution - $crate::obj::function::FunctionResult::Yield(0) - } - $crate::obj::function::FunctionState::Resume(0) => { - $crate::obj::function::FunctionResult::Return - } - _ => unreachable!(), - } + vm.call(method, &[]) } }; } diff --git a/src/obj/str.rs b/src/obj/str.rs index a303fee..4aafa8b 100644 --- a/src/obj/str.rs +++ b/src/obj/str.rs @@ -63,59 +63,59 @@ impl Object for Str { impl Str { impl_do_call!(to_str); - pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn init(_vm: &mut Vm) -> ObjP { // This is a no-op. We don't want the user-exposed `__init__` function to do anything, // instantiation is done in the `__call__` function. - FunctionResult::ReturnPush(Nil::create()) + Nil::create() } - pub(crate) fn to_str(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_str(vm: &mut Vm) -> ObjP { // top item of the stack should just be ourselves, so return immediately - FunctionResult::Return + vm.peek() } - pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_int(vm: &mut Vm) -> ObjP { let parsed: Result = with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { str_inst.str_value().parse() }); match parsed { - Ok(int) => Int::create(int).into(), + Ok(int) => Int::create(int), // TODO Str::to_int - throw an exception when we fail to parse an integer // BLOCKED-ON - exceptions Err(e) => todo!("error parsing string to an integer: {}", e), } } - pub(crate) fn to_float(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_float(vm: &mut Vm) -> ObjP { let parsed: Result = with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { str_inst.str_value().parse() }); match parsed { - Ok(float) => Float::create(float).into(), + Ok(float) => Float::create(float), // TODO Str::to_int - throw an exception when we fail to parse an integer // BLOCKED-ON - exceptions Err(e) => todo!("error parsing string to a float: {}", e), } } - pub(crate) fn to_list(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn to_list(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let list_items = with_obj_downcast(this, |str: &Str| { str.str_value().chars().map(|c| Str::create(c)).collect() }); - List::create(list_items).into() + List::create(list_items) } - pub(crate) fn len(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn len(vm: &mut Vm) -> ObjP { let len = with_obj_downcast(vm.frame_stack()[0].clone(), |str_inst: &Str| { str_inst.str_value().len() as i64 }); - Int::create(len).into() + Int::create(len) } - pub(crate) fn add(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn add(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); if !obj_is_inst::(&rhs) { @@ -128,10 +128,10 @@ impl Str { } let new = format!("{}{}", lhs.borrow(), rhs.borrow()); - Str::create(new).into() + Str::create(new) } - pub(crate) fn mul(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn mul(vm: &mut Vm) -> ObjP { let lhs = vm.frame_stack()[0].clone(); let rhs = vm.frame_stack()[1].clone(); let repeat_count = if let Some(int_inst) = rhs.borrow().as_any().downcast_ref::() { @@ -146,10 +146,10 @@ impl Str { }; let repeat_count = repeat_count.max(0) as usize; let new = format!("{}", lhs.borrow()).repeat(repeat_count); - Str::create(new).into() + Str::create(new) } - pub(crate) fn index(vm: &mut Vm, _state: FunctionState) -> FunctionResult { + pub(crate) fn index(vm: &mut Vm) -> ObjP { let this = vm.frame_stack()[0].clone(); let index_obj = vm.frame_stack()[1].clone(); diff --git a/src/obj/ty.rs b/src/obj/ty.rs index 932bcb4..2838b69 100644 --- a/src/obj/ty.rs +++ b/src/obj/ty.rs @@ -91,6 +91,7 @@ impl Object for Ty { .vtable .get("__call__") .expect("Why does a type not have a __call__ member?"); + function.borrow().call(vm, argc + 1); } diff --git a/src/vm.rs b/src/vm.rs index a9ffbda..1b266ad 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -71,7 +71,7 @@ pub struct Chunk { pub(crate) enum Function { Chunk(Rc), Module(Rc), - Builtin(BuiltinFunctionPtr, FunctionState), + Builtin(BuiltinFunctionPtr), } #[derive(Debug)] @@ -158,7 +158,7 @@ impl<'c> Vm<'c> { pub fn chunk(&self) -> Option<&Chunk> { match &self.frame().function { Function::Chunk(chunk) | Function::Module(chunk) => Some(chunk), - Function::Builtin(_, _) => None, + Function::Builtin(_) => None, } } @@ -278,34 +278,11 @@ impl<'c> Vm<'c> { ip += 1; op } - Function::Builtin(function, state) => { + Function::Builtin(function) => { // 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 - } - } + let result = (function)(self); + self.push(result); + Op::Return } }; self.set_ip(ip); @@ -327,7 +304,17 @@ impl<'c> Vm<'c> { self.stack.push(value); } - pub fn run(&mut self) { + pub fn call(&mut self, fun: ObjP, argv: &[ObjP]) -> ObjP { + self.push(fun.clone()); + for arg in argv { + self.push(arg.clone()); + } + fun.borrow().call(self, argv.len() as Argc); + self.resume(); + self.pop() + } + + pub fn resume(&mut self) { loop { let op = self.dispatch(); @@ -450,6 +437,10 @@ impl<'c> Vm<'c> { // also pop the function object off of the stack self.stack.pop(); self.push(return_value); + + if matches!(self.frame().function, Function::Builtin(_)) { + return; + } } Op::CloseOver { depth, slot } => { // since we're closing over a value, and functions ultimately come from