366 lines
9.3 KiB
Rust
366 lines
9.3 KiB
Rust
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<String>) -> 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<char> {
|
|
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<String, VmError> {
|
|
Ok(format!("{:?}", self.text))
|
|
}
|
|
fn str(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
|
Ok(self.text.clone())
|
|
}
|
|
fn is_true(&self) -> Result<bool, VmError> {
|
|
Ok(!self.text.is_empty())
|
|
}
|
|
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
|
let mut hasher = DefaultHasher::new();
|
|
self.text.hash(&mut hasher);
|
|
Ok(hasher.finish())
|
|
}
|
|
fn compare(&self, other: &dyn ObjectPayload, op: CompareOp) -> Result<Option<bool>, VmError> {
|
|
let Some(other) = other.as_any().downcast_ref::<Self>() 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<String, VmError> {
|
|
Ok(format!("<dict len={}>", self.len()))
|
|
}
|
|
fn is_true(&self) -> Result<bool, VmError> {
|
|
Ok(!self.is_empty())
|
|
}
|
|
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
|
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<Value>,
|
|
}
|
|
|
|
impl EndorCellObject {
|
|
pub fn new(value: Option<Value>) -> 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<String, VmError> {
|
|
Ok(if self.value.is_some() {
|
|
"<cell occupied>"
|
|
} else {
|
|
"<cell empty>"
|
|
}
|
|
.into())
|
|
}
|
|
fn hash(&self, _identity: ObjectRef) -> Result<u64, VmError> {
|
|
Err(VmError::UnsupportedOperation("cell hash"))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct EndorFunctionObject {
|
|
name: String,
|
|
code: ObjectRef,
|
|
builtins: Option<ObjectRef>,
|
|
globals: ObjectRef,
|
|
defaults: Vec<Value>,
|
|
closure: Vec<ObjectRef>,
|
|
}
|
|
|
|
impl EndorFunctionObject {
|
|
pub(crate) fn new(
|
|
name: String,
|
|
code: ObjectRef,
|
|
globals: ObjectRef,
|
|
builtins: Option<ObjectRef>,
|
|
defaults: Vec<Value>,
|
|
closure: Vec<ObjectRef>,
|
|
) -> 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<ObjectRef> {
|
|
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<String, VmError> {
|
|
Ok(format!("<function {}>", 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<Value>,
|
|
cause: Option<ObjectRef>,
|
|
context: Option<ObjectRef>,
|
|
suppress_context: bool,
|
|
notes: Vec<String>,
|
|
traceback: Vec<EndorTracebackEntry>,
|
|
}
|
|
|
|
impl EndorBaseExceptionObject {
|
|
pub fn new(name: impl Into<String>, args: Vec<Value>) -> 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<String>) -> 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<ObjectRef> {
|
|
self.cause
|
|
}
|
|
pub fn context(&self) -> Option<ObjectRef> {
|
|
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<String, VmError> {
|
|
Ok(format!(
|
|
"{}({})",
|
|
self.name,
|
|
self.args
|
|
.iter()
|
|
.map(|arg| format!("{arg:?}"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
))
|
|
}
|
|
fn str(&self, _identity: ObjectRef) -> Result<String, VmError> {
|
|
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);
|
|
}
|
|
}
|