// TODO obj.rs - remove the warning suppression #![allow(dead_code)] mod macros; // Leave this comment here - macros must come first pub mod function; use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Display}; use std::rc::Rc; use gc::{Finalize, Gc, GcCell, Trace}; use crate::obj::function::*; use crate::obj::macros::*; use crate::vm::{Argc, Chunk, Vm}; pub type Ptr = Gc>; pub type ObjP = Ptr; pub type Attrs = HashMap; // TODO obj::with_obj_downcast - optimize downcasts of "known" types with an unchecked downcast /// Downcast an object pointer to a concrete type, and do something with that object. pub fn with_obj_downcast(ptr: ObjP, closure: impl FnOnce(&T) -> Out) -> Out where T: Obj + 'static, { let borrowed = ptr.borrow(); if let Some(obj) = borrowed.as_any().downcast_ref::() { closure(obj) } else { panic!( "could not downcast '{:?}' to {}", ptr, std::any::type_name::() ) } } /// Downcast an object pointer to a concrete type, and do something with that object. pub fn with_obj_downcast_mut(ptr: ObjP, closure: impl FnOnce(&mut T) -> Out) -> Out where T: Obj + 'static, { let mut borrowed = ptr.borrow_mut(); if let Some(obj) = borrowed.as_any_mut().downcast_mut::() { closure(obj) } else { panic!( "could not downcast '{:?}' to {}", ptr, std::any::type_name::() ) } } pub fn obj_is_inst(ptr: &ObjP) -> bool where T: Obj + 'static, { let borrowed = ptr.borrow(); borrowed.as_any().downcast_ref::().is_some() } pub fn upcast_obj(ptr: Ptr) -> ObjP { unsafe { let ptr = Ptr::into_raw(ptr) as *const GcCell; Ptr::from_raw(ptr) } } pub fn init_types(builtins: &mut HashMap) { #![allow(non_snake_case)] macro_rules! types { ( base_type: $base_type:ident, $( $name:ident { $( $vtable_name:ident => $vtable_value:expr ),* $(,)? } ),* $(,)? ) => {{ $( let $name = make_ptr(TypeInst::new(stringify!($name))); )* // We have to instantiate these objects all by hand. This is because the `instantiate` // function does some stuff that may accidentally cause infinite recursion while we are // setting up these fundamental types. $({ let base_type = $base_type.clone(); $name.borrow_mut().set_attr("__type__", base_type); with_obj_downcast_mut($name.clone(), |type_inst: &mut TypeInst| { type_inst.base.is_instantiated = true; }); //$name.borrow_mut().base.is_instantiated = true; })* $( builtins.insert(stringify!($name).to_string(), $name.clone()); )* $({ $( let vtable_name = stringify!($vtable_name); let vtable_value = $vtable_value; with_obj_downcast_mut($name.clone(), |type_inst: &mut TypeInst| { type_inst.vtable.insert(vtable_name.to_string(), vtable_value); }); )* })* }}; } types! { // base type base_type: Type, // type definitions Type { // Method conversion to_str => builtins.create_builtin_function("to_str", BaseObjInst::to_str, 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), len => builtins.create_builtin_function("len", BaseObjInst::not_implemented_un, 1), // Operators __add__ => builtins.create_builtin_function("__add__", BaseObjInst::not_implemented_bin, 2), __sub__ => builtins.create_builtin_function("__sub__", BaseObjInst::not_implemented_bin, 2), __mul__ => builtins.create_builtin_function("__mul__", BaseObjInst::not_implemented_bin, 2), __div__ => builtins.create_builtin_function("__div__", BaseObjInst::not_implemented_bin, 2), __and__ => builtins.create_builtin_function("__and__", BaseObjInst::and, 2), __or__ => builtins.create_builtin_function("__or__", BaseObjInst::or, 2), __ne__ => builtins.create_builtin_function("__ne__", BaseObjInst::ne, 2), __eq__ => builtins.create_builtin_function("__eq__", BaseObjInst::eq, 2), __gt__ => builtins.create_builtin_function("__gt__", BaseObjInst::not_implemented_bin, 2), __ge__ => builtins.create_builtin_function("__ge__", BaseObjInst::not_implemented_bin, 2), __lt__ => builtins.create_builtin_function("__lt__", BaseObjInst::not_implemented_bin, 2), __le__ => builtins.create_builtin_function("__le__", BaseObjInst::not_implemented_bin, 2), __pos__ => builtins.create_builtin_function("__pos__", BaseObjInst::not_implemented_un, 1), __neg__ => builtins.create_builtin_function("__neg__", BaseObjInst::not_implemented_un, 1), __not__ => builtins.create_builtin_function("__not__", BaseObjInst::not, 1), }, Obj { }, Str { to_str => builtins.create_builtin_function("to_str", StrInst::to_str, 1), to_repr => builtins.create_builtin_function("to_repr", StrInst::to_repr, 1), len => builtins.create_builtin_function("len", StrInst::len, 1), // Operators __add__ => builtins.create_builtin_function("__add__", StrInst::add, 2), __mul__ => builtins.create_builtin_function("__mul__", StrInst::mul, 2), // .lower, .upper, .slice, etc }, Int { // Operators __add__ => builtins.create_builtin_function("__add__", IntInst::add, 2), __sub__ => builtins.create_builtin_function("__sub__", IntInst::sub, 2), __mul__ => builtins.create_builtin_function("__mul__", IntInst::mul, 2), __div__ => builtins.create_builtin_function("__div__", IntInst::div, 2), __gt__ => builtins.create_builtin_function("__gt__", IntInst::gt, 2), __ge__ => builtins.create_builtin_function("__ge__", IntInst::ge, 2), __lt__ => builtins.create_builtin_function("__lt__", IntInst::lt, 2), __le__ => builtins.create_builtin_function("__le__", IntInst::le, 2), __pos__ => builtins.create_builtin_function("__pos__", IntInst::pos, 1), __neg__ => builtins.create_builtin_function("__neg__", IntInst::neg, 1), }, Float { }, Bool { }, Nil { }, BuiltinFunction { }, UserFunction { }, Method { }, } } pub trait ObjFactory { fn builtins(&self) -> &HashMap; fn create_obj(&self) -> ObjP { ObjInst::create(self.builtins().get("Obj").unwrap().clone()) } fn create_type(&self, name: impl ToString) -> ObjP { TypeInst::create(self.builtins().get("Type").unwrap().clone(), name) } fn create_str(&self, str_value: impl ToString) -> ObjP { StrInst::create(self.builtins().get("Str").unwrap().clone(), str_value) } fn create_int(&self, int_value: i64) -> ObjP { IntInst::create(self.builtins().get("Int").unwrap().clone(), int_value) } fn create_float(&self, float_value: f64) -> ObjP { FloatInst::create(self.builtins().get("Float").unwrap().clone(), float_value) } fn create_bool(&self, bool_value: bool) -> ObjP { BoolInst::create(self.builtins().get("Bool").unwrap().clone(), bool_value) } fn create_nil(&self) -> ObjP { NilInst::create(self.builtins().get("Nil").unwrap().clone()) } fn create_builtin_function( &self, name: impl ToString, function: BuiltinFunctionPtr, arity: Argc, ) -> ObjP { BuiltinFunctionInst::create( self.builtins().get("BuiltinFunction").unwrap().clone(), name, function, arity, ) } fn create_user_function(&self, chunk: Chunk, arity: Argc) -> ObjP { UserFunctionInst::create( self.builtins().get("UserFunction").unwrap().clone(), chunk, arity, ) } fn create_method(&self, self_binding: ObjP, function: ObjP) -> ObjP { MethodInst::create( self.builtins().get("Method").unwrap().clone(), self_binding, function, ) } } impl ObjFactory for HashMap { fn builtins(&self) -> &Self { self } } /// Convenience function for creating pointers, in case the `Arc>` pointer type has to /// change. /// /// I would implement this as a `From` but it doesn't seem to work for a foreign type, and I'm /// not sure why. pub fn make_ptr(obj: T) -> ObjP { upcast_obj(Ptr::new(GcCell::new(obj))) } //////////////////////////////////////////////////////////////////////////////// // Obj //////////////////////////////////////////////////////////////////////////////// pub trait Obj: Debug + Display + Any + Trace { fn instantiate(&mut self, ty: ObjP); fn is_instantiated(&self) -> bool; fn attrs(&self) -> &Attrs; fn attrs_mut(&mut self) -> &mut Attrs; fn set_attr(&mut self, name: &str, value: ObjP) { self.attrs_mut().insert(name.to_string(), value); } fn get_attr(&self, name: &str) -> Option { // check attrs, then check vtable self.attrs().get(name).map(Ptr::clone) } /// This tries to get an attribute using these sources: /// * `self.attrs()` /// * `self.type_inst().vtable` /// * `self.type_inst().type_inst().vtable` - our type's type's vtable (search up the type /// inheritance tree) /// /// If the value is found in a vtable, then it is inserted as an attribute. If it is a /// function (BuiltinFunctionInst, UserFunctionInst), then it is wrapped in a `MethodInst` /// first. fn get_attr_lazy(&mut self, self_ptr: ObjP, method_ty: ObjP, name: &str) -> Option { let attr = self.get_attr(name); if attr.is_some() { return attr; } let mut type_inst = self.type_inst(); loop { let vtable_entry = with_obj_downcast_mut(type_inst.clone(), |type_inst: &mut TypeInst| { type_inst.vtable.get(name).cloned() }) .map(|vtable_entry| { let ptr = if obj_is_inst::(&vtable_entry) || obj_is_inst::(&vtable_entry) { MethodInst::create(method_ty.clone(), self_ptr.clone(), vtable_entry) } else { vtable_entry }; self.set_attr(name, ptr.clone()); ptr }); if vtable_entry.is_some() { return vtable_entry; } let type_inst_copy = type_inst.borrow().type_inst(); if type_inst.borrow().equals(&*type_inst_copy.borrow()) { return None; } type_inst = type_inst_copy; } } fn type_inst(&self) -> ObjP { self.get_attr("__type__").unwrap() } fn type_name(&self) -> Rc { with_obj_downcast(self.type_inst(), |type_inst: &TypeInst| { Rc::clone(&type_inst.name) }) } fn arity(&self) -> Option { None } fn call(&self, _vm: &mut Vm, _argc: Argc) { // TODO Obj::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") } fn is_truthy(&self) -> bool { true } fn equals(&self, other: &dyn Obj) -> bool; fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; } //////////////////////////////////////////////////////////////////////////////// // BaseObjInst //////////////////////////////////////////////////////////////////////////////// #[derive(Default, Debug, Trace)] pub(crate) struct BaseObjInst { attrs: Attrs, is_instantiated: bool, } impl Finalize for BaseObjInst { fn finalize(&self) {} } impl Clone for BaseObjInst { fn clone(&self) -> Self { Self { attrs: self.attrs.clone(), is_instantiated: self.is_instantiated, } } } impl Display for BaseObjInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "", (self as *const _ as usize)) } } impl Obj for BaseObjInst { fn instantiate(&mut self, ty: ObjP) { self.set_attr("__type__", ty); self.is_instantiated = true; } fn is_instantiated(&self) -> bool { self.is_instantiated } fn attrs(&self) -> &Attrs { &self.attrs } fn attrs_mut(&mut self) -> &mut Attrs { &mut self.attrs } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { // compare all attrs self.attrs.iter().all(|(k1, v1)| { other .attrs .get(k1) .map(|v2| v2.borrow().equals(&*v1.borrow())) .unwrap_or(false) }) && self.is_instantiated == other.is_instantiated } else { false } } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } //////////////////////////////////////////////////////////////////////////////// // ObjInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Trace)] pub struct ObjInst { base: BaseObjInst, } impl ObjInst { pub fn new() -> Self { Self { base: Default::default(), } } impl_create!(); } impl Finalize for ObjInst { fn finalize(&self) {} } impl Display for ObjInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "", (self as *const _ as usize)) } } impl Obj for ObjInst { fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self.base.equals(&other.base) } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // TypeInst //////////////////////////////////////////////////////////////////////////////// #[derive(Trace)] pub struct TypeInst { #[unsafe_ignore_trace] name: Rc, base: BaseObjInst, vtable: HashMap, } impl Finalize for TypeInst { fn finalize(&self) {} } impl TypeInst { pub fn new(name: impl ToString) -> Self { Self { name: Rc::new(name.to_string()), base: Default::default(), vtable: Default::default(), } } impl_create!(name: impl ToString); pub fn name(&self) -> &Rc { &self.name } } impl Debug for TypeInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, "", self.name, (self as *const _ as usize) ) } } impl Display for TypeInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!( fmt, "", self.name, (self as *const _ as usize) ) } } impl Obj for TypeInst { fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { // TODO TypeInst::equals : something more robust than this // Types should hold equality if they have the same name // the problem is that Type.get_attr("__type__") is going to return itself, so we have // to go through attributes to specially exclude to the __type__ attribute if it points // to ourself. // How do we detect that it's pointing to ourself? I suppose pointers are the way self.name == other.name } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // StrInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Trace)] pub struct StrInst { #[unsafe_ignore_trace] str_value: Rc, base: BaseObjInst, } impl StrInst { pub fn new(str_value: impl ToString) -> Self { Self { str_value: Rc::new(str_value.to_string()), base: Default::default(), } } impl_create!(str_value: impl ToString); pub fn str_value(&self) -> &Rc { &self.str_value } } impl Finalize for StrInst { fn finalize(&self) {} } impl Display for StrInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.str_value) } } impl Obj for StrInst { fn is_truthy(&self) -> bool { !self.str_value.is_empty() } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self.str_value == other.str_value } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // IntInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Trace)] pub struct IntInst { int_value: i64, base: BaseObjInst, } impl IntInst { pub fn new(int_value: i64) -> Self { Self { int_value, base: Default::default(), } } impl_create!(int_value: i64); pub fn int_value(&self) -> i64 { self.int_value } } impl Finalize for IntInst { fn finalize(&self) {} } impl Display for IntInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.int_value) } } impl Obj for IntInst { fn is_truthy(&self) -> bool { self.int_value != 0 } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self.int_value == other.int_value } else if let Some(other) = other.as_any().downcast_ref::() { self.int_value as f64 == other.float_value } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // FloatInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Trace)] pub struct FloatInst { float_value: f64, base: BaseObjInst, } impl FloatInst { pub fn new(float_value: f64) -> Self { Self { float_value, base: Default::default(), } } impl_create!(float_value: f64); pub fn float_value(&self) -> f64 { self.float_value } } impl Finalize for FloatInst { fn finalize(&self) {} } impl Display for FloatInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.float_value) } } impl Obj for FloatInst { fn is_truthy(&self) -> bool { self.float_value != 0.0 } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self.float_value == other.float_value } else if let Some(other) = other.as_any().downcast_ref::() { self.float_value == other.int_value as f64 } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // BoolInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Trace)] pub struct BoolInst { bool_value: bool, base: BaseObjInst, } impl BoolInst { pub fn new(bool_value: bool) -> Self { Self { bool_value, base: Default::default(), } } impl_create!(bool_value: bool); pub fn bool_value(&self) -> bool { self.bool_value } } impl Finalize for BoolInst { fn finalize(&self) {} } impl Display for BoolInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}", self.bool_value) } } impl Obj for BoolInst { fn is_truthy(&self) -> bool { self.bool_value } fn equals(&self, other: &dyn Obj) -> bool { if let Some(other) = other.as_any().downcast_ref::() { self.bool_value == other.bool_value } else { false } } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // NilInst //////////////////////////////////////////////////////////////////////////////// #[derive(Debug, Default, Trace)] pub struct NilInst { base: BaseObjInst, } impl NilInst { pub fn new() -> Self { Default::default() } impl_create!(); } impl Finalize for NilInst { fn finalize(&self) {} } impl Display for NilInst { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "nil") } } impl Obj for NilInst { fn is_truthy(&self) -> bool { false } fn equals(&self, other: &dyn Obj) -> bool { other.as_any().downcast_ref::().is_some() } impl_base_obj!(); } //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// #[test] fn test_new_objects() { let mut builtins = HashMap::new(); init_types(&mut builtins); let type_value = builtins.create_type("Type"); assert_eq!(&*type_value.borrow().type_name(), "Type"); let str_value = builtins.create_str("asdfasdfasdfasdfasdf"); assert_eq!(&*str_value.borrow().type_name(), "Str"); let int_value = builtins.create_int(1234); assert_eq!(&*int_value.borrow().type_name(), "Int"); let float_value = builtins.create_float(1234.5678); assert_eq!(&*float_value.borrow().type_name(), "Float"); let nil_value = builtins.create_nil(); assert_eq!(&*nil_value.borrow().type_name(), "Nil"); } #[test] fn test_obj_equals() { let mut builtins = HashMap::new(); init_types(&mut builtins); let int1 = builtins.create_int(1234); let int2 = builtins.create_int(1234); assert!(int1.borrow().equals(&*int2.borrow())); assert!(int2.borrow().equals(&*int1.borrow())); let float1 = builtins.create_float(1234.0); assert!(int1.borrow().equals(&*float1.borrow())); assert!(float1.borrow().equals(&*int2.borrow())); // self-equality let str1 = builtins.create_str("1234"); assert!(str1.borrow().equals(&*str1.borrow())); let str2 = builtins.create_str("1234"); assert!(str1.borrow().equals(&*str2.borrow())); assert!(str2.borrow().equals(&*str1.borrow())); assert!(!str1.borrow().equals(&*float1.borrow())); assert!(!str1.borrow().equals(&*int1.borrow())); let obj1 = builtins.create_obj(); let obj2 = builtins.create_obj(); assert!(obj1.borrow().equals(&*obj2.borrow())); // these objects aren't equal anymore obj1.borrow_mut().set_attr("my_attr", str2.clone()); assert!(!obj1.borrow().equals(&*obj2.borrow())); // but now they are! obj2.borrow_mut().set_attr("my_attr", str2.clone()); assert!(obj2.borrow().equals(&*obj1.borrow())); } #[test] fn test_obj_vtable() { let mut builtins = HashMap::new(); init_types(&mut builtins); let str1 = builtins.create_str("asdfasdfasdf"); let to_string_ptr = str1.borrow_mut().get_attr_lazy( str1.clone(), builtins.get("Method").unwrap().clone(), "to_str", ); assert!(to_string_ptr.is_some()); let to_string_ptr = to_string_ptr.unwrap(); assert!(obj_is_inst::(&to_string_ptr)); with_obj_downcast(to_string_ptr.clone(), |method: &MethodInst| { assert!(method.self_binding().borrow().equals(&*str1.borrow())); }); // now get the method's to_string ptr let method_to_string_ptr = to_string_ptr.borrow_mut().get_attr_lazy( to_string_ptr.clone(), builtins.get("Method").unwrap().clone(), "to_str", ); assert!(method_to_string_ptr.is_some()); // this is like doing "asdfasdfasdf".to_string().to_string() let method_to_string_ptr = method_to_string_ptr.unwrap(); assert!(obj_is_inst::(&method_to_string_ptr)); with_obj_downcast(method_to_string_ptr.clone(), |method: &MethodInst| { assert!(method .self_binding() .borrow() .equals(&*to_string_ptr.borrow())); }); }