Files
not-python-rust/src/obj.rs

1060 lines
27 KiB
Rust
Raw Normal View History

// TODO obj.rs - remove the warning suppression
#![allow(unused_variables, dead_code)]
use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Display};
use std::ptr;
use std::sync::{Arc, LazyLock, Mutex, RwLock};
use common_macros::hash_map;
use crate::vm::{Argc, Chunk, Frame, Vm};
pub type Ptr<T> = Arc<RwLock<T>>;
pub type ObjP = Ptr<dyn Obj + 'static>;
pub type Attrs = HashMap<String, ObjP>;
/// Downcast an object pointer to a concrete type, and do something with that object.
pub fn with_obj_downcast<T, Out>(ptr: ObjP, closure: impl FnOnce(&T) -> Out) -> Out
where
T: Obj + 'static,
{
let borrowed = ptr.try_read().expect("could not lock object for reading");
if let Some(obj) = borrowed.as_any().downcast_ref::<T>() {
closure(obj)
} else {
panic!(
"could not downcast '{:?}' to {}",
borrowed,
std::any::type_name::<T>()
)
}
}
pub fn obj_is_inst<T>(ptr: &ObjP) -> bool
where
T: Obj + 'static,
{
let borrowed = ptr.try_read().expect("could not lock object for reading");
borrowed.as_any().downcast_ref::<T>().is_some()
}
/// Builtin types macro
macro_rules! builtin_types {
(
$(
$type_name:ident => { $( $vtable_name:ident => $vtable_value:expr ),* $(,)? }
),+ $(,)?
) => {
pub static TYPES: LazyLock<RwLock<HashMap<String, Ptr<TypeInst>>>> = LazyLock::new(|| {
RwLock::new(hash_map! {
$(
stringify!($type_name).to_string() => make_ptr(TypeInst::new(stringify!($type_name)))
),+
})
});
static TYPE_SYSTEM_INIT: LazyLock<Mutex<bool>> = LazyLock::new(|| Mutex::new(false));
/// Initialize types.
///
/// This should only be called once.
fn init_base_types() {
// instantiate
$(
if stringify!($type_name) != "Type" {
let ty = Ptr::clone(&TYPES.try_read().unwrap()[stringify!($type_name)]);
ty.try_write().unwrap().instantiate();
}
)+
// __name__
$(
{
let name = StrInst::create(stringify!($type_name));
let ty = Ptr::clone(&TYPES.try_read().unwrap()[stringify!($type_name)]);
ty.try_write()
.unwrap()
.set_attr("__name__", name);
}
)+
// vtable
$(
{
let ptr = Ptr::clone(&TYPES.try_read().unwrap()[stringify!($type_name)]);
let ty = ptr.try_write().unwrap();
$(
ty.vtable.insert($vtable_name.into(), $vtable_value);
)*
}
)+
}
};
}
pub(crate) fn init_types() {
// Taking the lock here will lock the entire function from being run twice
// simulataneously and prevent race conditions.
//
// Race conditions really can only happen during testing so this is just a precaution.
let mut lock_guard = TYPE_SYSTEM_INIT.lock().unwrap();
if *lock_guard {
if cfg!(test) {
return;
} else {
panic!("do not initialize type system twice");
}
}
// Init type_type here
{
let type_type_ptr = Ptr::clone(&TYPES.try_read().unwrap()["Type"]);
let mut type_type = type_type_ptr.try_write().unwrap();
type_type.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Type"]) as ObjP,
);
type_type.base.is_instantiated = true;
}
// Init the rest of the types
init_base_types();
*lock_guard = true;
}
fn placeholder(_: &mut Vm, _: Vec<ObjP>) -> ObjP {
NilInst::create()
}
fn to_string(_: &mut Vm, args: Vec<ObjP>) -> ObjP {
let str_value = format!("{}", args[0].try_read().unwrap());
StrInst::create(str_value)
}
builtin_types! {
Type => {},
Obj => {},
Str => {},
Int => {},
Float => {},
Bool => {},
Nil => {},
BuiltinFunction => {},
UserFunction => {},
Method => {},
}
/// Convenience function for creating pointers, in case the `Arc<RwLock<T>>` pointer type has to
/// change.
///
/// I would implement this as a `From<T>` but it doesn't seem to work for a foreign type, and I'm
/// not sure why.
pub fn make_ptr<T: Obj>(obj: T) -> Ptr<T> {
Arc::new(RwLock::new(obj))
}
////////////////////////////////////////////////////////////////////////////////
// Obj
////////////////////////////////////////////////////////////////////////////////
pub trait Obj: Debug + Display + Any + Send + Sync {
fn instantiate(&mut self);
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<ObjP> {
self.attrs().get(name).map(Arc::clone)
}
fn type_inst(&self) -> ObjP {
self.get_attr("__type__").unwrap()
}
fn type_name(&self) -> Arc<String> {
with_obj_downcast(self.type_inst(), |type_inst: &TypeInst| {
Arc::clone(&type_inst.name)
})
}
fn arity(&self) -> Option<Argc> {
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(Debug, Default, Clone)]
struct BaseObjInst {
attrs: HashMap<String, ObjP>,
is_instantiated: bool,
}
impl Display for BaseObjInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "<BaseObjInst at {:x}>", (self as *const _ as usize))
}
}
impl Obj for BaseObjInst {
fn instantiate(&mut self) {
if self.get_attr("__type__").is_none() {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Obj"]) as ObjP,
);
}
// TODO BaseObjInst::instantiate - instantiate VTable
// Okay, we are running into a little snag here:
// * TypeInst::vtable holds a collection of named objects that will get copied into the
// attributes during instatiation.
// * If an object that gets copied is a function (UserFunctionInst, BuiltinFunctionInst),
// it will be wrapped by a MethodInst
// * MethodInst requires a pointer to the function being wrapped, as well as a pointer to
// the "self" object.
// * This is the root of the problem - ***we need the pointer to the object that we are
// currently instantiating.***
/*
let type_inst_ptr = Ptr::clone(&self.type_inst());
with_obj_downcast(type_inst_ptr, |type_inst: &TypeInst| {
for (key, value_ptr) in type_inst.vtable.iter() {
// copy functions over as MethodInst
if obj_is_inst::<BuiltinFunctionInst>(&value_ptr)
|| obj_is_inst::<UserFunctionInst>(&value_ptr)
{
self.set_attr(key, MethodInst::create(value_ptr));
}
}
});
*/
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::<BaseObjInst>() {
// compare all attrs
self.attrs.iter().all(|(k1, v1)| {
other
.attrs
.get(k1)
.map(|v2| v2.try_read().unwrap().equals(&*v1.try_read().unwrap()))
.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
}
}
macro_rules! impl_base_obj {
($base_name:ident) => {
fn is_instantiated(&self) -> bool {
self.$base_name.is_instantiated()
}
fn attrs(&self) -> &Attrs {
self.$base_name.attrs()
}
fn attrs_mut(&mut self) -> &mut Attrs {
self.$base_name.attrs_mut()
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
};
() => {
impl_base_obj! { base }
};
}
////////////////////////////////////////////////////////////////////////////////
// ObjInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct ObjInst {
base: BaseObjInst,
}
impl ObjInst {
pub fn new() -> Self {
Self {
base: Default::default(),
}
}
pub fn create() -> Ptr<Self> {
let mut new = Self::new();
new.instantiate();
make_ptr(new)
}
}
impl Display for ObjInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "<ObjInst at {:x}>", (self as *const _ as usize))
}
}
impl Obj for ObjInst {
fn instantiate(&mut self) {
self.base.instantiate();
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<ObjInst>() {
self.base.equals(&other.base)
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// TypeInst
////////////////////////////////////////////////////////////////////////////////
pub struct TypeInst {
name: Arc<String>,
base: BaseObjInst,
vtable: HashMap<String, ObjP>,
}
impl TypeInst {
pub fn new(name: impl ToString) -> Self {
Self {
name: Arc::new(name.to_string()),
base: Default::default(),
vtable: Default::default(),
}
}
pub fn create(name: impl ToString) -> Ptr<Self> {
let mut new = Self::new(name);
new.instantiate();
make_ptr(new)
}
pub fn name(&self) -> &Arc<String> {
&self.name
}
}
impl Debug for TypeInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"<Type {} at {:x}>",
self.name,
(self as *const _ as usize)
)
}
}
impl Display for TypeInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"<Type {} at {:x}>",
self.name,
(self as *const _ as usize)
)
}
}
impl Obj for TypeInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Type"]) as ObjP,
);
self.base.instantiate();
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<TypeInst>() {
// 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)]
pub struct StrInst {
str_value: Arc<String>,
base: BaseObjInst,
}
impl Display for StrInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.str_value)
}
}
impl StrInst {
pub fn new(str_value: impl ToString) -> Self {
Self {
str_value: Arc::new(str_value.to_string()),
base: Default::default(),
}
}
pub fn create(str_value: impl ToString) -> Ptr<Self> {
let mut new = Self::new(str_value);
new.instantiate();
make_ptr(new)
}
pub fn str_value(&self) -> &Arc<String> {
&self.str_value
}
}
impl Obj for StrInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Str"]) as ObjP,
);
self.base.instantiate();
}
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::<StrInst>() {
self.str_value == other.str_value
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// IntInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct IntInst {
int_value: i64,
base: BaseObjInst,
}
impl IntInst {
pub fn new(int_value: i64) -> Self {
Self {
int_value,
base: Default::default(),
}
}
pub fn create(int_value: i64) -> Ptr<Self> {
let mut new = Self::new(int_value);
new.instantiate();
make_ptr(new)
}
pub fn int_value(&self) -> i64 {
self.int_value
}
}
impl Display for IntInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.int_value)
}
}
impl Obj for IntInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Int"]) as ObjP,
);
self.base.instantiate();
}
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::<IntInst>() {
self.int_value == other.int_value
} else if let Some(other) = other.as_any().downcast_ref::<FloatInst>() {
self.int_value as f64 == other.float_value
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// FloatInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct FloatInst {
float_value: f64,
base: BaseObjInst,
}
impl FloatInst {
pub fn new(float_value: f64) -> Self {
Self {
float_value,
base: Default::default(),
}
}
pub fn create(float_value: f64) -> Ptr<Self> {
let mut new = Self::new(float_value);
new.instantiate();
make_ptr(new)
}
pub fn float_value(&self) -> f64 {
self.float_value
}
}
impl Display for FloatInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.float_value)
}
}
impl Obj for FloatInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Float"]) as ObjP,
);
self.base.instantiate();
}
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::<FloatInst>() {
self.float_value == other.float_value
} else if let Some(other) = other.as_any().downcast_ref::<IntInst>() {
self.float_value == other.int_value as f64
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// BoolInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct BoolInst {
bool_value: bool,
base: BaseObjInst,
}
impl BoolInst {
pub fn new(bool_value: bool) -> Self {
Self {
bool_value,
base: Default::default(),
}
}
pub fn create(bool_value: bool) -> Ptr<Self> {
// TODO BoolInst::create : interning
let mut new = Self::new(bool_value);
new.instantiate();
make_ptr(new)
}
pub fn bool_value(&self) -> bool {
self.bool_value
}
}
impl Display for BoolInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.bool_value)
}
}
impl Obj for BoolInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Bool"]) as ObjP,
);
self.base.instantiate();
}
fn is_truthy(&self) -> bool {
self.bool_value
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<BoolInst>() {
self.bool_value == other.bool_value
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// NilInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Default)]
pub struct NilInst {
base: BaseObjInst,
}
impl NilInst {
pub fn new() -> Self {
Default::default()
}
pub fn create() -> Ptr<Self> {
// TODO NilInst::create : interning
let mut new = Self::new();
new.instantiate();
make_ptr(new)
}
}
impl Display for NilInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "nil")
}
}
impl Obj for NilInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Nil"]) as ObjP,
);
self.base.instantiate();
}
fn is_truthy(&self) -> bool {
false
}
fn equals(&self, other: &dyn Obj) -> bool {
other.as_any().downcast_ref::<NilInst>().is_some()
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// BuiltinFunctionInst
////////////////////////////////////////////////////////////////////////////////
pub type BuiltinFunctionPtr = fn(vm: &mut Vm, args: Vec<ObjP>) -> ObjP;
#[derive(Debug)]
pub struct BuiltinFunctionInst {
base: BaseObjInst,
name: String,
function: BuiltinFunctionPtr,
arity: Argc,
}
impl BuiltinFunctionInst {
pub fn new(name: String, function: BuiltinFunctionPtr, arity: Argc) -> Self {
Self {
base: Default::default(),
name,
function,
arity,
}
}
pub fn create(name: String, function: BuiltinFunctionPtr, arity: Argc) -> Ptr<Self> {
let mut new = Self::new(name, function, arity);
new.instantiate();
make_ptr(new)
}
pub fn name(&self) -> &String {
&self.name
}
}
impl Display for BuiltinFunctionInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"<BuiltinFunction {}/{} at 0x{:x}>",
self.name(),
self.arity().unwrap(),
self.function as *const BuiltinFunctionPtr as usize
)
}
}
impl Obj for BuiltinFunctionInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["UserFunction"]) as ObjP,
);
self.base.instantiate();
}
fn arity(&self) -> Option<Argc> {
Some(self.arity)
}
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);
}
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::<BuiltinFunctionInst>() {
ptr::addr_eq(self, other)
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// UserFunctionInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone)]
pub struct UserFunctionInst {
base: BaseObjInst,
name: Arc<String>,
chunk: Arc<Chunk>,
arity: Argc,
captures: Vec<ObjP>,
}
impl UserFunctionInst {
pub fn new(chunk: Chunk, arity: Argc) -> Self {
Self {
base: Default::default(),
name: Arc::new("(anonymous)".to_string()),
chunk: Arc::new(chunk),
arity,
captures: Default::default(),
}
}
pub fn create(chunk: Chunk, arity: Argc) -> Ptr<Self> {
let mut new = Self::new(chunk, arity);
new.instantiate();
make_ptr(new)
}
pub fn name(&self) -> &String {
&self.name
}
pub fn set_name(&mut self, name: Arc<String>) {
self.name = name;
}
pub fn chunk(&self) -> &Chunk {
&self.chunk
}
pub fn push_capture(&mut self, value: ObjP) {
self.captures.push(value);
}
}
impl Display for UserFunctionInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(
fmt,
"<UserFunction {}/{} at 0x{:x}>",
self.name(),
self.arity().unwrap(),
self as *const _ as usize
)
}
}
impl Obj for UserFunctionInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["UserFunction"]) as ObjP,
);
self.base.instantiate();
}
fn arity(&self) -> Option<Argc> {
Some(self.arity)
}
fn call(&self, vm: &mut Vm, argc: Argc) {
assert_eq!(argc, self.arity, "argc must match arity");
let new_frame = Frame {
name: Arc::clone(&self.name),
chunk: Arc::clone(&self.chunk),
ip: 0,
stack_base: vm.stack().len() - (argc as usize),
};
vm.push_frame(new_frame);
for capture in &self.captures {
vm.push(Ptr::clone(&capture));
}
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<UserFunctionInst>() {
// TODO UserFunctionInst::equals : need something more robust than checking addr_eq.
ptr::addr_eq(self, other)
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// MethodInst
////////////////////////////////////////////////////////////////////////////////
#[derive(Debug)]
pub struct MethodInst {
base: BaseObjInst,
self_binding: ObjP,
function: ObjP,
}
impl MethodInst {
pub fn new(self_binding: ObjP, function: ObjP) -> Self {
Self {
base: Default::default(),
self_binding,
function,
}
}
pub fn create(self_binding: ObjP, function: ObjP) -> Ptr<Self> {
let mut new = Self::new(self_binding, function);
new.instantiate();
make_ptr(new)
}
pub fn self_binding(&self) -> &ObjP {
&self.self_binding
}
}
impl Display for MethodInst {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", self.function.try_read().unwrap())
}
}
impl Obj for MethodInst {
fn instantiate(&mut self) {
self.set_attr(
"__type__",
Ptr::clone(&TYPES.try_read().unwrap()["Method"]) as ObjP,
);
self.base.instantiate();
}
fn arity(&self) -> Option<Argc> {
self.function.try_read().unwrap().arity()
}
fn call(&self, vm: &mut Vm, argc: Argc) {
self.function.try_read().unwrap().call(vm, argc)
}
fn equals(&self, other: &dyn Obj) -> bool {
if let Some(other) = other.as_any().downcast_ref::<MethodInst>() {
ptr::addr_eq(&*self.self_binding, &*other.self_binding)
&& ptr::addr_eq(&*self.function, &*other.function)
} else {
false
}
}
impl_base_obj!();
}
////////////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////////////
#[test]
fn test_new_objects() {
init_types();
let type_value = TypeInst::create("Type");
assert_eq!(&*type_value.try_read().unwrap().type_name(), "Type");
let str_value = StrInst::create("asdfasdfasdfasdfasdf");
assert_eq!(&*str_value.try_read().unwrap().type_name(), "Str");
let int_value = IntInst::create(1234);
assert_eq!(&*int_value.try_read().unwrap().type_name(), "Int");
let float_value = FloatInst::create(1234.5678);
assert_eq!(&*float_value.try_read().unwrap().type_name(), "Float");
let nil_value = NilInst::create();
assert_eq!(&*nil_value.try_read().unwrap().type_name(), "Nil");
}
#[test]
fn test_obj_equals() {
init_types();
let int1 = IntInst::create(1234);
let int2 = IntInst::create(1234);
assert!(int1.try_read().unwrap().equals(&*int2.try_read().unwrap()));
assert!(int2.try_read().unwrap().equals(&*int1.try_read().unwrap()));
let float1 = FloatInst::create(1234.0);
assert!(int1
.try_read()
.unwrap()
.equals(&*float1.try_read().unwrap()));
assert!(float1
.try_read()
.unwrap()
.equals(&*int2.try_read().unwrap()));
// self-equality
let str1 = StrInst::create("1234");
assert!(str1.try_read().unwrap().equals(&*str1.try_read().unwrap()));
let str2 = StrInst::create("1234");
assert!(str1.try_read().unwrap().equals(&*str2.try_read().unwrap()));
assert!(str2.try_read().unwrap().equals(&*str1.try_read().unwrap()));
assert!(!str1
.try_read()
.unwrap()
.equals(&*float1.try_read().unwrap()));
assert!(!str1.try_read().unwrap().equals(&*int1.try_read().unwrap()));
let obj1 = ObjInst::create();
let obj2 = ObjInst::create();
assert!(obj1.try_read().unwrap().equals(&*obj2.try_read().unwrap()));
// these objects aren't equal anymore
obj1.try_write()
.unwrap()
.set_attr("my_attr", Ptr::clone(&str2) as ObjP);
assert!(!obj1.try_read().unwrap().equals(&*obj2.try_read().unwrap()));
// but now they are!
obj2.try_write()
.unwrap()
.set_attr("my_attr", Ptr::clone(&str2) as ObjP);
assert!(obj2.try_read().unwrap().equals(&*obj1.try_read().unwrap()));
}