diff --git a/Cargo.lock b/Cargo.lock
index a3a8a4ce..96c3ff73 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1609,6 +1609,7 @@ version = "0.1.0"
dependencies = [
"bincode",
"bytes",
+ "itertools 0.13.0",
"lazy_static",
"moor-values",
"pest",
diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs
index 2e18c5d5..cf10d8d2 100644
--- a/crates/common/src/lib.rs
+++ b/crates/common/src/lib.rs
@@ -20,9 +20,10 @@ pub use encode::{
};
pub use var::{
- v_bool, v_empty_list, v_empty_map, v_empty_str, v_err, v_float, v_int, v_list, v_list_iter,
- v_map, v_none, v_obj, v_objid, v_str, v_string, Associative, ErrorPack, IndexMode, List, Map,
- Sequence, Str, Var, Variant, AMBIGUOUS, FAILED_MATCH, NOTHING, SYSTEM_OBJECT,
+ v_bool, v_empty_list, v_empty_map, v_empty_str, v_err, v_float, v_flyweight, v_int, v_list,
+ v_list_iter, v_map, v_map_iter, v_none, v_obj, v_objid, v_str, v_string, Associative,
+ ErrorPack, IndexMode, List, Map, Sequence, Str, Var, Variant, AMBIGUOUS, FAILED_MATCH, NOTHING,
+ SYSTEM_OBJECT,
};
pub use var::{Error, Obj, Symbol, VarType};
diff --git a/crates/common/src/tasks/events.rs b/crates/common/src/tasks/events.rs
index 1e5b5157..dd71935d 100644
--- a/crates/common/src/tasks/events.rs
+++ b/crates/common/src/tasks/events.rs
@@ -12,7 +12,7 @@
// this program. If not, see .
//
-use crate::{Obj, Symbol, Var};
+use crate::{Symbol, Var};
use bincode::{Decode, Encode};
use std::time::SystemTime;
@@ -23,7 +23,7 @@ pub struct NarrativeEvent {
/// When the event happened, in the server's system time.
pub timestamp: SystemTime,
/// The object that authored or caused the event.
- pub author: Obj,
+ pub author: Var,
/// The event itself.
pub event: Event,
}
@@ -41,7 +41,7 @@ pub enum Event {
impl NarrativeEvent {
#[must_use]
- pub fn notify(author: Obj, value: Var, content_type: Option) -> Self {
+ pub fn notify(author: Var, value: Var, content_type: Option) -> Self {
Self {
timestamp: SystemTime::now(),
author,
@@ -54,7 +54,7 @@ impl NarrativeEvent {
self.timestamp
}
#[must_use]
- pub fn author(&self) -> &Obj {
+ pub fn author(&self) -> &Var {
&self.author
}
#[must_use]
diff --git a/crates/common/src/var/flyweight.rs b/crates/common/src/var/flyweight.rs
new file mode 100644
index 00000000..810c2e94
--- /dev/null
+++ b/crates/common/src/var/flyweight.rs
@@ -0,0 +1,295 @@
+// Copyright (C) 2024 Ryan Daum
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU General Public License as published by the Free Software
+// Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program. If not, see .
+//
+
+//! A "flyweight" is a lightweight object type which consists only of a delegate, a set of
+//! "slots" (symbol -> var pairs), a single "contents" value (a list), and an optional sealed/signed
+//! state which makes it opaque.
+//!
+//! It is a reference counted, immutable bucket of slots.
+//! Verbs called on it dispatch to the delegate. `this`, `caller`, perms etc all resolve to the
+//! actual flyweight.
+//! Properties are resolved in the slots, then the delegate.
+//! Verbs are resolved in the delegate.
+//!
+//! Type protocol for an unsealed flyweight is a sequence. It behaves like a list around the
+//! contents portion. The slots are accessed with a property access notation.
+//!
+//! The delegate is visible via the `.delegate` property access.
+//! The slots can be listed with a `.slots` property access.
+//! It is therefore illegal for a slot to have the name `slots` or `delegate`.
+//!
+//! So appending, etc can be done like:
+//! `< x.delegate, x.slots, {@x, y} >`
+//!
+//! Literal syntax is:
+//!
+//! `< delegate, [ slot -> value, ... ], contents >`
+//!
+//! Setting the secret is done with the `seal(priv-key, secret)` function, which signs the flyweight with a
+//! private key. The flyweight then becomes opaque without calling `unseal(pub-key, secret)` with the
+//! right public key and the correct secret.
+//!
+//! When a flyweight is sealed, `.slots`, and `.delegate` (and literal output) will not
+//! be available without calling `unseal()` with the correct secret.
+//!
+//! The purpose is to support two kinds of scenarios:
+//! "Lightweight" non-persistent patterns where a full object would be overkill.
+//! "Capability" style security patterns where the flyweight is a capability to a full object.
+//!
+//!
+//! The structure of the flyweight also resembles an XML/HTML node, with a delegate as the tag name,
+//! slots as the attributes, and contents as the inner text/nodes.
+
+use crate::Error::E_TYPE;
+use crate::{Error, List, Obj, Sequence, Symbol, Var, Variant};
+use bincode::de::{BorrowDecoder, Decoder};
+use bincode::enc::Encoder;
+use bincode::error::{DecodeError, EncodeError};
+use bincode::{BorrowDecode, Decode, Encode};
+use std::fmt::{Debug, Formatter};
+use std::hash::Hash;
+use std::sync::Arc;
+
+#[derive(Clone, Encode, Decode, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Flyweight(Arc);
+
+#[derive(Clone, Hash, PartialOrd, Ord, Eq)]
+struct Inner {
+ delegate: Obj,
+ slots: im::Vector<(Symbol, Var)>,
+ contents: List,
+ /// If `secret` is present it's a string signed with a key-pair that can be used to unseal
+ /// the flyweight.
+ /// The meaning of the key is up to the application.
+ seal: Option,
+}
+
+impl PartialEq for Inner {
+ fn eq(&self, other: &Self) -> bool {
+ // Two flyweights where there are 'secrets' involved are never eq.
+ // To avoid leaking information about the secret.
+ if self.seal.is_some() || other.seal.is_some() {
+ return false;
+ }
+ self.delegate == other.delegate
+ && self.slots == other.slots
+ && self.contents == other.contents
+ }
+}
+
+impl Hash for Flyweight {
+ fn hash(&self, state: &mut H) {
+ self.0.delegate.hash(state);
+ self.0.slots.hash(state);
+ self.0.contents.hash(state);
+ self.0.seal.hash(state);
+ }
+}
+
+impl Encode for Inner {
+ fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> {
+ self.delegate.encode(encoder)?;
+ self.slots.len().encode(encoder)?;
+ for (k, v) in &self.slots {
+ k.encode(encoder)?;
+ v.encode(encoder)?;
+ }
+ self.contents.encode(encoder)?;
+ self.seal.encode(encoder)
+ }
+}
+
+impl Decode for Inner {
+ fn decode(decoder: &mut D) -> Result {
+ let delegate = Obj::decode(decoder)?;
+ let len = usize::decode(decoder)?;
+ let mut slots = im::Vector::new();
+ for _ in 0..len {
+ let k = Symbol::decode(decoder)?;
+ let v = Var::decode(decoder)?;
+ slots.push_back((k, v));
+ }
+ let contents = List::decode(decoder)?;
+ let seal = Option::::decode(decoder)?;
+ Ok(Self {
+ delegate,
+ slots,
+ contents,
+ seal,
+ })
+ }
+}
+
+impl<'a> BorrowDecode<'a> for Inner {
+ fn borrow_decode>(decoder: &mut D) -> Result {
+ let delegate = Obj::borrow_decode(decoder)?;
+ let len = usize::borrow_decode(decoder)?;
+ let mut slots = im::Vector::new();
+ for _ in 0..len {
+ let k = Symbol::borrow_decode(decoder)?;
+ let v = Var::borrow_decode(decoder)?;
+ slots.push_back((k, v));
+ }
+ let contents = List::borrow_decode(decoder)?;
+ let seal = Option::::borrow_decode(decoder)?;
+ Ok(Self {
+ delegate,
+ slots,
+ contents,
+ seal,
+ })
+ }
+}
+impl Debug for Flyweight {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ if self.0.seal.is_some() {
+ write!(f, "")
+ } else {
+ write!(
+ f,
+ "<{:?}, {:?}, {:?}>",
+ self.0.delegate, self.0.slots, self.0.contents
+ )
+ }
+ }
+}
+
+impl Flyweight {
+ pub fn mk_flyweight(
+ delegate: Obj,
+ slots: &[(Symbol, Var)],
+ contents: List,
+ seal: Option,
+ ) -> Self {
+ Self(Arc::new(Inner {
+ delegate,
+ slots: slots.into(),
+ contents,
+ seal,
+ }))
+ }
+}
+
+impl Flyweight {
+ /// Return the slot with the given key, if it exists.
+ pub fn get_slot(&self, key: &Symbol) -> Option<&Var> {
+ self.0.slots.iter().find(|(k, _)| k == key).map(|(_, v)| v)
+ }
+
+ pub fn slots(&self) -> &im::Vector<(Symbol, Var)> {
+ &self.0.slots
+ }
+
+ pub fn delegate(&self) -> &Obj {
+ &self.0.delegate
+ }
+
+ pub fn seal(&self) -> Option<&String> {
+ self.0.seal.as_ref()
+ }
+
+ pub fn contents(&self) -> &List {
+ &self.0.contents
+ }
+
+ pub fn is_sealed(&self) -> bool {
+ self.0.seal.is_some()
+ }
+
+ pub fn with_new_contents(&self, new_contents: List) -> Var {
+ let fi = Inner {
+ delegate: self.0.delegate.clone(),
+ slots: self.0.slots.clone(),
+ contents: new_contents,
+ seal: self.0.seal.clone(),
+ };
+ let fl = Flyweight(Arc::new(fi));
+ let variant = Variant::Flyweight(fl);
+ Var::from_variant(variant)
+ }
+}
+
+impl Sequence for Flyweight {
+ fn is_empty(&self) -> bool {
+ self.0.contents.is_empty()
+ }
+
+ fn len(&self) -> usize {
+ self.0.contents.len()
+ }
+
+ fn index_in(&self, value: &Var, case_sensitive: bool) -> Result, Error> {
+ self.0.contents.index_in(value, case_sensitive)
+ }
+
+ fn contains(&self, value: &Var, case_sensitive: bool) -> Result {
+ self.0.contents.contains(value, case_sensitive)
+ }
+
+ fn index(&self, index: usize) -> Result {
+ self.0.contents.index(index)
+ }
+
+ fn index_set(&self, index: usize, value: &Var) -> Result {
+ let new_contents = self.0.contents.index_set(index, value)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+
+ fn push(&self, value: &Var) -> Result {
+ let new_contents = self.0.contents.push(value)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+
+ fn insert(&self, index: usize, value: &Var) -> Result {
+ let new_contents = self.0.contents.insert(index, value)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+
+ fn range(&self, from: isize, to: isize) -> Result {
+ self.0.contents.range(from, to)
+ }
+
+ fn range_set(&self, from: isize, to: isize, with: &Var) -> Result {
+ let new_contents = self.0.contents.range_set(from, to, with)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+
+ fn append(&self, other: &Var) -> Result {
+ let new_contents = self.0.contents.append(other)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+
+ fn remove_at(&self, index: usize) -> Result {
+ let new_contents = self.0.contents.remove_at(index)?;
+ let Variant::List(new_contents_as_list) = new_contents.variant() else {
+ return Err(E_TYPE);
+ };
+ Ok(self.with_new_contents(new_contents_as_list.clone()))
+ }
+}
diff --git a/crates/common/src/var/list.rs b/crates/common/src/var/list.rs
index 40a7d8ae..26657c9f 100644
--- a/crates/common/src/var/list.rs
+++ b/crates/common/src/var/list.rs
@@ -24,6 +24,7 @@ use bincode::error::{DecodeError, EncodeError};
use bincode::{BorrowDecode, Decode, Encode};
use num_traits::ToPrimitive;
use std::cmp::max;
+use std::fmt::{Debug, Formatter};
use std::hash::Hash;
#[derive(Clone)]
@@ -35,6 +36,11 @@ impl List {
Var::from_variant(Variant::List(List(Box::new(l))))
}
+ pub fn mk_list(values: &[Var]) -> List {
+ let l = im::Vector::from(values.to_vec());
+ List(Box::new(l))
+ }
+
pub fn iter(&self) -> impl Iterator- + '_ {
(0..self.len()).map(move |i| self.index(i).unwrap())
}
@@ -73,6 +79,11 @@ impl List {
}
}
+impl Debug for List {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?}", self.0)
+ }
+}
impl Sequence for List {
fn is_empty(&self) -> bool {
self.0.is_empty()
diff --git a/crates/common/src/var/mod.rs b/crates/common/src/var/mod.rs
index a98739ef..75b0763e 100644
--- a/crates/common/src/var/mod.rs
+++ b/crates/common/src/var/mod.rs
@@ -13,6 +13,7 @@
//
mod error;
+mod flyweight;
mod list;
mod map;
mod obj;
@@ -24,6 +25,7 @@ mod var;
mod variant;
pub use error::{Error, ErrorPack};
+pub use flyweight::Flyweight;
pub use list::List;
pub use map::Map;
pub use obj::{Obj, AMBIGUOUS, FAILED_MATCH, NOTHING, SYSTEM_OBJECT};
@@ -32,8 +34,8 @@ pub use string::Str;
use strum::FromRepr;
pub use symbol::Symbol;
pub use var::{
- v_bool, v_empty_list, v_empty_map, v_empty_str, v_err, v_float, v_int, v_list, v_list_iter,
- v_map, v_none, v_obj, v_objid, v_str, v_string, Var,
+ v_bool, v_empty_list, v_empty_map, v_empty_str, v_err, v_float, v_flyweight, v_int, v_list,
+ v_list_iter, v_map, v_map_iter, v_none, v_obj, v_objid, v_str, v_string, Var,
};
pub use variant::Variant;
@@ -51,6 +53,7 @@ pub enum VarType {
TYPE_LABEL = 7, // present only in textdump */
TYPE_FLOAT = 9,
TYPE_MAP = 10,
+ TYPE_FLYWEIGHT = 11,
}
/// Sequence index modes: 0 or 1 indexed.
diff --git a/crates/common/src/var/var.rs b/crates/common/src/var/var.rs
index 6ecf12bc..8890e6d8 100644
--- a/crates/common/src/var/var.rs
+++ b/crates/common/src/var/var.rs
@@ -15,10 +15,10 @@
use crate::var::list::List;
use crate::var::variant::Variant;
use crate::var::Error::{E_INVARG, E_RANGE, E_TYPE};
-use crate::var::{map, IndexMode, Sequence, TypeClass};
+use crate::var::{map, Flyweight, IndexMode, Sequence, TypeClass};
use crate::var::{string, Associative};
use crate::var::{Error, Obj, VarType};
-use crate::BincodeAsByteBufferExt;
+use crate::{BincodeAsByteBufferExt, Symbol};
use bincode::{Decode, Encode};
use std::cmp::{min, Ordering};
use std::fmt::{Debug, Formatter};
@@ -75,6 +75,7 @@ impl Var {
Variant::None => VarType::TYPE_NONE,
Variant::Float(_) => VarType::TYPE_FLOAT,
Variant::Map(_) => VarType::TYPE_MAP,
+ Variant::Flyweight(_) => VarType::TYPE_FLYWEIGHT,
}
}
@@ -90,6 +91,10 @@ impl Var {
map::Map::build(pairs.iter())
}
+ pub fn mk_map_iter<'a, I: Iterator
- >(pairs: I) -> Self {
+ map::Map::build(pairs)
+ }
+
pub fn variant(&self) -> &Variant {
&self.0
}
@@ -104,6 +109,7 @@ impl Var {
Variant::Str(s) => !s.is_empty(),
Variant::Map(m) => !m.is_empty(),
Variant::Err(_) => false,
+ Variant::Flyweight(f) => !f.is_empty(),
}
}
@@ -392,6 +398,7 @@ impl Var {
pub fn type_class(&self) -> TypeClass {
match self.variant() {
Variant::List(s) => TypeClass::Sequence(s),
+ Variant::Flyweight(f) => TypeClass::Sequence(f),
Variant::Str(s) => TypeClass::Sequence(s),
Variant::Map(m) => TypeClass::Associative(m),
_ => TypeClass::Scalar,
@@ -436,6 +443,10 @@ pub fn v_map(pairs: &[(Var, Var)]) -> Var {
Var::mk_map(pairs)
}
+pub fn v_map_iter<'a, I: Iterator
- >(pairs: I) -> Var {
+ Var::mk_map_iter(pairs)
+}
+
pub fn v_float(f: f64) -> Var {
Var::mk_float(f)
}
@@ -452,6 +463,16 @@ pub fn v_obj(o: Obj) -> Var {
Var::mk_object(o)
}
+pub fn v_flyweight(
+ delegate: Obj,
+ slots: &[(Symbol, Var)],
+ contents: List,
+ seal: Option
,
+) -> Var {
+ let fl = Flyweight::mk_flyweight(delegate, slots, contents, seal);
+ Var::from_variant(Variant::Flyweight(fl))
+}
+
pub fn v_empty_list() -> Var {
// TODO: lazy static
v_list(&[])
diff --git a/crates/common/src/var/variant.rs b/crates/common/src/var/variant.rs
index 10da004d..94e948b9 100644
--- a/crates/common/src/var/variant.rs
+++ b/crates/common/src/var/variant.rs
@@ -12,6 +12,7 @@
// this program. If not, see .
//
+use crate::var::flyweight::Flyweight;
use crate::var::list::List;
use crate::var::Associative;
use crate::var::{map, string, Sequence};
@@ -32,6 +33,7 @@ pub enum Variant {
Str(string::Str),
Map(map::Map),
Err(Error),
+ Flyweight(Flyweight),
}
impl Hash for Variant {
@@ -45,6 +47,7 @@ impl Hash for Variant {
Variant::Str(s) => s.hash(state),
Variant::Map(m) => m.hash(state),
Variant::Err(e) => e.hash(state),
+ Variant::Flyweight(f) => f.hash(state),
}
}
}
@@ -60,6 +63,8 @@ impl Ord for Variant {
(Variant::Str(l), Variant::Str(r)) => l.cmp(r),
(Variant::Map(l), Variant::Map(r)) => l.cmp(r),
(Variant::Err(l), Variant::Err(r)) => l.cmp(r),
+ (Variant::Flyweight(l), Variant::Flyweight(r)) => l.cmp(r),
+
(Variant::None, _) => Ordering::Less,
(_, Variant::None) => Ordering::Greater,
(Variant::Obj(_), _) => Ordering::Less,
@@ -74,6 +79,9 @@ impl Ord for Variant {
(_, Variant::Str(_)) => Ordering::Greater,
(Variant::Map(_), _) => Ordering::Less,
(_, Variant::Map(_)) => Ordering::Greater,
+
+ (Variant::Flyweight(_), _) => Ordering::Less,
+ (_, Variant::Flyweight(_)) => Ordering::Greater,
}
}
}
@@ -105,6 +113,7 @@ impl Debug for Variant {
write!(f, "Map([size = {}, items = {:?}])", m.len(), i)
}
Variant::Err(e) => write!(f, "Error({:?})", e),
+ Variant::Flyweight(fl) => write!(f, "Flyweight({:?})", fl),
}
}
}
@@ -120,6 +129,7 @@ impl PartialEq for Variant {
(Variant::List(s), Variant::List(o)) => s == o,
(Variant::Map(s), Variant::Map(o)) => s == o,
(Variant::Err(s), Variant::Err(o)) => s == o,
+ (Variant::Flyweight(s), Variant::Flyweight(o)) => s == o,
(Variant::None, Variant::None) => true,
_ => false,
}
diff --git a/crates/compiler/Cargo.toml b/crates/compiler/Cargo.toml
index 0664e473..708563f9 100644
--- a/crates/compiler/Cargo.toml
+++ b/crates/compiler/Cargo.toml
@@ -23,6 +23,7 @@ moor-values = { path = "../common" }
## General usefulness
bincode.workspace = true
bytes.workspace = true
+itertools.workspace = true
lazy_static.workspace = true
strum.workspace = true
diff --git a/crates/compiler/src/ast.rs b/crates/compiler/src/ast.rs
index 1c143339..2dbdddb8 100644
--- a/crates/compiler/src/ast.rs
+++ b/crates/compiler/src/ast.rs
@@ -166,6 +166,7 @@ pub enum Expr {
Index(Box, Box),
List(Vec),
Map(Vec<(Expr, Expr)>),
+ Flyweight(Box, Vec<(Symbol, Expr)>, Vec),
Scatter(Vec, Box),
Length,
}
diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs
index 51f970b6..d8147073 100644
--- a/crates/compiler/src/codegen.rs
+++ b/crates/compiler/src/codegen.rs
@@ -18,8 +18,8 @@ use std::sync::Arc;
use tracing::error;
-use moor_values::Var;
use moor_values::Variant;
+use moor_values::{v_str, Var};
use crate::ast::{
Arg, BinaryOp, CatchCodes, Expr, ScatterItem, ScatterKind, Stmt, StmtNode, UnaryOp,
@@ -499,6 +499,19 @@ impl CodegenState {
}
self.push_stack(1);
}
+ Expr::Flyweight(delegate, slots, contents) => {
+ // push delegate, slots, contents. op is # of slots.
+ self.generate_expr(delegate.as_ref())?;
+ for (k, v) in slots {
+ self.generate_expr(v)?;
+ self.pop_stack(1);
+ self.generate_expr(&Expr::Value(v_str(k.as_str())))?;
+ self.pop_stack(1);
+ }
+ self.generate_arg_list(contents)?;
+ self.emit(Op::MakeFlyweight(slots.len()));
+ self.pop_stack(1);
+ }
Expr::Scatter(scatter, right) => self.generate_scatter_assign(scatter, right)?,
Expr::Assign { left, right } => self.generate_assign(left, right)?,
}
diff --git a/crates/compiler/src/decompile.rs b/crates/compiler/src/decompile.rs
index 61d3b0f6..2746aa2c 100644
--- a/crates/compiler/src/decompile.rs
+++ b/crates/compiler/src/decompile.rs
@@ -12,7 +12,7 @@
// this program. If not, see .
//
-use moor_values::{v_err, v_int, v_none, v_obj, Var};
+use moor_values::{v_err, v_int, v_none, v_obj, Symbol, Var};
use moor_values::{v_float, Variant};
use std::collections::{HashMap, VecDeque};
@@ -617,6 +617,35 @@ impl Decompile {
list.push(arg);
self.push_expr(Expr::List(list));
}
+ Op::MakeFlyweight(num_slots) => {
+ let mut slots = Vec::with_capacity(num_slots);
+ let contents = self.pop_expr()?;
+ let Expr::List(contents) = contents else {
+ return Err(MalformedProgram("expected list for contents".to_string()));
+ };
+ for _ in 0..num_slots {
+ let k = self.pop_expr()?;
+ let v = self.pop_expr()?;
+ let k = match k {
+ Expr::Value(s) => match s.variant() {
+ Variant::Str(s) => Symbol::mk(s.as_string().as_str()),
+ _ => {
+ return Err(MalformedProgram(
+ "expected string for flyweight slot name".to_string(),
+ ));
+ }
+ },
+ _ => {
+ return Err(MalformedProgram(
+ "expected string for flyweight slot name".to_string(),
+ ));
+ }
+ };
+ slots.push((k, v));
+ }
+ let delegate = self.pop_expr()?;
+ self.push_expr(Expr::Flyweight(Box::new(delegate), slots, contents));
+ }
Op::Pass => {
let args = self.pop_expr()?;
let Expr::List(args) = args else {
@@ -1201,4 +1230,11 @@ return 0 && "Automatically Added Return";
let (parse, decompiled) = parse_decompile(program);
assert_trees_match_recursive(&parse.stmts, &decompiled.stmts);
}
+
+ #[test]
+ fn test_flyweight() {
+ let program = r#"let flywt = < #1, [ colour -> "orange", z -> 5 ], {#2, #4, "a"}>;"#;
+ let (parse, decompiled) = parse_decompile(program);
+ assert_trees_match_recursive(&parse.stmts, &decompiled.stmts);
+ }
}
diff --git a/crates/compiler/src/moo.pest b/crates/compiler/src/moo.pest
index a353925c..0fe64d04 100644
--- a/crates/compiler/src/moo.pest
+++ b/crates/compiler/src/moo.pest
@@ -141,6 +141,7 @@ primary = _{
| sysprop_call
| sysprop
| try_expr
+ | flyweight
| map
| list
| atom
@@ -157,6 +158,12 @@ sysprop_call = { sysprop ~ arglist }
atom = { integer | float | string | object | err | ident }
arglist = { "(" ~ exprlist ~ ")" | "()" }
list = { ("{" ~ exprlist ~ "}") | "{}" }
+
+// flyweight is < parent, [ prop -> value, ... ], { contents, ... } >
+flyweight = { "<" ~ expr ~ ("," ~ flyweight_slots)? ~ ("," ~ flyweight_contents)? ~ ">" }
+flyweight_slots = { "[" ~ (ident ~ "->" ~ expr) ~ ("," ~ ident ~ "->" ~ expr)* ~ "]"}
+flyweight_contents = { "{" ~ exprlist? ~ "}" }
+
exprlist = { argument ~ ("," ~ argument)* }
argument = { expr | "@" ~ expr }
map = { ("[" ~ (expr ~ "->" ~ expr) ~ ("," ~ expr ~ "->" ~ expr)* ~ "]") | "[]" }
diff --git a/crates/compiler/src/opcode.rs b/crates/compiler/src/opcode.rs
index 3445d67c..182cb3ab 100644
--- a/crates/compiler/src/opcode.rs
+++ b/crates/compiler/src/opcode.rs
@@ -80,6 +80,7 @@ pub enum Op {
MakeSingletonList,
MakeMap,
MapInsert,
+ MakeFlyweight(usize),
Mod,
Mul,
Ne,
diff --git a/crates/compiler/src/parse.rs b/crates/compiler/src/parse.rs
index 8d20a557..5c7a9a2c 100644
--- a/crates/compiler/src/parse.rs
+++ b/crates/compiler/src/parse.rs
@@ -19,6 +19,7 @@ use std::collections::HashMap;
use std::rc::Rc;
use std::str::FromStr;
+use itertools::Itertools;
use moor_values::SYSTEM_OBJECT;
use moor_values::{v_none, Symbol};
use pest::pratt_parser::{Assoc, Op, PrattParser};
@@ -56,9 +57,10 @@ pub struct CompileOptions {
pub lexical_scopes: bool,
/// Whether to support a Map datatype ([ k -> v, .. ]) compatible with Stunt/ToastStunt
pub map_type: bool,
- // TODO: future options:
- // - symbol types
- // - disable "#" style object references (obscure_references)
+ /// Whether to support the flyweight type (a delegate object with slots and contents)
+ pub flyweight_type: bool, // TODO: future options:
+ // - symbol types
+ // - disable "#" style object references (obscure_references)
}
impl Default for CompileOptions {
@@ -66,6 +68,7 @@ impl Default for CompileOptions {
Self {
lexical_scopes: true,
map_type: true,
+ flyweight_type: true,
}
}
}
@@ -312,6 +315,63 @@ impl TreeTransformer {
.collect();
Ok(Expr::Map(pairs))
}
+ Rule::flyweight => {
+ if !self.options.flyweight_type {
+ return Err(CompileError::DisabledFeature("Maps".to_string()));
+ }
+ let mut parts = primary.into_inner();
+
+ // Three components:
+ // 1. The delegate object
+ // 2. The slots
+ // 3. The contents
+ let delegate = primary_self
+ .clone()
+ .parse_expr(parts.next().unwrap().into_inner())?;
+
+ let mut slots = vec![];
+ let mut contents = vec![];
+
+ // If the next is `flyweight_slots`, parse the pairs inside it
+ for next in parts {
+ match next.as_rule() {
+ Rule::flyweight_slots => {
+ // Parse the slots, they're a sequence of ident, expr pairs.
+ // Collect them into two iterators,
+ let slot_pairs = next.into_inner().chunks(2);
+ for mut pair in &slot_pairs {
+ let slot_name =
+ Symbol::mk_case_insensitive(pair.next().unwrap().as_str());
+
+ // "delegate" and "slots" are forbidden slot names.
+ if slot_name == Symbol::mk_case_insensitive("delegate")
+ || slot_name == Symbol::mk_case_insensitive("slots")
+ {
+ return Err(CompileError::ParseError(format!(
+ "Invalid slot name: {} for flyweight",
+ slot_name
+ )));
+ }
+
+ let slot_expr = primary_self
+ .clone()
+ .parse_expr(pair.next().unwrap().into_inner())?;
+ slots.push((slot_name, slot_expr));
+ }
+ }
+ Rule::flyweight_contents => {
+ if let Some(exprlist) = next.into_inner().next() {
+ let exprlist = exprlist.into_inner();
+ contents = primary_self.clone().parse_exprlist(exprlist)?;
+ }
+ }
+ _ => {
+ panic!("Unexpected rule: {:?}", next.as_rule());
+ }
+ };
+ }
+ Ok(Expr::Flyweight(Box::new(delegate), slots, contents))
+ }
Rule::builtin_call => {
let mut inner = primary.into_inner();
let bf = inner.next().unwrap().as_str();
@@ -1194,7 +1254,7 @@ mod tests {
use moor_values::{v_none, Symbol};
use crate::ast::Arg::{Normal, Splice};
- use crate::ast::Expr::{Call, Id, Prop, Value, Verb};
+ use crate::ast::Expr::{Call, Flyweight, Id, Prop, Value, Verb};
use crate::ast::{
BinaryOp, CatchCodes, CondArm, ElseArm, ExceptArm, Expr, ScatterItem, ScatterKind, Stmt,
StmtNode, UnaryOp,
@@ -2692,4 +2752,49 @@ mod tests {
let parse = parse_program(program, CompileOptions::default());
assert!(matches!(parse, Err(CompileError::DuplicateVariable(_))));
}
+
+ #[test]
+ fn test_empty_flyweight() {
+ let program = r#"<#1>;"#;
+ let parse = parse_program(program, CompileOptions::default()).unwrap();
+ assert_eq!(
+ stripped_stmts(&parse.stmts),
+ vec![StmtNode::Expr(Flyweight(
+ Box::new(Value(v_objid(1))),
+ vec![],
+ vec![],
+ ))]
+ );
+ }
+
+ #[test]
+ fn test_flyweight_no_slots_just_contents() {
+ let program = r#"<#1, {2}>;"#;
+ let parse = parse_program(program, CompileOptions::default()).unwrap();
+ assert_eq!(
+ stripped_stmts(&parse.stmts),
+ vec![StmtNode::Expr(Flyweight(
+ Box::new(Value(v_objid(1))),
+ vec![],
+ vec![Normal(Value(v_int(2)))],
+ ))]
+ );
+ }
+
+ #[test]
+ fn test_flyweight_only_slots() {
+ let program = r#"<#1, [a->1 , b->2]>;"#;
+ let parse = parse_program(program, CompileOptions::default()).unwrap();
+ assert_eq!(
+ stripped_stmts(&parse.stmts),
+ vec![StmtNode::Expr(Flyweight(
+ Box::new(Value(v_objid(1))),
+ vec![
+ (Symbol::mk("a"), Value(v_int(1))),
+ (Symbol::mk("b"), Value(v_int(2)))
+ ],
+ vec![],
+ ))]
+ );
+ }
}
diff --git a/crates/compiler/src/unparse.rs b/crates/compiler/src/unparse.rs
index c35274f7..186d7ac2 100644
--- a/crates/compiler/src/unparse.rs
+++ b/crates/compiler/src/unparse.rs
@@ -13,7 +13,7 @@
//
use moor_values::util::quote_str;
-use moor_values::{Var, Variant};
+use moor_values::{Sequence, Var, Variant};
use crate::ast::{Expr, Stmt, StmtNode};
use crate::decompile::DecompileError;
@@ -71,6 +71,7 @@ impl Expr {
Expr::Id(_) => 1,
Expr::List(_) => 1,
Expr::Map(_) => 1,
+ Expr::Flyweight(..) => 1,
Expr::Pass { .. } => 1,
Expr::Call { .. } => 1,
Expr::Length => 1,
@@ -338,6 +339,36 @@ impl<'a> Unparse<'a> {
Ok(buffer)
}
Expr::Length => Ok(String::from("$")),
+ Expr::Flyweight(delegate, slots, contents) => {
+ // "< #1, [ slot -> value, ...], {1, 2, 3} >"
+ let mut buffer = String::new();
+ buffer.push('<');
+ buffer.push_str(self.unparse_expr(delegate)?.as_str());
+ if !slots.is_empty() {
+ buffer.push_str(", [");
+ for (slot, value) in slots {
+ buffer.push_str(slot.as_str());
+ buffer.push_str(" -> ");
+ buffer.push_str(self.unparse_expr(value)?.as_str());
+ buffer.push_str(", ");
+ }
+ buffer.pop();
+ buffer.pop();
+ buffer.push(']');
+ }
+ if !contents.is_empty() {
+ buffer.push_str(", {");
+ for value in contents {
+ buffer.push_str(self.unparse_arg(value)?.as_str());
+ buffer.push_str(", ");
+ }
+ buffer.pop();
+ buffer.pop();
+ buffer.push('}');
+ }
+ buffer.push('>');
+ Ok(buffer)
+ }
}
}
@@ -768,6 +799,44 @@ pub fn to_literal(v: &Var) -> String {
result
}
Variant::Err(e) => e.name().to_string(),
+ Variant::Flyweight(fl) => {
+ // If sealed, just return
+ if fl.seal().is_some() {
+ return "".to_string();
+ }
+
+ // Syntax:
+ // < delegate, [ s -> v, ... ], v, v, v ... >
+ let mut result = String::new();
+ result.push('<');
+ result.push_str(fl.delegate().to_literal().as_str());
+ if !fl.slots().is_empty() {
+ result.push_str(", [");
+ for (i, (k, v)) in fl.slots().iter().enumerate() {
+ if i > 0 {
+ result.push_str(", ");
+ }
+ result.push_str(k.as_str());
+ result.push_str(" -> ");
+ result.push_str(to_literal(v).as_str());
+ }
+ result.push(']');
+ }
+ let v = fl.contents();
+ if !v.is_empty() {
+ result.push_str(", {");
+ for (i, v) in v.iter().enumerate() {
+ if i > 0 {
+ result.push_str(", ");
+ }
+ result.push_str(to_literal(&v).as_str());
+ }
+ result.push('}');
+ }
+
+ result.push('>');
+ result
+ }
}
}
@@ -963,6 +1032,14 @@ mod tests {
assert_eq!(stripped.trim(), result.trim());
}
+ #[test]
+ fn test_flyweight() {
+ let program = r#"return <#1, [slot -> "123"], {1, 2, 3}>;"#;
+ let stripped = unindent(program);
+ let result = parse_and_unparse(&stripped).unwrap();
+ assert_eq!(stripped.trim(), result.trim());
+ }
+
pub fn parse_and_unparse(original: &str) -> Result {
let tree = crate::parse::parse_program(original, CompileOptions::default()).unwrap();
Ok(unparse(&tree)?.join("\n"))
diff --git a/crates/daemon/src/main.rs b/crates/daemon/src/main.rs
index b43a9929..e4184a14 100644
--- a/crates/daemon/src/main.rs
+++ b/crates/daemon/src/main.rs
@@ -198,6 +198,13 @@ struct Args {
default_value = "true"
)]
type_dispatch: bool,
+
+ #[arg(
+ long,
+ help = "Enable flyweight types. Flyweights are a lightweight, object delegate",
+ default_value = "true"
+ )]
+ flyweight_type: bool,
}
fn main() -> Result<(), Report> {
@@ -263,6 +270,7 @@ fn main() -> Result<(), Report> {
lexical_scopes: args.lexical_scopes,
map_type: args.map_type,
type_dispatch: args.type_dispatch,
+ flyweight_type: args.flyweight_type,
});
// If the database already existed, do not try to import the textdump...
diff --git a/crates/kernel/benches/vm_benches.rs b/crates/kernel/benches/vm_benches.rs
index f7cd042d..899b3f92 100644
--- a/crates/kernel/benches/vm_benches.rs
+++ b/crates/kernel/benches/vm_benches.rs
@@ -35,7 +35,7 @@ use moor_values::model::{BinaryType, VerbFlag};
use moor_values::model::{WorldState, WorldStateSource};
use moor_values::tasks::AbortLimitReason;
use moor_values::util::BitEnum;
-use moor_values::Symbol;
+use moor_values::{v_obj, Symbol};
use moor_values::{AsByteBuffer, Var, NOTHING, SYSTEM_OBJECT};
fn create_db() -> TxDB {
@@ -66,12 +66,12 @@ pub fn prepare_call_verb(
vi,
VerbCall {
verb_name,
- location: SYSTEM_OBJECT,
- this: SYSTEM_OBJECT,
+ location: v_obj(SYSTEM_OBJECT),
+ this: v_obj(SYSTEM_OBJECT),
player: SYSTEM_OBJECT,
args,
argstr: "".to_string(),
- caller: SYSTEM_OBJECT,
+ caller: v_obj(SYSTEM_OBJECT),
},
);
vm_host
diff --git a/crates/kernel/src/builtins/bf_objects.rs b/crates/kernel/src/builtins/bf_objects.rs
index e01a1396..4e1b3773 100644
--- a/crates/kernel/src/builtins/bf_objects.rs
+++ b/crates/kernel/src/builtins/bf_objects.rs
@@ -164,8 +164,8 @@ fn bf_create(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *INITIALIZE_SYM,
- location: new_obj.clone(),
- this: new_obj.clone(),
+ location: v_obj(new_obj.clone()),
+ this: v_obj(new_obj),
player: bf_args.exec_state.top().player.clone(),
args: vec![],
argstr: "".to_string(),
@@ -275,8 +275,8 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *RECYCLE_SYM,
- location: obj.clone(),
- this: obj,
+ location: v_obj(obj.clone()),
+ this: v_obj(obj),
player: bf_args.exec_state.top().player.clone(),
args: Vec::new(),
argstr: "".to_string(),
@@ -344,8 +344,8 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *EXITFUNC_SYM,
- location: head_obj.clone(),
- this: head_obj.clone(),
+ location: v_obj(head_obj.clone()),
+ this: v_obj(head_obj.clone()),
player: bf_args.exec_state.top().player.clone(),
args: vec![v_obj(obj)],
argstr: "".to_string(),
@@ -446,8 +446,8 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *ACCEPT_SYM,
- location: whereto.clone(),
- this: whereto.clone(),
+ location: v_obj(whereto.clone()),
+ this: v_obj(whereto.clone()),
player: bf_args.exec_state.top().player.clone(),
args: vec![v_obj(what)],
argstr: "".to_string(),
@@ -524,8 +524,8 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *EXITFUNC_SYM,
- location: original_location.clone(),
- this: original_location,
+ location: v_obj(original_location.clone()),
+ this: v_obj(original_location),
player: bf_args.exec_state.top().player.clone(),
args: vec![v_obj(what)],
argstr: "".to_string(),
@@ -571,8 +571,8 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result {
binary,
call: VerbCall {
verb_name: *ENTERFUNC_SYM,
- location: whereto.clone(),
- this: whereto,
+ location: v_obj(whereto.clone()),
+ this: v_obj(whereto),
player: bf_args.exec_state.top().player.clone(),
args: vec![v_obj(what)],
argstr: "".to_string(),
diff --git a/crates/kernel/src/builtins/bf_server.rs b/crates/kernel/src/builtins/bf_server.rs
index 5be5bcad..80251076 100644
--- a/crates/kernel/src/builtins/bf_server.rs
+++ b/crates/kernel/src/builtins/bf_server.rs
@@ -171,7 +171,7 @@ fn bf_callers(bf_args: &mut BfCallState<'_>) -> Result {
Ok(Ret(v_list_iter(callers.iter().map(|c| {
let callers = vec![
// this
- v_obj(c.this.clone()),
+ c.this.clone(),
// verb name
v_string(c.verb_name.to_string()),
// 'programmer'
@@ -459,7 +459,7 @@ fn bf_queued_tasks(bf_args: &mut BfCallState<'_>) -> Result {
let verb_loc = v_obj(task.verb_definer.clone());
let verb_name = v_str(task.verb_name.as_str());
let line = v_int(task.line_number as i64);
- let this = v_obj(task.this.clone());
+ let this = task.this.clone();
v_list(&[
task_id, start_time, x, y, programmer, verb_loc, verb_name, line, this,
])
diff --git a/crates/kernel/src/builtins/bf_values.rs b/crates/kernel/src/builtins/bf_values.rs
index 7579a984..7ca09bb9 100644
--- a/crates/kernel/src/builtins/bf_values.rs
+++ b/crates/kernel/src/builtins/bf_values.rs
@@ -42,6 +42,13 @@ fn bf_tostr(bf_args: &mut BfCallState<'_>) -> Result {
Variant::List(_) => result.push_str("{list}"),
Variant::Map(_) => result.push_str("[map]"),
Variant::Err(e) => result.push_str(e.name()),
+ Variant::Flyweight(fl) => {
+ if fl.is_sealed() {
+ result.push_str("")
+ } else {
+ result.push_str("")
+ }
+ }
}
}
Ok(Ret(v_str(result.as_str())))
diff --git a/crates/kernel/src/config.rs b/crates/kernel/src/config.rs
index e040d7f2..0cdba02b 100644
--- a/crates/kernel/src/config.rs
+++ b/crates/kernel/src/config.rs
@@ -37,6 +37,8 @@ pub struct Config {
/// Whether to support primitive-type verb dispatching. E.g. "test":reverse() becomes
/// $string:reverse("test")
pub type_dispatch: bool,
+ /// Whether to support flyweight types. Flyweights are a lightweight, non-persistent thingy
+ pub flyweight_type: bool,
}
impl Default for Config {
@@ -48,6 +50,7 @@ impl Default for Config {
lexical_scopes: true,
map_type: true,
type_dispatch: true,
+ flyweight_type: true,
}
}
}
@@ -57,6 +60,7 @@ impl Config {
CompileOptions {
lexical_scopes: self.lexical_scopes,
map_type: self.map_type,
+ flyweight_type: self.flyweight_type,
}
}
}
diff --git a/crates/kernel/src/tasks/mod.rs b/crates/kernel/src/tasks/mod.rs
index 8e2f7ec2..5cc06d7a 100644
--- a/crates/kernel/src/tasks/mod.rs
+++ b/crates/kernel/src/tasks/mod.rs
@@ -81,12 +81,12 @@ impl TaskHandle {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VerbCall {
pub verb_name: Symbol,
- pub location: Obj,
- pub this: Obj,
+ pub location: Var,
+ pub this: Var,
pub player: Obj,
pub args: Vec,
pub argstr: String,
- pub caller: Obj,
+ pub caller: Var,
}
/// External interface description of a task, for purpose of e.g. the queued_tasks() builtin.
@@ -98,7 +98,7 @@ pub struct TaskDescription {
pub verb_name: Symbol,
pub verb_definer: Obj,
pub line_number: usize,
- pub this: Obj,
+ pub this: Var,
}
/// The set of options that can be configured for the server via core $server_options.
@@ -133,8 +133,8 @@ pub mod vm_test_utils {
use moor_compiler::Program;
use moor_values::model::WorldState;
- use moor_values::Symbol;
use moor_values::SYSTEM_OBJECT;
+ use moor_values::{v_obj, Symbol};
use moor_values::{Obj, Var};
use crate::builtins::BuiltinRegistry;
@@ -223,12 +223,12 @@ pub mod vm_test_utils {
vi,
VerbCall {
verb_name,
- location: SYSTEM_OBJECT,
- this: SYSTEM_OBJECT,
+ location: v_obj(SYSTEM_OBJECT),
+ this: v_obj(SYSTEM_OBJECT),
player: SYSTEM_OBJECT,
args,
argstr: "".to_string(),
- caller: SYSTEM_OBJECT,
+ caller: v_obj(SYSTEM_OBJECT),
},
);
})
@@ -336,7 +336,7 @@ pub enum TaskStart {
/// The scheduler is telling the task to run a (method) verb.
StartVerb {
player: Obj,
- vloc: Obj,
+ vloc: Var,
verb: Symbol,
args: Vec,
argstr: String,
diff --git a/crates/kernel/src/tasks/scheduler.rs b/crates/kernel/src/tasks/scheduler.rs
index 298043fc..0ff83715 100644
--- a/crates/kernel/src/tasks/scheduler.rs
+++ b/crates/kernel/src/tasks/scheduler.rs
@@ -399,10 +399,10 @@ impl Scheduler {
.expect("Could not send task handle reply");
return;
};
- vloc
+ v_obj(vloc)
} else {
match vloc {
- ObjectRef::Id(id) => id,
+ ObjectRef::Id(id) => v_obj(id),
_ => panic!("Unexpected object reference in vloc"),
}
};
@@ -479,7 +479,7 @@ impl Scheduler {
let args = command.into_iter().map(v_string).collect::>();
let task_start = Arc::new(TaskStart::StartVerb {
player: player.clone(),
- vloc: handler_object,
+ vloc: v_obj(handler_object),
verb: *DO_OUT_OF_BAND_COMMAND,
args,
argstr,
diff --git a/crates/kernel/src/tasks/task.rs b/crates/kernel/src/tasks/task.rs
index 7a229a9c..6b37fada 100644
--- a/crates/kernel/src/tasks/task.rs
+++ b/crates/kernel/src/tasks/task.rs
@@ -41,9 +41,9 @@ use moor_values::tasks::CommandError;
use moor_values::tasks::CommandError::PermissionDenied;
use moor_values::tasks::TaskId;
use moor_values::util::parse_into_words;
-use moor_values::Obj;
-use moor_values::Symbol;
use moor_values::{v_int, v_str};
+use moor_values::{v_obj, Obj};
+use moor_values::{Symbol, Variant};
use moor_values::{NOTHING, SYSTEM_OBJECT};
use crate::builtins::BuiltinRegistry;
@@ -370,12 +370,29 @@ impl Task {
player: player.clone(),
args: args.clone(),
argstr: argstr.clone(),
- caller: NOTHING,
+ caller: v_obj(NOTHING),
};
// Find the callable verb ...
+ // Obj or flyweight?
+ let object_location = match &verb_call.this.variant() {
+ Variant::Flyweight(f) => f.delegate().clone(),
+ Variant::Obj(o) => o.clone(),
+ _ => {
+ control_sender
+ .send((
+ self.task_id,
+ TaskControlMsg::TaskVerbNotFound(
+ verb_call.this,
+ verb_call.verb_name,
+ ),
+ ))
+ .expect("Could not send start response");
+ return false;
+ }
+ };
match world_state.find_method_verb_on(
&self.perms,
- &verb_call.this,
+ &object_location,
verb_call.verb_name,
) {
Err(WorldStateError::VerbNotFound(_, _)) => {
@@ -461,12 +478,12 @@ impl Task {
let args = arguments.iter().map(|s| v_str(s)).collect::>();
let verb_call = VerbCall {
verb_name: Symbol::mk("do_command"),
- location: handler_object.clone(),
- this: handler_object.clone(),
+ location: v_obj(handler_object.clone()),
+ this: v_obj(handler_object.clone()),
player: player.clone(),
args,
argstr: command.to_string(),
- caller: handler_object.clone(),
+ caller: v_obj(handler_object.clone()),
};
self.vm_host.start_call_method_verb(
self.task_id,
@@ -558,12 +575,12 @@ impl Task {
};
let verb_call = VerbCall {
verb_name: Symbol::mk_case_insensitive(parsed_command.verb.as_str()),
- location: target.clone(),
- this: target,
+ location: v_obj(target.clone()),
+ this: v_obj(target),
player: player.clone(),
args: parsed_command.args.clone(),
argstr: parsed_command.argstr.clone(),
- caller: player.clone(),
+ caller: v_obj(player.clone()),
};
self.vm_host.start_call_command_verb(
self.task_id,
@@ -681,8 +698,8 @@ mod tests {
use moor_values::tasks::{CommandError, Event, TaskId};
use moor_values::util::BitEnum;
use moor_values::Error::E_DIV;
- use moor_values::Symbol;
use moor_values::{v_int, v_str};
+ use moor_values::{v_obj, Symbol};
use moor_values::{AsByteBuffer, NOTHING, SYSTEM_OBJECT};
use crate::builtins::BuiltinRegistry;
@@ -892,7 +909,7 @@ mod tests {
panic!("Expected Notify, got {:?}", msg);
};
assert_eq!(player, SYSTEM_OBJECT);
- assert_eq!(event.author(), &SYSTEM_OBJECT);
+ assert_eq!(event.author(), &v_obj(SYSTEM_OBJECT));
assert_eq!(event.event, Event::Notify(v_str("12345"), None));
// Also scheduler should have received a TaskSuccess message.
diff --git a/crates/kernel/src/tasks/task_scheduler_client.rs b/crates/kernel/src/tasks/task_scheduler_client.rs
index 7ac1ddba..b0bc6dee 100644
--- a/crates/kernel/src/tasks/task_scheduler_client.rs
+++ b/crates/kernel/src/tasks/task_scheduler_client.rs
@@ -64,9 +64,9 @@ impl TaskSchedulerClient {
}
/// Send a message to the scheduler that the verb to be executed was not found.
- pub fn verb_not_found(&self, objid: Obj, verb: Symbol) {
+ pub fn verb_not_found(&self, what: Var, verb: Symbol) {
self.scheduler_sender
- .send((self.task_id, TaskControlMsg::TaskVerbNotFound(objid, verb)))
+ .send((self.task_id, TaskControlMsg::TaskVerbNotFound(what, verb)))
.expect("Could not deliver client message -- scheduler shut down?");
}
@@ -270,7 +270,7 @@ pub enum TaskControlMsg {
/// A 'StartCommandVerb' type task failed to parse or match the command.
TaskCommandError(CommandError),
/// The verb to be executed was not found.
- TaskVerbNotFound(Obj, Symbol),
+ TaskVerbNotFound(Var, Symbol),
/// An exception was thrown while executing the verb.
TaskException(Exception),
/// The task is requesting that it be forked.
diff --git a/crates/kernel/src/tasks/vm_host.rs b/crates/kernel/src/tasks/vm_host.rs
index 95f59815..69f367cf 100644
--- a/crates/kernel/src/tasks/vm_host.rs
+++ b/crates/kernel/src/tasks/vm_host.rs
@@ -442,7 +442,7 @@ impl VmHost {
pub fn verb_definer(&self) -> Obj {
self.vm_exec_state.top().verb_definer()
}
- pub fn this(&self) -> Obj {
+ pub fn this(&self) -> Var {
self.vm_exec_state.top().this.clone()
}
pub fn line_number(&self) -> usize {
diff --git a/crates/kernel/src/textdump/read.rs b/crates/kernel/src/textdump/read.rs
index 3c947903..1cf317ce 100644
--- a/crates/kernel/src/textdump/read.rs
+++ b/crates/kernel/src/textdump/read.rs
@@ -21,8 +21,8 @@ use tracing::info;
use moor_compiler::Label;
use moor_values::model::CompileError;
use moor_values::model::WorldStateError;
-use moor_values::Obj;
-use moor_values::{v_err, v_float, v_int, v_none, v_obj, v_str, Var, VarType};
+use moor_values::{v_err, v_float, v_int, v_none, v_obj, v_str, List, Symbol, Var, VarType};
+use moor_values::{v_flyweight, Obj};
use moor_values::{v_list, v_map, Error};
use crate::textdump::{EncodingMode, Object, Propval, Textdump, Verb, Verbdef};
@@ -168,6 +168,31 @@ impl TextdumpReader {
let l = Label(l_num as u16);
v_int(l.0 as i64)
}
+ VarType::TYPE_FLYWEIGHT => {
+ let delegate = self.read_objid()?;
+ let num_slots = self.read_num()?;
+ let mut slots = Vec::with_capacity(num_slots as usize);
+ for _ in 0..num_slots {
+ let key = self.read_string().unwrap();
+ let key = Symbol::mk(&key);
+ let value = self.read_var().unwrap();
+ slots.push((key, value));
+ }
+ let c_size = self.read_num()?;
+ let contents: Vec = (0..c_size).map(|_i| self.read_var().unwrap()).collect();
+ let seal = if self.read_num()? == 1 {
+ Some(self.read_string()?)
+ } else {
+ None
+ };
+
+ v_flyweight(
+ delegate,
+ &slots,
+ List::from_iter(contents),
+ seal,
+ )
+ }
};
Ok(v)
}
diff --git a/crates/kernel/src/textdump/write.rs b/crates/kernel/src/textdump/write.rs
index 04265fd2..75d0a899 100644
--- a/crates/kernel/src/textdump/write.rs
+++ b/crates/kernel/src/textdump/write.rs
@@ -99,6 +99,29 @@ impl TextdumpWriter {
// sprintf(buffer, "%%.%dg\n", DBL_DIG + 4);
writeln!(self.writer, "{}\n{:+e}", VarType::TYPE_FLOAT as i64, f)?;
}
+ Variant::Flyweight(flyweight) => {
+ // delegate, slots (len, [key, value, ...]), contents (len, ...), seal (1/0, string)
+ writeln!(self.writer, "{}", VarType::TYPE_FLYWEIGHT as i64)?;
+ writeln!(self.writer, "{}", flyweight.delegate().id().0)?;
+ writeln!(self.writer, "{}", flyweight.slots().len())?;
+ for (k, v) in flyweight.slots().iter() {
+ writeln!(self.writer, "{}", k)?;
+ self.write_var(v, false)?;
+ }
+ writeln!(self.writer, "{}", flyweight.contents().len())?;
+ for v in flyweight.contents().iter() {
+ self.write_var(&v, false)?;
+ }
+ match flyweight.seal() {
+ Some(s) => {
+ writeln!(self.writer, "1")?;
+ writeln!(self.writer, "{}", s)?;
+ }
+ None => {
+ writeln!(self.writer, "0")?;
+ }
+ }
+ }
}
Ok(())
}
diff --git a/crates/kernel/src/vm/activation.rs b/crates/kernel/src/vm/activation.rs
index 316e6e11..7c635bfd 100644
--- a/crates/kernel/src/vm/activation.rs
+++ b/crates/kernel/src/vm/activation.rs
@@ -50,7 +50,7 @@ pub(crate) struct Activation {
/// running this activation.
pub(crate) frame: Frame,
/// The object that is the receiver of the current verb call.
- pub(crate) this: Obj,
+ pub(crate) this: Var,
/// The object that is the 'player' role; that is, the active user of this task.
pub(crate) player: Obj,
/// The arguments to the verb or bf being called.
@@ -89,7 +89,7 @@ impl Encode for Activation {
impl Decode for Activation {
fn decode(decoder: &mut D) -> Result {
let frame = Frame::decode(decoder)?;
- let this = Obj::decode(decoder)?;
+ let this = Var::decode(decoder)?;
let player = Obj::decode(decoder)?;
let args = Vec::::decode(decoder)?;
let verb_name = Symbol::decode(decoder)?;
@@ -116,7 +116,7 @@ impl Decode for Activation {
impl<'de> BorrowDecode<'de> for Activation {
fn borrow_decode>(decoder: &mut D) -> Result {
let frame = Frame::decode(decoder)?;
- let this = Obj::decode(decoder)?;
+ let this = Var::decode(decoder)?;
let player = Obj::decode(decoder)?;
let args = Vec::::decode(decoder)?;
let verb_name = Symbol::decode(decoder)?;
@@ -235,15 +235,12 @@ impl Activation {
let frame = MooStackFrame::new(program);
let mut frame = Frame::Moo(frame);
set_constants(&mut frame);
- frame.set_global_variable(GlobalName::this, v_obj(verb_call_request.call.this.clone()));
+ frame.set_global_variable(GlobalName::this, verb_call_request.call.this.clone());
frame.set_global_variable(
GlobalName::player,
v_obj(verb_call_request.call.player.clone()),
);
- frame.set_global_variable(
- GlobalName::caller,
- v_obj(verb_call_request.call.caller.clone()),
- );
+ frame.set_global_variable(GlobalName::caller, verb_call_request.call.caller.clone());
frame.set_global_variable(
GlobalName::verb,
v_str(verb_call_request.call.verb_name.as_str()),
@@ -335,7 +332,7 @@ impl Activation {
Self {
frame,
- this: player.clone(),
+ this: v_obj(player.clone()),
player: player.clone(),
verbdef,
verb_name: *EVAL_SYMBOL,
@@ -371,7 +368,7 @@ impl Activation {
let frame = Frame::Bf(bf_frame);
Self {
frame,
- this: NOTHING,
+ this: v_obj(NOTHING),
player,
verbdef,
verb_name: bf_name,
diff --git a/crates/kernel/src/vm/exec_state.rs b/crates/kernel/src/vm/exec_state.rs
index c7dc8a04..002da982 100644
--- a/crates/kernel/src/vm/exec_state.rs
+++ b/crates/kernel/src/vm/exec_state.rs
@@ -16,8 +16,8 @@ use std::time::{Duration, SystemTime};
use bincode::{Decode, Encode};
-use moor_values::Var;
use moor_values::NOTHING;
+use moor_values::{v_obj, Var};
use moor_values::{Obj, Symbol};
use crate::vm::activation::{Activation, Frame};
@@ -27,7 +27,7 @@ use moor_values::tasks::TaskId;
// {this, verb-name, programmer, verb-loc, player, line-number}
#[derive(Clone)]
pub struct Caller {
- pub this: Obj,
+ pub this: Var,
pub verb_name: Symbol,
pub programmer: Obj,
pub definer: Obj,
@@ -115,7 +115,7 @@ impl VMExecState {
}
/// Return the object that called the current activation.
- pub(crate) fn caller(&self) -> Obj {
+ pub(crate) fn caller(&self) -> Var {
let stack_iter = self.stack.iter().rev();
// Skip builtin-frames (for now?)
@@ -125,7 +125,7 @@ impl VMExecState {
}
return activation.this.clone();
}
- NOTHING
+ v_obj(NOTHING)
}
/// Return the activation record of the caller of the current activation.
@@ -156,9 +156,9 @@ impl VMExecState {
stack_top.map(|a| a.permissions.clone()).unwrap_or(NOTHING)
}
- pub(crate) fn this(&self) -> Obj {
+ pub(crate) fn this(&self) -> Var {
let stack_top = self.stack.iter().rev().find(|a| !a.is_builtin_frame());
- stack_top.map(|a| a.this.clone()).unwrap_or(NOTHING)
+ stack_top.map(|a| a.this.clone()).unwrap_or(v_obj(NOTHING))
}
/// Update the permissions of the current task, as called by the `set_task_perms`
diff --git a/crates/kernel/src/vm/moo_execute.rs b/crates/kernel/src/vm/moo_execute.rs
index efd79099..e5a821c1 100644
--- a/crates/kernel/src/vm/moo_execute.rs
+++ b/crates/kernel/src/vm/moo_execute.rs
@@ -12,37 +12,24 @@
// this program. If not, see .
//
-use lazy_static::lazy_static;
use std::ops::Add;
use std::sync::Arc;
use std::time::Duration;
-use tracing::debug;
use crate::tasks::sessions::Session;
use crate::vm::activation::Frame;
use crate::vm::moo_frame::{CatchType, ScopeType};
use crate::vm::vm_unwind::FinallyReason;
use crate::vm::{ExecutionResult, Fork, VMExecState, VmExecParams};
-use moor_compiler::{Op, ScatterLabel};
+use moor_compiler::{to_literal, Op, ScatterLabel};
use moor_values::model::WorldState;
-use moor_values::model::WorldStateError;
use moor_values::Error::{E_ARGS, E_DIV, E_INVARG, E_INVIND, E_TYPE, E_VARNF};
use moor_values::{
- v_bool, v_empty_list, v_empty_map, v_err, v_float, v_int, v_list, v_none, v_obj, IndexMode,
- Obj, Sequence,
+ v_bool, v_empty_list, v_empty_map, v_err, v_float, v_flyweight, v_int, v_list, v_map, v_none,
+ v_obj, v_str, Error, IndexMode, Obj, Sequence, Str, Var, Variant,
};
use moor_values::{Symbol, VarType};
-use moor_values::{Variant, SYSTEM_OBJECT};
-
-lazy_static! {
- static ref LIST_SYM: Symbol = Symbol::mk("list");
- static ref MAP_SYM: Symbol = Symbol::mk("map");
- static ref STRING_SYM: Symbol = Symbol::mk("string");
- static ref INTEGER_SYM: Symbol = Symbol::mk("integer");
- static ref FLOAT_SYM: Symbol = Symbol::mk("float");
- static ref ERROR_SYM: Symbol = Symbol::mk("error");
-}
macro_rules! binary_bool_op {
( $f:ident, $op:tt ) => {
@@ -298,7 +285,7 @@ pub fn moo_frame_execute(
Op::ImmEmptyList => f.push(v_empty_list()),
Op::ListAddTail => {
let (tail, list) = (f.pop(), f.peek_top_mut());
- if list.type_code() != VarType::TYPE_LIST {
+ if !list.is_sequence() || list.type_code() == VarType::TYPE_STR {
f.pop();
return state.push_error(E_TYPE);
}
@@ -318,7 +305,12 @@ pub fn moo_frame_execute(
let (tail, list) = (f.pop(), f.peek_top_mut());
// Don't allow strings here.
- if tail.type_code() != list.type_code() || list.type_code() != VarType::TYPE_LIST {
+ if list.type_code() == VarType::TYPE_STR {
+ f.pop();
+ return state.push_error(E_TYPE);
+ }
+
+ if !tail.is_sequence() || !list.is_sequence() {
f.pop();
return state.push_error(E_TYPE);
}
@@ -366,6 +358,31 @@ pub fn moo_frame_execute(
}
}
}
+ Op::MakeFlyweight(num_slots) => {
+ // Stack should be: contents, slots, delegate
+ let contents = f.pop();
+ // Contents must be a list
+ let Variant::List(contents) = contents.variant() else {
+ return state.push_error(E_TYPE);
+ };
+ let mut slots = Vec::with_capacity(*num_slots);
+ for _ in 0..*num_slots {
+ let (k, v) = (f.pop(), f.pop());
+ let Variant::Str(k) = k.variant() else {
+ return state.push_error(E_TYPE);
+ };
+ let sym = Symbol::mk_case_insensitive(k.as_string());
+ slots.push((sym, v));
+ }
+ let delegate = f.pop();
+ let Variant::Obj(delegate) = delegate.variant() else {
+ return state.push_error(E_TYPE);
+ };
+ // Slots should be v_str -> value, num_slots times
+
+ let flyweight = v_flyweight(delegate.clone(), &slots, contents.clone(), None);
+ f.push(flyweight);
+ }
Op::PutTemp => {
f.temp = f.peek_top().clone();
}
@@ -529,22 +546,15 @@ pub fn moo_frame_execute(
return state.push_error(E_TYPE);
};
- let Variant::Obj(obj) = obj.variant() else {
- return state.push_error(E_INVIND);
- };
- let propname = Symbol::mk_case_insensitive(propname.as_string());
- let result = world_state.retrieve_property(&a.permissions, obj, propname);
- match result {
+ let value = get_property(world_state, &a.permissions, obj, propname);
+ match value {
Ok(v) => {
f.poke(0, v);
}
- Err(WorldStateError::RollbackRetry) => {
- return ExecutionResult::RollbackRestart;
- }
Err(e) => {
- return state.push_error(e.to_error_code());
+ return state.push_error(e);
}
- };
+ }
}
Op::PushGetProp => {
let (propname, obj) = f.peek2();
@@ -553,23 +563,15 @@ pub fn moo_frame_execute(
return state.push_error(E_TYPE);
};
- let Variant::Obj(obj) = obj.variant() else {
- return state.push_error(E_INVIND);
- };
- let propname = Symbol::mk_case_insensitive(propname.as_string());
- let result = world_state.retrieve_property(&a.permissions, obj, propname);
- match result {
+ let value = get_property(world_state, &a.permissions, obj, propname);
+ match value {
Ok(v) => {
f.push(v);
}
- Err(WorldStateError::RollbackRetry) => {
- return ExecutionResult::RollbackRestart;
- }
Err(e) => {
- debug!(obj = ?obj, propname = propname.as_str(), "Error resolving property");
- return state.push_error(e.to_error_code());
+ return state.push_error(e);
}
- };
+ }
}
Op::PutProp => {
let (rhs, propname, obj) = (f.pop(), f.pop(), f.peek_top());
@@ -589,7 +591,6 @@ pub fn moo_frame_execute(
Ok(()) => {
f.poke(0, rhs);
}
- Err(WorldStateError::RollbackRetry) => return ExecutionResult::RollbackRestart,
Err(e) => {
return state.push_error(e.to_error_code());
}
@@ -632,56 +633,17 @@ pub fn moo_frame_execute(
}
Op::CallVerb => {
let (args, verb, obj) = (f.pop(), f.pop(), f.pop());
- let (args, verb, obj) = match (args.variant(), verb.variant(), obj.variant()) {
- (Variant::List(l), Variant::Str(s), Variant::Obj(o)) => {
- (l.clone(), s, o.clone())
- }
- (Variant::List(l), Variant::Str(s), non_obj) => {
- if !exec_params.config.type_dispatch {
- return state.push_error(E_TYPE);
- }
- // If the object is not an object, we look at its type, and look for a
- // sysprop that corresponds, then dispatch to that, with the object as the
- // first argument.
- // e.g. "blah":reverse() becomes $string:reverse("blah")
- let sysprop_sym = match non_obj {
- Variant::Int(_) => *INTEGER_SYM,
- Variant::Float(_) => *FLOAT_SYM,
- Variant::Str(_) => *STRING_SYM,
- Variant::List(_) => *LIST_SYM,
- Variant::Map(_) => *MAP_SYM,
- Variant::Err(_) => *ERROR_SYM,
- _ => {
- return state.push_error(E_TYPE);
- }
- };
- let prop_val = match world_state.retrieve_property(
- &a.permissions,
- &SYSTEM_OBJECT,
- sysprop_sym,
- ) {
- Ok(prop_val) => prop_val,
- Err(e) => {
- return state.push_error(e.to_error_code());
- }
- };
- let Variant::Obj(prop_val) = prop_val.variant() else {
- return state.push_error(E_TYPE);
- };
- let arguments = l
- .insert(0, &obj)
- .expect("Failed to insert object for dispatch");
- let Variant::List(arguments) = arguments.variant() else {
- return state.push_error(E_TYPE);
- };
- (arguments.clone(), s, prop_val.clone())
- }
- _ => {
- return state.push_error(E_TYPE);
- }
+ let (Variant::List(l), Variant::Str(s)) = (args.variant(), verb.variant()) else {
+ return state.push_error(E_TYPE);
};
- let verb = Symbol::mk_case_insensitive(verb.as_string());
- return state.prepare_call_verb(world_state, &obj, verb, args);
+ let verb = Symbol::mk_case_insensitive(s.as_string());
+ let result = state.verb_dispatch(exec_params, world_state, obj, verb, l.clone());
+ match result {
+ Ok(r) => return r,
+ Err(e) => {
+ return state.push_error(e);
+ }
+ }
}
Op::Return => {
let ret_val = f.pop();
@@ -763,7 +725,7 @@ pub fn moo_frame_execute(
let ScopeType::TryCatch(..) = handler.scope_type else {
panic!(
"Handler is not a catch handler; {}:{} line {}",
- a.this,
+ to_literal(&a.this),
a.verb_name,
f.find_line_no(f.pc - 1).unwrap()
);
@@ -894,10 +856,10 @@ pub fn moo_frame_execute(
}
}
Op::CheckListForSplice => {
- let Variant::List(_) = f.peek_top().variant() else {
+ if !f.peek_top().is_sequence() {
f.pop();
return state.push_error(E_TYPE);
- };
+ }
}
}
}
@@ -906,3 +868,55 @@ pub fn moo_frame_execute(
// us.
ExecutionResult::More
}
+
+fn get_property(
+ world_state: &mut dyn WorldState,
+ permissions: &Obj,
+ obj: &Var,
+ propname: &Str,
+) -> Result {
+ match obj.variant() {
+ Variant::Obj(obj) => {
+ let propname = Symbol::mk_case_insensitive(propname.as_string());
+ let result = world_state.retrieve_property(permissions, obj, propname);
+ match result {
+ Ok(v) => Ok(v),
+ Err(e) => Err(e.to_error_code()),
+ }
+ }
+ Variant::Flyweight(flyweight) => {
+ let propname = Symbol::mk_case_insensitive(propname.as_string());
+
+ // If propname is `delegate`, return the delegate object.
+ // If the propname is `slots`, return the slots list.
+ // Otherwise, return the value from the slots list.
+ let value = match propname.as_str() {
+ "delegate" => v_obj(flyweight.delegate().clone()),
+ "slots" => {
+ let slots: Vec<_> = flyweight
+ .slots()
+ .iter()
+ .map(|(k, v)| (v_str(k.as_str()), v.clone()))
+ .collect();
+
+ v_map(&slots)
+ }
+ _ => {
+ if let Some(result) = flyweight.get_slot(&propname) {
+ result.clone()
+ } else {
+ // Now check the delegate
+ let delegate = flyweight.delegate();
+ let result = world_state.retrieve_property(permissions, delegate, propname);
+ match result {
+ Ok(v) => v,
+ Err(e) => return Err(e.to_error_code()),
+ }
+ }
+ }
+ };
+ Ok(value)
+ }
+ _ => Err(E_INVIND),
+ }
+}
diff --git a/crates/kernel/src/vm/vm_call.rs b/crates/kernel/src/vm/vm_call.rs
index 113de50c..6ba1c3fd 100644
--- a/crates/kernel/src/vm/vm_call.rs
+++ b/crates/kernel/src/vm/vm_call.rs
@@ -12,17 +12,17 @@
// this program. If not, see .
//
+use lazy_static::lazy_static;
use std::sync::Arc;
-
use tracing::trace;
use moor_compiler::{to_literal, BuiltinId, Program, BUILTINS};
use moor_values::model::VerbDef;
use moor_values::model::WorldState;
use moor_values::model::WorldStateError;
-use moor_values::Error::{E_INVIND, E_PERM, E_VERBNF};
-use moor_values::Symbol;
-use moor_values::{v_int, Var};
+use moor_values::Error::{E_INVIND, E_PERM, E_TYPE, E_VERBNF};
+use moor_values::{v_int, v_obj, Var};
+use moor_values::{Error, Sequence, Symbol, Variant, SYSTEM_OBJECT};
use moor_values::{List, Obj};
use crate::builtins::{BfCallState, BfErr, BfRet};
@@ -34,6 +34,15 @@ use crate::vm::{ExecutionResult, Fork};
use crate::vm::{VMExecState, VmExecParams};
use moor_values::matching::command_parse::ParsedCommand;
+lazy_static! {
+ static ref LIST_SYM: Symbol = Symbol::mk("list");
+ static ref MAP_SYM: Symbol = Symbol::mk("map");
+ static ref STRING_SYM: Symbol = Symbol::mk("string");
+ static ref INTEGER_SYM: Symbol = Symbol::mk("integer");
+ static ref FLOAT_SYM: Symbol = Symbol::mk("float");
+ static ref ERROR_SYM: Symbol = Symbol::mk("error");
+}
+
pub(crate) fn args_literal(args: &[Var]) -> String {
args.iter()
.map(to_literal)
@@ -62,21 +71,73 @@ pub enum VerbProgram {
}
impl VMExecState {
- /// Entry point for preparing a verb call for execution, invoked from the CallVerb opcode
- /// Seek the verb and prepare the call parameters.
- /// All parameters for player, caller, etc. are pulled off the stack.
- /// The call params will be returned back to the task in the scheduler, which will then dispatch
- /// back through to `do_method_call`
- pub(crate) fn prepare_call_verb(
+ /// Entry point for dispatching a verb (method) call.
+ /// Called from the VM execution loop for CallVerb opcodes.
+ pub(crate) fn verb_dispatch(
+ &mut self,
+ exec_params: &VmExecParams,
+ world_state: &mut dyn WorldState,
+ target: Var,
+ verb: Symbol,
+ args: List,
+ ) -> Result {
+ let (args, this, location) = match target.variant() {
+ Variant::Obj(o) => (args, target.clone(), o.clone()),
+ Variant::Flyweight(f) => (args, target.clone(), f.delegate().clone()),
+ non_obj => {
+ if !exec_params.config.type_dispatch {
+ return Err(E_TYPE);
+ }
+ // If the object is not an object of frob, it's a primitive.
+ // For primitives, we look at its type, and look for a
+ // sysprop that corresponds, then dispatch to that, with the object as the
+ // first argument.
+ // e.g. "blah":reverse() becomes $string:reverse("blah")
+ let sysprop_sym = match non_obj {
+ Variant::Int(_) => *INTEGER_SYM,
+ Variant::Float(_) => *FLOAT_SYM,
+ Variant::Str(_) => *STRING_SYM,
+ Variant::List(_) => *LIST_SYM,
+ Variant::Map(_) => *MAP_SYM,
+ Variant::Err(_) => *ERROR_SYM,
+ _ => {
+ return Err(E_TYPE);
+ }
+ };
+ let perms = self.top().permissions.clone();
+ let prop_val =
+ match world_state.retrieve_property(&perms, &SYSTEM_OBJECT, sysprop_sym) {
+ Ok(prop_val) => prop_val,
+ Err(e) => {
+ return Err(e.to_error_code());
+ }
+ };
+ let Variant::Obj(prop_val) = prop_val.variant() else {
+ return Err(E_TYPE);
+ };
+ let arguments = args
+ .insert(0, &target)
+ .expect("Failed to insert object for dispatch");
+ let Variant::List(arguments) = arguments.variant() else {
+ return Err(E_TYPE);
+ };
+ (arguments.clone(), v_obj(prop_val.clone()), prop_val.clone())
+ }
+ };
+ Ok(self.prepare_call_verb(world_state, location, this, verb, args.clone()))
+ }
+
+ fn prepare_call_verb(
&mut self,
world_state: &mut dyn WorldState,
- this: &Obj,
+ location: Obj,
+ this: Var,
verb_name: Symbol,
args: List,
) -> ExecutionResult {
let call = VerbCall {
verb_name,
- location: this.clone(),
+ location: v_obj(location.clone()),
this: this.clone(),
player: self.top().player.clone(),
args: args.iter().collect(),
@@ -87,14 +148,14 @@ impl VMExecState {
};
let self_valid = world_state
- .valid(this)
+ .valid(&location)
.expect("Error checking object validity");
if !self_valid {
return self.push_error(E_INVIND);
}
// Find the callable verb ...
let (binary, resolved_verb) =
- match world_state.find_method_verb_on(&self.top().permissions, this, verb_name) {
+ match world_state.find_method_verb_on(&self.top().permissions, &location, verb_name) {
Ok(vi) => vi,
Err(WorldStateError::ObjectPermissionDenied) => {
return self.push_error(E_PERM);
@@ -162,7 +223,7 @@ impl VMExecState {
let caller = self.caller();
let call = VerbCall {
verb_name: verb,
- location: parent,
+ location: v_obj(parent),
this: self.top().this.clone(),
player: self.top().player.clone(),
args: args.iter().collect(),
diff --git a/crates/kernel/src/vm/vm_test.rs b/crates/kernel/src/vm/vm_test.rs
index fab674ed..1ecf7c78 100644
--- a/crates/kernel/src/vm/vm_test.rs
+++ b/crates/kernel/src/vm/vm_test.rs
@@ -23,7 +23,8 @@ mod tests {
use moor_values::util::BitEnum;
use moor_values::Error::E_DIV;
use moor_values::{
- v_bool, v_empty_list, v_err, v_int, v_list, v_map, v_none, v_obj, v_objid, v_str, Var,
+ v_bool, v_empty_list, v_err, v_flyweight, v_int, v_list, v_map, v_none, v_obj, v_objid,
+ v_str, List, Obj, Var,
};
use moor_values::NOTHING;
@@ -1167,4 +1168,57 @@ mod tests {
Ok(v_list(&[v_int(1), v_int(6), v_int(7), v_int(8), v_int(9)]))
);
}
+
+ #[test]
+ fn test_make_flyweight() {
+ let program = r#"return <#1, [slot -> "123"], {1, 2, 3}>;"#;
+ let mut state = world_with_test_program(program);
+ let session = Arc::new(NoopClientSession::new());
+ let result = call_verb(
+ state.as_mut(),
+ session,
+ Arc::new(BuiltinRegistry::new()),
+ "test",
+ vec![],
+ );
+ assert_eq!(
+ result.unwrap(),
+ v_flyweight(
+ Obj::mk_id(1),
+ &[(Symbol::mk("slot"), v_str("123"))],
+ List::mk_list(&[v_int(1), v_int(2), v_int(3)]),
+ None
+ )
+ );
+ }
+
+ #[test]
+ fn test_flyweight_slot() {
+ let program = r#"return <#1, [slot -> "123"], {1, 2, 3}>.slot;"#;
+ let mut state = world_with_test_program(program);
+ let session = Arc::new(NoopClientSession::new());
+ let result = call_verb(
+ state.as_mut(),
+ session,
+ Arc::new(BuiltinRegistry::new()),
+ "test",
+ vec![],
+ );
+ assert_eq!(result.unwrap(), v_str("123"));
+ }
+
+ #[test]
+ fn test_flyweight_sequence() {
+ let program = r#"return <#1, [slot -> "123"], {1, 2, 3}>[2];"#;
+ let mut state = world_with_test_program(program);
+ let session = Arc::new(NoopClientSession::new());
+ let result = call_verb(
+ state.as_mut(),
+ session,
+ Arc::new(BuiltinRegistry::new()),
+ "test",
+ vec![],
+ );
+ assert_eq!(result.unwrap(), v_int(2));
+ }
}
diff --git a/crates/kernel/src/vm/vm_unwind.rs b/crates/kernel/src/vm/vm_unwind.rs
index 8bfb721e..17b6f30f 100644
--- a/crates/kernel/src/vm/vm_unwind.rs
+++ b/crates/kernel/src/vm/vm_unwind.rs
@@ -13,7 +13,7 @@
//
use bincode::{Decode, Encode};
-use moor_compiler::{Label, Offset, BUILTINS};
+use moor_compiler::{to_literal, Label, Offset, BUILTINS};
use moor_values::model::Named;
use moor_values::model::VerbFlag;
use moor_values::tasks::Exception;
@@ -52,7 +52,7 @@ impl VMExecState {
let traceback_entry = match &a.frame {
Frame::Moo(_) => {
vec![
- v_obj(a.this.clone()),
+ a.this.clone(),
v_str(a.verbdef.names().join(" ").as_str()),
v_obj(a.verb_definer()),
v_obj(a.verb_owner()),
@@ -63,7 +63,7 @@ impl VMExecState {
Frame::Bf(bf_frame) => {
let bf_name = BUILTINS.name_of(bf_frame.bf_id).unwrap();
vec![
- v_obj(a.this.clone()),
+ a.this.clone(),
v_str(bf_name.as_str()),
v_obj(NOTHING),
v_obj(NOTHING),
@@ -98,8 +98,8 @@ impl VMExecState {
pieces.push(format!("builtin {bf_name}",));
}
}
- if a.verb_definer() != a.this {
- pieces.push(format!(" (this == {})", a.this));
+ if v_obj(a.verb_definer()) != a.this {
+ pieces.push(format!(" (this == {})", to_literal(&a.this)));
}
if let Some(line_num) = a.frame.find_line_no() {
pieces.push(format!(" (line {})", line_num));
diff --git a/crates/testing/load-tools/src/setup.rs b/crates/testing/load-tools/src/setup.rs
index 8cbb0f01..fcbc9514 100644
--- a/crates/testing/load-tools/src/setup.rs
+++ b/crates/testing/load-tools/src/setup.rs
@@ -310,7 +310,7 @@ pub async fn initialization_session(
connection_oid.clone(),
auth_token.clone(),
client_token.clone(),
- verb_name.clone(),
+ *verb_name,
verb_code.split('\n').map(|s| s.to_string()).collect(),
)
.await;
diff --git a/crates/testing/load-tools/src/tx-list-append.rs b/crates/testing/load-tools/src/tx-list-append.rs
index 4e2336c8..d1c4873c 100644
--- a/crates/testing/load-tools/src/tx-list-append.rs
+++ b/crates/testing/load-tools/src/tx-list-append.rs
@@ -380,7 +380,7 @@ async fn list_append_workload(
let kill_switch = kill_switch.clone();
let zmq_ctx = zmq_ctx.clone();
let rpc_address = args.client_args.rpc_address.clone();
- let client_id = client_id.clone();
+ let client_id = client_id;
let client_token = client_token.clone();
let connection_oid = connection_oid.clone();
tokio::spawn(async move {
@@ -500,7 +500,7 @@ async fn list_append_workload(
append_ops.push(Value::Vector(vec![
Value::Keyword(Keyword::from_name("append")),
Value::Integer(*property as i64),
- Value::Integer(*value as i64),
+ Value::Integer(*value),
]));
}
}
@@ -531,7 +531,7 @@ async fn list_append_workload(
read_ops.push(Value::Vector(vec![
Value::Keyword(Keyword::from_name("r")),
Value::Integer(*property as i64),
- Value::Vector(values.iter().map(|v| Value::Integer(*v as i64)).collect()),
+ Value::Vector(values.iter().map(|v| Value::Integer(*v)).collect()),
]));
}
map.insert(
@@ -559,7 +559,7 @@ async fn list_append_workload(
append_ops.push(Value::Vector(vec![
Value::Keyword(Keyword::from_name("append")),
Value::Integer(*property as i64),
- Value::Integer(*value as i64),
+ Value::Integer(*value),
]));
}
}
@@ -586,7 +586,7 @@ async fn list_append_workload(
read_ops.push(Value::Vector(vec![
Value::Keyword(Keyword::from_name("r")),
Value::Integer(*property as i64),
- Value::Vector(values.iter().map(|v| Value::Integer(*v as i64)).collect()),
+ Value::Vector(values.iter().map(|v| Value::Integer(*v)).collect()),
]));
}
map.insert(
diff --git a/crates/testing/load-tools/src/verb-dispatch-load-test.rs b/crates/testing/load-tools/src/verb-dispatch-load-test.rs
index 5493f304..93e28dfe 100644
--- a/crates/testing/load-tools/src/verb-dispatch-load-test.rs
+++ b/crates/testing/load-tools/src/verb-dispatch-load-test.rs
@@ -200,7 +200,7 @@ async fn load_test_workload(
let kill_switch = kill_switch.clone();
let zmq_ctx = zmq_ctx.clone();
let rpc_address = args.client_args.rpc_address.clone();
- let client_id = client_id.clone();
+ let client_id = client_id;
let client_token = client_token.clone();
let connection_oid = connection_oid.clone();
tokio::spawn(async move {
diff --git a/crates/web-host/src/host/mod.rs b/crates/web-host/src/host/mod.rs
index 0597f52e..764c18bc 100644
--- a/crates/web-host/src/host/mod.rs
+++ b/crates/web-host/src/host/mod.rs
@@ -86,6 +86,19 @@ pub fn var_as_json(v: &Var) -> serde_json::Value {
}
json!({ "map_pairs": v })
}
+ Variant::Flyweight(f) => {
+ if f.is_sealed() {
+ json!("sealed_flyweight")
+ } else {
+ let mut slotmap = serde_json::Map::new();
+ for s in f.slots() {
+ slotmap.insert(s.0.to_string(), var_as_json(&s.1));
+ }
+
+ let json_map = serde_json::Value::Object(slotmap);
+ json!({"flyweight": json_map})
+ }
+ }
}
}
diff --git a/crates/web-host/src/host/ws_connection.rs b/crates/web-host/src/host/ws_connection.rs
index c42abd91..12955e38 100644
--- a/crates/web-host/src/host/ws_connection.rs
+++ b/crates/web-host/src/host/ws_connection.rs
@@ -17,7 +17,7 @@ use axum::extract::ws::{Message, WebSocket};
use futures_util::stream::SplitSink;
use futures_util::{SinkExt, StreamExt};
use moor_values::tasks::{AbortLimitReason, CommandError, Event, SchedulerError, VerbProgramError};
-use moor_values::{Obj, Var};
+use moor_values::{v_obj, Obj, Var};
use rpc_async_client::pubsub_client::broadcast_recv;
use rpc_async_client::pubsub_client::events_recv;
use rpc_async_client::rpc_client::RpcSendClient;
@@ -50,7 +50,7 @@ pub struct WebSocketConnection {
/// The JSON output of a narrative event.
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NarrativeOutput {
- author: i32,
+ author: Value,
#[serde(skip_serializing_if = "Option::is_none")]
system_message: Option,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -83,7 +83,7 @@ impl WebSocketConnection {
Self::emit_narrative(
&mut ws_sender,
NarrativeOutput {
- author: self.player.id().0,
+ author: var_as_json(&v_obj(self.player.clone())),
system_message: Some(connect_message.to_string()),
message: None,
content_type: Some("text/plain".to_string()),
@@ -120,7 +120,7 @@ impl WebSocketConnection {
match event {
ClientEvent::SystemMessage(author, msg) => {
Self::emit_narrative(&mut ws_sender, NarrativeOutput {
- author: author.id().0,
+ author: var_as_json(&v_obj(author)),
system_message: Some(msg),
message: None,
content_type: Some("text/plain".to_string()),
@@ -132,7 +132,7 @@ impl WebSocketConnection {
let Event::Notify(msg, content_type) = msg;
let content_type = content_type.map(|s| s.to_string());
Self::emit_narrative(&mut ws_sender, NarrativeOutput {
- author: event.author.id().0,
+ author: var_as_json(event.author()),
system_message: None,
message: Some(var_as_json(&msg)),
content_type,
@@ -144,7 +144,7 @@ impl WebSocketConnection {
}
ClientEvent::Disconnect() => {
Self::emit_narrative(&mut ws_sender, NarrativeOutput {
- author: self.player.id().0,
+ author: var_as_json(&v_obj(self.player.clone())),
system_message: Some("** Disconnected **".to_string()),
message: None,
content_type: Some("text/plain".to_string()),