From 5c9819c2dcc5ad9d4534a861d5901414ee9f3948 Mon Sep 17 00:00:00 2001 From: Ryan Daum Date: Sat, 7 Dec 2024 17:54:09 -0500 Subject: [PATCH] Adds a new "flyweight" type - a lightweight object "Flyweights" are a bit of a fusion of anonymous objects and ColdMUD frobs. They are composed of a sequence of "slots" which behave like properties, a "delegate" to which verb and property (in case of slot miss) delegate to, and a "contents" which is a sequence of values. They are immutable and reference counted, and passed by value like MOO lists and maps. They will additionally (future) support a "sealing" function which makes their contents opaque. "this", and "caller" can be flyweights. In the future object/room contents will permit flyweights. And command matching will permit their return as well. --- Cargo.lock | 1 + crates/common/src/lib.rs | 7 +- crates/common/src/tasks/events.rs | 8 +- crates/common/src/var/flyweight.rs | 295 ++++++++++++++++++ crates/common/src/var/list.rs | 11 + crates/common/src/var/mod.rs | 7 +- crates/common/src/var/var.rs | 25 +- crates/common/src/var/variant.rs | 10 + crates/compiler/Cargo.toml | 1 + crates/compiler/src/ast.rs | 1 + crates/compiler/src/codegen.rs | 15 +- crates/compiler/src/decompile.rs | 38 ++- crates/compiler/src/moo.pest | 7 + crates/compiler/src/opcode.rs | 1 + crates/compiler/src/parse.rs | 113 ++++++- crates/compiler/src/unparse.rs | 79 ++++- crates/daemon/src/main.rs | 8 + crates/kernel/benches/vm_benches.rs | 8 +- crates/kernel/src/builtins/bf_objects.rs | 24 +- crates/kernel/src/builtins/bf_server.rs | 4 +- crates/kernel/src/builtins/bf_values.rs | 7 + crates/kernel/src/config.rs | 4 + crates/kernel/src/tasks/mod.rs | 18 +- crates/kernel/src/tasks/scheduler.rs | 6 +- crates/kernel/src/tasks/task.rs | 41 ++- .../kernel/src/tasks/task_scheduler_client.rs | 6 +- crates/kernel/src/tasks/vm_host.rs | 2 +- crates/kernel/src/textdump/read.rs | 29 +- crates/kernel/src/textdump/write.rs | 23 ++ crates/kernel/src/vm/activation.rs | 17 +- crates/kernel/src/vm/exec_state.rs | 12 +- crates/kernel/src/vm/moo_execute.rs | 202 ++++++------ crates/kernel/src/vm/vm_call.rs | 91 +++++- crates/kernel/src/vm/vm_test.rs | 56 +++- crates/kernel/src/vm/vm_unwind.rs | 10 +- crates/testing/load-tools/src/setup.rs | 2 +- .../testing/load-tools/src/tx-list-append.rs | 10 +- .../load-tools/src/verb-dispatch-load-test.rs | 2 +- crates/web-host/src/host/mod.rs | 13 + crates/web-host/src/host/ws_connection.rs | 12 +- 40 files changed, 1016 insertions(+), 210 deletions(-) create mode 100644 crates/common/src/var/flyweight.rs 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()),