diff --git a/src/builtins.rs b/src/builtins.rs index 8de9fed..1058ac9 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -45,6 +45,7 @@ pub(crate) fn println(vm: &mut Vm, state: FunctionState) -> FunctionResult { .borrow() .get_vtable_attr(obj.clone(), "to_str") .expect("no to_str"); + vm.push(method.clone()); method.borrow().call(vm, 0); FunctionResult::Yield(0) } @@ -64,6 +65,7 @@ pub(crate) fn print(vm: &mut Vm, state: FunctionState) -> FunctionResult { .borrow() .get_vtable_attr(obj.clone(), "to_str") .expect("no to_str"); + vm.push(method.clone()); method.borrow().call(vm, 0); FunctionResult::Yield(0) } @@ -93,6 +95,7 @@ impl BaseObj { .get_vtable_attr(this.clone(), "to_repr") .clone() .expect("no to_repr"); + vm.push(method.clone()); method.borrow().call(vm, 0); FunctionResult::Yield(0) } @@ -167,6 +170,7 @@ impl BaseObj { .borrow() .get_vtable_attr(obj.clone(), "to_bool") .expect("no to_bool"); + vm.push(method.clone()); method.borrow().call(vm, 0); FunctionResult::Yield(0) } @@ -207,6 +211,38 @@ impl BaseObj { // Str implementations //////////////////////////////////////////////////////////////////////////////// impl Str { + pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + FunctionState::Begin => { + // get the top item off the stack and call to_str on it + let arg = vm.peek(); + let method = + if let Some(method) = arg.borrow().get_vtable_attr(arg.clone(), "to_str") { + method + } else { + // TODO Str::do_call - throw exception when target doesn't have a to_str() + // method + // BLOCKED-ON: exceptions + todo!("{} does not have a to_str() method", arg.borrow().ty_name()); + // NOTE - every object should have to_str, right? + }; + vm.push(method.clone()); + method.borrow().call(vm, 0); + + // resume execution + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => FunctionResult::Return, + _ => unreachable!(), + } + } + + pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + // 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()) + } + pub(crate) fn to_str(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { // top item of the stack should just be ourselves, so return immediately FunctionResult::Return @@ -344,6 +380,38 @@ impl Int { Float::create(int_value as f64).into() } + pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + FunctionState::Begin => { + // Pop the argument and call `to_int` on it + let arg = vm.peek(); + + let method = + if let Some(method) = arg.borrow().get_vtable_attr(arg.clone(), "to_int") { + method + } else { + // TODO Int::do_call - throw exception when arg doesn't have to_int() + // method + // BLOCKED-ON: exceptions + todo!("{} does not have a to_int() method", arg.borrow().ty_name()); + }; + + vm.push(method.clone()); + method.borrow().call(vm, 0); + + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => FunctionResult::Return, + _ => unreachable!(), + } + } + + pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + // 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()) + } + int_bin_op_math!(add, +); int_bin_op_math!(sub, -); @@ -454,6 +522,40 @@ macro_rules! float_bin_op_logical { } impl Float { + pub(crate) fn do_call(vm: &mut Vm, state: FunctionState) -> FunctionResult { + match state { + 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(), "to_float") { + method + } else { + // TODO Float::do_call - throw exception when target doesn't have a to_float() + // method + // BLOCKED-ON: exceptions + todo!( + "{} does not have a to_float() method", + arg.borrow().ty_name() + ); + }; + vm.push(method.clone()); + method.borrow().call(vm, 0); + + // resume execution + FunctionResult::Yield(0) + } + FunctionState::Resume(0) => FunctionResult::Return, + _ => unreachable!(), + } + } + + pub(crate) fn init(_vm: &mut Vm, _state: FunctionState) -> FunctionResult { + // 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()) + } + pub(crate) fn to_int(vm: &mut Vm, _state: FunctionState) -> FunctionResult { let float_value = with_obj_downcast(vm.frame_stack()[0].clone(), Float::float_value); Int::create(float_value as i64).into() diff --git a/src/obj.rs b/src/obj.rs index e5484a5..0af5dd3 100644 --- a/src/obj.rs +++ b/src/obj.rs @@ -126,6 +126,11 @@ pub fn init_types() { to_float => BuiltinFunction::create("to_float", BaseObj::not_implemented_un, 1), len => BuiltinFunction::create("len", BaseObj::not_implemented_un, 1), + // Constructor + // TODO Ty::do_call, Ty::init - implement these methods + __call__ => BuiltinFunction::create("__call__", BaseObj::not_implemented_un, 1), + __init__ => BuiltinFunction::create("__init__", BaseObj::not_implemented_un, 1), + // Operators __add__ => BuiltinFunction::create("__add__", BaseObj::not_implemented_bin, 2), __sub__ => BuiltinFunction::create("__sub__", BaseObj::not_implemented_bin, 2), @@ -143,7 +148,9 @@ pub fn init_types() { __neg__ => BuiltinFunction::create("__neg__", BaseObj::not_implemented_un, 1), __not__ => BuiltinFunction::create("__not__", BaseObj::not, 1), }, - Obj { }, + Obj { + //__call__ => BuiltinFunction::create("__call__", + }, Str { // Conversion methods to_str => BuiltinFunction::create("to_str", Str::to_str, 1), @@ -151,6 +158,10 @@ pub fn init_types() { to_float => BuiltinFunction::create("to_float", Str::to_float, 1), len => BuiltinFunction::create("len", Str::len, 1), + // Constructor + __call__ => BuiltinFunction::create("__call__", Str::do_call, 2), + __init__ => BuiltinFunction::create("__init__", Str::init, 2), + // Operators __add__ => BuiltinFunction::create("__add__", Str::add, 2), __mul__ => BuiltinFunction::create("__mul__", Str::mul, 2), @@ -161,6 +172,10 @@ pub fn init_types() { to_int => BuiltinFunction::create("to_int", Int::to_int, 1), to_float => BuiltinFunction::create("to_float", Int::to_float, 1), + // Constructor + __call__ => BuiltinFunction::create("__call__", Int::do_call, 2), + __init__ => BuiltinFunction::create("__init__", Int::init, 2), + // Operators __add__ => BuiltinFunction::create("__add__", Int::add, 2), __sub__ => BuiltinFunction::create("__sub__", Int::sub, 2), @@ -179,6 +194,10 @@ pub fn init_types() { to_int => BuiltinFunction::create("to_int", Float::to_int, 1), to_float => BuiltinFunction::create("to_float", Float::to_float, 1), + // Constructor + __call__ => BuiltinFunction::create("__call__", Float::do_call, 2), + __init__ => BuiltinFunction::create("__init__", Float::init, 2), + // Operators __add__ => BuiltinFunction::create("__add__", Float::add, 2), __sub__ => BuiltinFunction::create("__sub__", Float::sub, 2), @@ -279,7 +298,14 @@ pub trait Object: Debug + Display + Any + Trace { fn call(&self, _vm: &mut Vm, _argc: Argc) { // TODO Object::call - need to handle "this object cannot be called" errors // BLOCKED-ON: exceptions - todo!("Raise some kind of not implemented/not callable error for non-callable objects") + /* + let method = self.get_vtable_attr("__call__").expect( + "TODO: Raise some kind of not implemented/not callable error for non-callable objects", + ); + */ + todo!( + "TODO: Raise some kind of not implemented/not callable error for non-callable objects" + ) } fn is_truthy(&self) -> bool { @@ -428,18 +454,18 @@ impl Ty { } } - impl_create!(name: impl ToString); - pub fn name(&self) -> &Rc { &self.name } + + impl_create!(name: impl ToString); } impl Debug for Ty { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, - "", + "", self.name, (self as *const _ as usize) ) @@ -448,12 +474,7 @@ impl Debug for Ty { impl Display for Ty { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!( - fmt, - "", - self.name, - (self as *const _ as usize) - ) + Debug::fmt(self, fmt) } } @@ -472,6 +493,43 @@ impl Object for Ty { } } + fn call(&self, vm: &mut Vm, argc: Argc) { + // TODO Object::call - need to handle "this object cannot be called" errors + // BLOCKED-ON: exceptions + + // I don't think there's any way we could call this *without* it being a method. + // If you do e.g. `Int.__call__`, Int is an object, so it should be doing `__call__` as a + // vtable value. + + if cfg!(debug_assertions) { + let index = vm.stack().len() - 1 - argc as usize; + let this = vm.stack()[index].clone(); + assert!( + std::ptr::addr_eq(&*this.borrow(), self), + "calling {}.__call__ on type that is not ourselves", + self.ty_name() + ); + } + + let function = self + .vtable + .get("__call__") + .expect("Why does a type not have a __call__ member?"); + function.borrow().call(vm, argc); + } + + fn arity(&self) -> Option { + // HACK XXX NOTE Ty __call__ arity : + // We need to tread carefully here. Normally, `__call__` would be wrapped as a method. + // However, we have to get the `__call__` member directly from the vtable. + // We are subtracting 1 from the arity, because whenever it *does* become a method, the + // arity will match when we call `Ty` directly. + self.vtable + .get("__call__") + .and_then(|function| function.borrow().arity()) + .map(|n| n - 1) + } + impl_base_obj!(Ty); } @@ -536,7 +594,7 @@ impl Object for Str { #[derive(Trace, Finalize)] pub struct Int { base: BaseObj, - int_value: i64, + pub(crate) int_value: i64, } impl Int { diff --git a/src/vm.rs b/src/vm.rs index da4edc8..26bc83b 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -357,8 +357,10 @@ impl Vm { // does not match the function's arity // BLOCKED-ON: exceptions todo!( - "throw an error because we passed the wrong number of arguments to {}", - fun_ptr.borrow() + "throw an error because we passed the wrong number of arguments to {} (got {}, expected {})", + fun_ptr.borrow(), + argc, + arity ); } fun_ptr.borrow().call(self, argc as Argc);