use std::any::Any; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use crate::{CompareOp, ObjectPayload, ObjectRef, Value, VmError}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct EndorStringObject { text: String, } impl EndorStringObject { pub fn new(text: impl Into) -> Self { Self { text: text.into() } } pub fn as_str(&self) -> &str { &self.text } pub fn char_count(&self) -> usize { self.text.chars().count() } pub fn char_at(&self, index: usize) -> Option { self.text.chars().nth(index) } } impl ObjectPayload for EndorStringObject { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "string" } fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("{:?}", self.text)) } fn str(&self, _identity: ObjectRef) -> Result { Ok(self.text.clone()) } fn is_true(&self) -> Result { Ok(!self.text.is_empty()) } fn hash(&self, _identity: ObjectRef) -> Result { let mut hasher = DefaultHasher::new(); self.text.hash(&mut hasher); Ok(hasher.finish()) } fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result, VmError> { let Some(other) = other.as_any().downcast_ref::() else { return Ok(None); }; Ok(Some(match op { CompareOp::Eq => self.text == other.text, CompareOp::Ne => self.text != other.text, CompareOp::Lt => self.text < other.text, CompareOp::Le => self.text <= other.text, CompareOp::Gt => self.text > other.text, CompareOp::Ge => self.text >= other.text, })) } } /// Insertion-ordered entries. Mutation is only exposed through Vm so every /// key/value reference is validated, retained, traced, and released correctly. /// Linear lookup is intentional for the initial representation layer. #[derive(Debug, Clone, Default)] pub struct EndorDictObject { pub(crate) entries: Vec<(Value, Value)>, } impl EndorDictObject { pub fn new() -> Self { Self::default() } pub fn len(&self) -> usize { self.entries.len() } pub fn is_empty(&self) -> bool { self.entries.is_empty() } pub fn entries(&self) -> &[(Value, Value)] { &self.entries } } impl ObjectPayload for EndorDictObject { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "dict" } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { for (key, value) in &self.entries { trace_value(key, visit); trace_value(value, visit); } } fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.len())) } fn is_true(&self) -> Result { Ok(!self.is_empty()) } fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("dict hash")) } } /// An empty cell differs from a cell containing Value::Nil. #[derive(Debug, Clone, Default)] pub struct EndorCellObject { pub(crate) value: Option, } impl EndorCellObject { pub fn new(value: Option) -> Self { Self { value } } pub fn value(&self) -> Option<&Value> { self.value.as_ref() } } impl ObjectPayload for EndorCellObject { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "cell" } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { if let Some(value) = &self.value { trace_value(value, visit); } } fn repr(&self, _identity: ObjectRef) -> Result { Ok(if self.value.is_some() { "" } else { "" } .into()) } fn hash(&self, _identity: ObjectRef) -> Result { Err(VmError::UnsupportedOperation("cell hash")) } } #[derive(Debug, Clone)] pub struct EndorFunctionObject { name: String, code: ObjectRef, builtins: Option, globals: ObjectRef, defaults: Vec, closure: Vec, } impl EndorFunctionObject { pub(crate) fn new( name: String, code: ObjectRef, globals: ObjectRef, builtins: Option, defaults: Vec, closure: Vec, ) -> Self { Self { name, code, globals, builtins, defaults, closure, } } pub fn name(&self) -> &str { &self.name } pub fn code(&self) -> ObjectRef { self.code } pub fn builtins(&self) -> Option { self.builtins } pub fn globals(&self) -> ObjectRef { self.globals } pub fn defaults(&self) -> &[Value] { &self.defaults } pub fn closure(&self) -> &[ObjectRef] { &self.closure } } impl ObjectPayload for EndorFunctionObject { fn is_function(&self) -> bool { true } fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "function" } fn allows_attributes(&self) -> bool { true } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { visit(self.code); visit(self.globals); if let Some(builtins) = self.builtins { visit(builtins); } for reference in &self.closure { visit(*reference); } for value in &self.defaults { trace_value(value, visit); } } fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!("", self.name)) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct EndorTracebackEntry { pub function: String, pub file: String, pub line: usize, } /// Common exception instance data, not an exception class or inheritance tree. #[derive(Debug, Clone)] pub struct EndorBaseExceptionObject { name: String, args: Vec, cause: Option, context: Option, suppress_context: bool, notes: Vec, traceback: Vec, } impl EndorBaseExceptionObject { pub fn new(name: impl Into, args: Vec) -> Self { Self { name: name.into(), args, cause: None, context: None, suppress_context: false, notes: Vec::new(), traceback: Vec::new(), } } pub fn with_cause(mut self, cause: ObjectRef) -> Self { self.cause = Some(cause); self.suppress_context = true; self } pub fn with_context(mut self, context: ObjectRef) -> Self { self.context = Some(context); self } pub fn suppress_context(mut self, suppress: bool) -> Self { self.suppress_context = suppress; self } pub fn with_note(mut self, note: impl Into) -> Self { self.notes.push(note.into()); self } pub fn with_traceback(mut self, entry: EndorTracebackEntry) -> Self { self.traceback.push(entry); self } pub fn name(&self) -> &str { &self.name } pub fn args(&self) -> &[Value] { &self.args } pub fn cause(&self) -> Option { self.cause } pub fn context(&self) -> Option { self.context } pub fn context_suppressed(&self) -> bool { self.suppress_context } pub fn notes(&self) -> &[String] { &self.notes } pub fn traceback(&self) -> &[EndorTracebackEntry] { &self.traceback } } impl ObjectPayload for EndorBaseExceptionObject { fn reference_assignment_only(&self) -> bool { true } fn as_any(&self) -> &dyn Any { self } fn kind_name(&self) -> &'static str { "exception" } fn allows_attributes(&self) -> bool { true } fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { for value in &self.args { trace_value(value, visit); } for reference in [self.cause, self.context].into_iter().flatten() { visit(reference); } } fn repr(&self, _identity: ObjectRef) -> Result { Ok(format!( "{}({})", self.name, self.args .iter() .map(|arg| format!("{arg:?}")) .collect::>() .join(", ") )) } fn str(&self, _identity: ObjectRef) -> Result { Ok(match self.args.as_slice() { [] => String::new(), [Value::Text(text)] => text.clone(), [Value::Number(number)] => number.to_string(), args => format!("{args:?}"), }) } } pub(crate) fn trace_value(value: &Value, visit: &mut dyn FnMut(ObjectRef)) { if let Value::Object(reference) = value { visit(*reference); } }