From 032853e758d3ed29097170aa9f028aa6709738d0 Mon Sep 17 00:00:00 2001 From: Ryan Daum Date: Tue, 27 Aug 2024 18:04:10 -0400 Subject: [PATCH] Rewritten Var/values implementation based around flexbuffers This basically rewrites most of the values layer to be based around values and collections held in flexbuffers. (Flexbuffers are part of the flatbuffers project, and are a kind of zero copy dynamic flatbuffer.) Part of a bigger effort to do the following: * Rip out custom serialization formats and replace with a combination of flatbuffers and flexbuffers * Same for all uses of bincode2 * Clean up the Var interface more broadly Along with this change came major cleanups generally to the interface in the values layer: * Make all index-based operations take an argument stating whether they should operate on one-indexed (like MOO) or zero-indexed (like most languages). This cleans up the ad-hoc way the conversion was happening before, and makes it easier to use languages other than MOO, but using the same values layer. * Move printing of the literal representation of a value out into the `compiler` crate, making it clear that the literal printing is of a MOO value, and leaving room for other languages to have other representations, or to add different pretty printing / decompilation options. * Makes the type system interface more regular in general, providing traits to identify operations for scalar/associative/sequence classes of type. To be done still: * Rip out use of bincode2 and custom serialization code and replace with schema'd flatbuffer entities. * There are definitely lots of excessive copies still happening, and there's likely much more clever things that can be happening inside Var, including switching between a 'building' and 'reading' mode such that a given value doesn't obtain its flexbuffered pickled form until first written to the database. Or something. --- Cargo.lock | 82 +- Cargo.toml | 2 + crates/compiler/src/ast.rs | 4 +- crates/compiler/src/builtins.rs | 6 +- crates/compiler/src/codegen.rs | 4 +- crates/compiler/src/codegen_tests.rs | 6 +- crates/compiler/src/decompile.rs | 4 +- crates/compiler/src/lib.rs | 2 +- crates/compiler/src/names.rs | 2 +- crates/compiler/src/opcode.rs | 4 +- crates/compiler/src/parse.rs | 14 +- crates/compiler/src/program.rs | 5 +- crates/compiler/src/unparse.rs | 62 +- crates/console-host/Cargo.toml | 1 + crates/console-host/src/main.rs | 34 +- crates/daemon/src/connections.rs | 2 +- crates/daemon/src/connections_rb.rs | 4 +- crates/daemon/src/connections_wt.rs | 4 +- crates/daemon/src/rpc_server.rs | 8 +- crates/daemon/src/rpc_session.rs | 2 +- .../src/wtrel/rel_transaction.rs | 2 +- crates/db-wiredtiger/third-party/wiredtiger | 2 +- crates/db/src/db_loader_client.rs | 6 +- crates/db/src/db_worldstate.rs | 20 +- crates/db/src/loader.rs | 4 +- crates/db/src/relational_worldstate.rs | 4 +- crates/db/src/worldstate_tests.rs | 6 +- crates/db/src/worldstate_transaction.rs | 6 +- crates/kernel/benches/vm_benches.rs | 8 +- crates/kernel/src/builtins/bf_list_sets.rs | 129 +- crates/kernel/src/builtins/bf_maps.rs | 19 +- crates/kernel/src/builtins/bf_num.rs | 6 +- crates/kernel/src/builtins/bf_objects.rs | 39 +- crates/kernel/src/builtins/bf_properties.rs | 34 +- crates/kernel/src/builtins/bf_server.rs | 154 ++- crates/kernel/src/builtins/bf_strings.rs | 34 +- crates/kernel/src/builtins/bf_values.rs | 36 +- crates/kernel/src/builtins/bf_verbs.rs | 58 +- crates/kernel/src/builtins/mod.rs | 8 +- crates/kernel/src/matching/match_env.rs | 4 +- .../kernel/src/matching/mock_matching_env.rs | 2 +- crates/kernel/src/matching/ws_match_env.rs | 2 +- crates/kernel/src/tasks/command_parse.rs | 6 +- crates/kernel/src/tasks/mod.rs | 16 +- crates/kernel/src/tasks/scheduler.rs | 12 +- crates/kernel/src/tasks/scheduler_client.rs | 4 +- crates/kernel/src/tasks/sessions.rs | 2 +- crates/kernel/src/tasks/suspension.rs | 2 +- crates/kernel/src/tasks/task.rs | 18 +- .../kernel/src/tasks/task_scheduler_client.rs | 6 +- crates/kernel/src/tasks/vm_host.rs | 14 +- crates/kernel/src/textdump/load_db.rs | 4 +- crates/kernel/src/textdump/mod.rs | 4 +- crates/kernel/src/textdump/read.rs | 10 +- crates/kernel/src/textdump/write.rs | 14 +- crates/kernel/src/textdump/write_db.rs | 4 +- crates/kernel/src/vm/activation.rs | 19 +- crates/kernel/src/vm/exec_state.rs | 4 +- crates/kernel/src/vm/mod.rs | 2 +- crates/kernel/src/vm/moo_execute.rs | 263 ++-- crates/kernel/src/vm/moo_frame.rs | 4 +- crates/kernel/src/vm/vm_call.rs | 24 +- crates/kernel/src/vm/vm_test.rs | 34 +- crates/kernel/src/vm/vm_unwind.rs | 7 +- crates/kernel/tests/textdump.rs | 4 +- crates/kernel/testsuite/common/mod.rs | 4 +- crates/kernel/testsuite/moot/map.moot | 4 +- crates/kernel/testsuite/moot_suite.rs | 7 +- crates/moot/src/lib.rs | 2 +- crates/rpc-common/src/lib.rs | 4 +- crates/telnet-host/Cargo.toml | 1 + crates/telnet-host/src/telnet.rs | 26 +- crates/values/Cargo.toml | 2 + crates/values/src/lib.rs | 9 +- crates/values/src/model/defset.rs | 2 +- crates/values/src/model/mod.rs | 2 +- crates/values/src/model/objects.rs | 2 +- crates/values/src/model/objset.rs | 2 +- crates/values/src/model/permissions.rs | 2 +- crates/values/src/model/propdef.rs | 8 +- crates/values/src/model/props.rs | 4 +- crates/values/src/model/verbdef.rs | 6 +- crates/values/src/model/verbs.rs | 4 +- crates/values/src/model/world_state.rs | 6 +- crates/values/src/tasks/errors.rs | 2 +- crates/values/src/tasks/events.rs | 2 +- crates/values/src/var/list.rs | 668 ++++++++--- crates/values/src/var/list_impl_buffer.rs | 665 ----------- crates/values/src/var/list_impl_vector.rs | 412 ------- crates/values/src/var/map.rs | 765 ++++++++++-- crates/values/src/var/mod.rs | 1056 +++-------------- crates/values/src/var/scalar.rs | 221 ++++ crates/values/src/var/storage.rs | 201 ++++ crates/values/src/var/string.rs | 493 ++++++-- crates/values/src/var/var.rs | 607 ++++++++++ crates/values/src/var/variant.rs | 255 +++- crates/values/src/var/varops.rs | 255 ---- crates/web-host/src/host/mod.rs | 11 +- crates/web-host/src/host/web_host.rs | 2 +- crates/web-host/src/host/ws_connection.rs | 2 +- 100 files changed, 3685 insertions(+), 3341 deletions(-) delete mode 100644 crates/values/src/var/list_impl_buffer.rs delete mode 100644 crates/values/src/var/list_impl_vector.rs create mode 100644 crates/values/src/var/scalar.rs create mode 100644 crates/values/src/var/storage.rs create mode 100644 crates/values/src/var/var.rs delete mode 100644 crates/values/src/var/varops.rs diff --git a/Cargo.lock b/Cargo.lock index c8c8e330..6a1c06ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1100,6 +1100,29 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "flatbuffers" +version = "24.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" +dependencies = [ + "bitflags 1.3.2", + "rustc_version", +] + +[[package]] +name = "flexbuffers" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d14128f06405808ce75bfebe11e9b0f9da18719ede6d7bdb1702d6bfe0f7e8" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "num_enum", + "serde", + "serde_derive", +] + [[package]] name = "flume" version = "0.11.0" @@ -1774,6 +1797,7 @@ dependencies = [ "clap_derive", "color-eyre", "eyre", + "moor-compiler", "moor-values", "owo-colors", "rpc-common", @@ -1934,6 +1958,7 @@ dependencies = [ "escargot", "eyre", "futures-util", + "moor-compiler", "moor-moot", "moor-values", "rpc-async-client", @@ -1959,6 +1984,8 @@ dependencies = [ "daumtils", "decorum", "enum-primitive-derive", + "flatbuffers", + "flexbuffers", "im", "itertools 0.13.0", "lazy_static", @@ -2059,6 +2086,27 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "object" version = "0.32.2" @@ -2382,6 +2430,16 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -3366,7 +3424,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.17", ] [[package]] @@ -3378,6 +3436,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.17" @@ -3388,7 +3457,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.16", ] [[package]] @@ -3946,6 +4015,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.6.16" diff --git a/Cargo.toml b/Cargo.toml index 288acdc4..e48d7fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,8 @@ daumtils = { git = "https://github.com/rdaum/daumtils.git", version = "0.2.0" } decorum = "0.3" # For ordering & comparing our floats encoding_rs = "0.8.34" enum-primitive-derive = "0.3" +flatbuffers = "24.3.25" +flexbuffers = "2.0.0" im = "15.1" inventory = "0.3.15" itertools = "0.13.0" diff --git a/crates/compiler/src/ast.rs b/crates/compiler/src/ast.rs index c0382aa1..1c143339 100644 --- a/crates/compiler/src/ast.rs +++ b/crates/compiler/src/ast.rs @@ -12,10 +12,10 @@ // this program. If not, see . // -use moor_values::var::Symbol; +use moor_values::Symbol; use std::fmt::Display; -use moor_values::var::Var; +use moor_values::Var; /// The abstract syntax tree produced by the parser and converted by codegen into opcodes. use crate::names::UnboundName; diff --git a/crates/compiler/src/builtins.rs b/crates/compiler/src/builtins.rs index 289866c7..4d71c6d5 100644 --- a/crates/compiler/src/builtins.rs +++ b/crates/compiler/src/builtins.rs @@ -14,9 +14,9 @@ use bincode::{Decode, Encode}; use lazy_static::lazy_static; -use moor_values::var::Symbol; -use moor_values::var::VarType; -use moor_values::var::VarType::TYPE_MAP; +use moor_values::Symbol; +use moor_values::VarType; +use moor_values::VarType::TYPE_MAP; /// Global registry of built-in function names. use std::collections::HashMap; use ArgCount::{Q, U}; diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs index 6d3c3776..7f22129a 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::Var; -use moor_values::var::Variant; +use moor_values::Var; +use moor_values::Variant; use crate::ast::{ Arg, BinaryOp, CatchCodes, Expr, ScatterItem, ScatterKind, Stmt, StmtNode, UnaryOp, diff --git a/crates/compiler/src/codegen_tests.rs b/crates/compiler/src/codegen_tests.rs index 413dfa88..ceda89aa 100644 --- a/crates/compiler/src/codegen_tests.rs +++ b/crates/compiler/src/codegen_tests.rs @@ -21,9 +21,9 @@ mod tests { use crate::opcode::{ScatterArgs, ScatterLabel}; use crate::CompileOptions; use moor_values::model::CompileError; - use moor_values::var::Error::{E_INVARG, E_INVIND, E_PERM, E_PROPNF, E_RANGE}; - use moor_values::var::Objid; - use moor_values::var::Symbol; + use moor_values::Error::{E_INVARG, E_INVIND, E_PERM, E_PROPNF, E_RANGE}; + use moor_values::Objid; + use moor_values::Symbol; use moor_values::SYSTEM_OBJECT; #[test] diff --git a/crates/compiler/src/decompile.rs b/crates/compiler/src/decompile.rs index b6c9e293..e7c712b8 100644 --- a/crates/compiler/src/decompile.rs +++ b/crates/compiler/src/decompile.rs @@ -12,8 +12,8 @@ // this program. If not, see . // -use moor_values::var::{v_err, v_int, v_none, v_objid, Var}; -use moor_values::var::{v_float, Variant}; +use moor_values::{v_err, v_int, v_none, v_objid, Var}; +use moor_values::{v_float, Variant}; use std::collections::{HashMap, VecDeque}; use crate::ast::{ diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index c0aa8f3c..deea43cd 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -37,7 +37,7 @@ pub use crate::names::{Name, UnboundNames}; pub use crate::opcode::{Op, ScatterLabel}; pub use crate::parse::CompileOptions; pub use crate::program::{Program, EMPTY_PROGRAM}; -pub use crate::unparse::unparse; +pub use crate::unparse::{to_literal, unparse}; #[macro_use] extern crate pest_derive; diff --git a/crates/compiler/src/names.rs b/crates/compiler/src/names.rs index 4fd498b3..7bdd0d76 100644 --- a/crates/compiler/src/names.rs +++ b/crates/compiler/src/names.rs @@ -15,7 +15,7 @@ use crate::GlobalName; use bincode::{Decode, Encode}; use moor_values::model::CompileError; -use moor_values::var::Symbol; +use moor_values::Symbol; use std::collections::HashMap; use strum::IntoEnumIterator; diff --git a/crates/compiler/src/opcode.rs b/crates/compiler/src/opcode.rs index 8aa98e2f..68c67f31 100644 --- a/crates/compiler/src/opcode.rs +++ b/crates/compiler/src/opcode.rs @@ -16,8 +16,8 @@ use crate::builtins::BuiltinId; use crate::labels::{Label, Offset}; use crate::names::Name; use bincode::{Decode, Encode}; -use moor_values::var::Error; -use moor_values::var::Objid; +use moor_values::Error; +use moor_values::Objid; #[derive(Clone, Debug, PartialEq, PartialOrd, Encode, Decode)] pub enum Op { diff --git a/crates/compiler/src/parse.rs b/crates/compiler/src/parse.rs index b004b83a..23b76c87 100644 --- a/crates/compiler/src/parse.rs +++ b/crates/compiler/src/parse.rs @@ -19,18 +19,18 @@ use std::collections::HashMap; use std::rc::Rc; use std::str::FromStr; -use moor_values::var::{v_none, Symbol}; use moor_values::SYSTEM_OBJECT; +use moor_values::{v_none, Symbol}; use pest::pratt_parser::{Assoc, Op, PrattParser}; pub use pest::Parser as PestParser; use tracing::{instrument, warn}; -use moor_values::var::Error::{ +use moor_values::Error::{ E_ARGS, E_DIV, E_FLOAT, E_INVARG, E_INVIND, E_MAXREC, E_NACC, E_NONE, E_PERM, E_PROPNF, E_QUOTA, E_RANGE, E_RECMOVE, E_TYPE, E_VARNF, E_VERBNF, }; -use moor_values::var::Objid; -use moor_values::var::{v_err, v_float, v_int, v_objid, v_str, v_string}; +use moor_values::Objid; +use moor_values::{v_err, v_float, v_int, v_objid, v_str, v_string}; use crate::ast::Arg::{Normal, Splice}; use crate::ast::StmtNode::Scope; @@ -1189,9 +1189,9 @@ pub fn unquote_str(s: &str) -> Result { #[cfg(test)] mod tests { - use moor_values::var::Error::{E_INVARG, E_PROPNF, E_VARNF}; - use moor_values::var::{v_err, v_float, v_int, v_obj, v_str}; - use moor_values::var::{v_none, Symbol}; + use moor_values::Error::{E_INVARG, E_PROPNF, E_VARNF}; + use moor_values::{v_err, v_float, v_int, v_obj, v_str}; + use moor_values::{v_none, Symbol}; use crate::ast::Arg::{Normal, Splice}; use crate::ast::Expr::{Call, Id, Prop, Value, Verb}; diff --git a/crates/compiler/src/program.rs b/crates/compiler/src/program.rs index 34247093..c23e0ba9 100644 --- a/crates/compiler/src/program.rs +++ b/crates/compiler/src/program.rs @@ -15,10 +15,11 @@ use crate::labels::{JumpLabel, Label}; use crate::names::{Name, Names}; use crate::opcode::Op; +use crate::unparse::to_literal; use bincode::{Decode, Encode}; use bytes::Bytes; use lazy_static::lazy_static; -use moor_values::var::Var; +use moor_values::Var; use moor_values::{AsByteBuffer, CountingWriter, DecodingError, EncodingError, BINCODE_CONFIG}; use std::fmt::{Display, Formatter}; use std::sync::Arc; @@ -84,7 +85,7 @@ impl Display for Program { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // Write literals indexed by their offset # for (i, l) in self.literals.iter().enumerate() { - writeln!(f, "L{}: {}", i, l.to_literal())?; + writeln!(f, "L{}: {}", i, to_literal(l))?; } // Write jump labels indexed by their offset & showing position & optional name diff --git a/crates/compiler/src/unparse.rs b/crates/compiler/src/unparse.rs index 74e04505..ae789e50 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::{Var, Variant}; use crate::ast::{Expr, Stmt, StmtNode}; use crate::decompile::DecompileError; @@ -109,13 +109,13 @@ impl<'a> Unparse<'a> { } } - fn unparse_var(&self, var: &moor_values::var::Var, aggressive: bool) -> String { + fn unparse_var(&self, var: &moor_values::Var, aggressive: bool) -> String { if !aggressive { - return format!("{var}"); + return to_literal(var); } if let Variant::Str(s) = var.variant() { - let s = s.as_str(); + let s = s.as_string(); // If the string contains anything that isn't alphanumeric and _, it's // not a valid ident and needs to be quoted. Likewise if it begins with a non-alpha/underscore @@ -123,12 +123,12 @@ impl<'a> Unparse<'a> { || (s.chars().next().unwrap().is_numeric() && !s.starts_with('_')); if !needs_quotes { - s.into() + s } else { - format!("({})", quote_str(s)) + format!("({})", quote_str(&s)) } } else { - format!("{var}") + to_literal(var) } } @@ -190,7 +190,7 @@ impl<'a> Unparse<'a> { Expr::Unary(op, expr) => Ok(format!("{}{}", op, brace_if_lower(expr))), Expr::Prop { location, property } => { let location = match (&**location, &**property) { - (Expr::Value(var), Expr::Value(_)) if var.is_root() => String::from("$"), + (Expr::Value(var), Expr::Value(_)) if var.is_sysobj() => String::from("$"), _ => format!("{}.", brace_if_lower(location)), }; let prop = match &**property { @@ -205,7 +205,7 @@ impl<'a> Unparse<'a> { args, } => { let location = match (&**location, &**verb) { - (Expr::Value(var), Expr::Value(_)) if var.is_root() => String::from("$"), + (Expr::Value(var), Expr::Value(_)) if var.is_sysobj() => String::from("$"), _ => format!("{}:", brace_if_lower(location)), }; let verb = match &**verb { @@ -727,6 +727,50 @@ pub fn annotate_line_numbers(start_line_no: usize, tree: &mut [Stmt]) -> usize { line_no } +/// Utility function to produce a MOO literal from a Var/Variant. +/// This is kept in `compiler` and not in `values` because it's specific to the MOO language, and +/// other languages could have different representations. +pub fn to_literal(v: &Var) -> String { + match v.variant() { + Variant::None => "None".to_string(), + Variant::Obj(oid) => { + format!("{}", oid) + } + Variant::Int(i) => i.to_string(), + Variant::Float(f) => { + format!("{f:?}") + } + Variant::List(l) => { + let mut result = String::new(); + result.push('{'); + for (i, v) in l.iter().enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(to_literal(&v).as_str()); + } + result.push('}'); + result + } + Variant::Str(s) => quote_str(&s.as_string()), + Variant::Map(m) => { + let mut result = String::new(); + result.push('['); + for (i, (k, v)) in m.iter().enumerate() { + if i > 0 { + result.push_str(", "); + } + result.push_str(to_literal(&k).as_str()); + result.push_str(" -> "); + result.push_str(to_literal(&v).as_str()); + } + result.push(']'); + result + } + Variant::Err(e) => e.name().to_string(), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/console-host/Cargo.toml b/crates/console-host/Cargo.toml index ecaa5482..005a57a2 100644 --- a/crates/console-host/Cargo.toml +++ b/crates/console-host/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true description = "A tool to connect to a local or remote moor daemon and interact with it via the TTY." [dependencies] +moor-compiler = { path = "../compiler" } moor-values = { path = "../values" } rpc-common = { path = "../rpc-common" } rpc-sync-client = { path = "../rpc-sync-client" } diff --git a/crates/console-host/src/main.rs b/crates/console-host/src/main.rs index e2a7f9ad..7bcfefe2 100644 --- a/crates/console-host/src/main.rs +++ b/crates/console-host/src/main.rs @@ -20,19 +20,19 @@ use std::time::SystemTime; use clap::Parser; use clap_derive::Parser; use color_eyre::owo_colors::OwoColorize; -use moor_values::var::Objid; -use rustyline::config::Configurer; -use rustyline::error::ReadlineError; -use rustyline::{ColorMode, DefaultEditor, ExternalPrinter}; -use tracing::{debug, error, info, trace, warn}; -use uuid::Uuid; - +use moor_compiler::to_literal; +use moor_values::Objid; use rpc_common::{ AuthToken, BroadcastEvent, ClientToken, ConnectionEvent, RpcRequest, RpcResponse, RpcResult, BROADCAST_TOPIC, }; use rpc_sync_client::RpcSendClient; use rpc_sync_client::{broadcast_recv, events_recv}; +use rustyline::config::Configurer; +use rustyline::error::ReadlineError; +use rustyline::{ColorMode, DefaultEditor, ExternalPrinter}; +use tracing::{debug, error, info, trace, warn}; +use uuid::Uuid; #[derive(Parser, Debug)] struct Args { @@ -249,14 +249,18 @@ fn console_loop( } match events_recv(client_id, &narr_sub_socket) { Ok(ConnectionEvent::Narrative(_, msg)) => { - printer - .print( - (match msg.event() { - moor_values::tasks::Event::Notify(s, _content_type) => s, - }) - .to_string(), - ) - .unwrap(); + let var = match msg.event() { + moor_values::tasks::Event::Notify(s, _content_type) => s, + }; + match var.variant() { + moor_values::Variant::Str(s) => { + printer.print(s.as_string().to_string()).unwrap(); + } + _ => { + let literal = to_literal(&var); + printer.print(format!("{}", literal.yellow())).unwrap(); + } + } } Ok(ConnectionEvent::SystemMessage(o, msg)) => { printer diff --git a/crates/daemon/src/connections.rs b/crates/daemon/src/connections.rs index d6323b30..07cc23f1 100644 --- a/crates/daemon/src/connections.rs +++ b/crates/daemon/src/connections.rs @@ -17,7 +17,7 @@ use std::time::{Duration, SystemTime}; use uuid::Uuid; use moor_kernel::tasks::sessions::SessionError; -use moor_values::var::Objid; +use moor_values::Objid; use rpc_common::RpcRequestError; pub const CONNECTION_TIMEOUT_DURATION: Duration = Duration::from_secs(30); diff --git a/crates/daemon/src/connections_rb.rs b/crates/daemon/src/connections_rb.rs index a9bd0596..bcf1367c 100644 --- a/crates/daemon/src/connections_rb.rs +++ b/crates/daemon/src/connections_rb.rs @@ -27,8 +27,8 @@ use uuid::Uuid; use bytes::Bytes; use daumtils::SliceRef; use moor_kernel::tasks::sessions::SessionError; -use moor_values::var::Objid; use moor_values::AsByteBuffer; +use moor_values::Objid; use relbox::{relation_info_for, RelBox, RelationId, RelationInfo, Transaction}; use rpc_common::RpcRequestError; @@ -444,7 +444,7 @@ impl ConnectionsDB for ConnectionsRb { mod tests { use std::sync::Arc; - use moor_values::var::Objid; + use moor_values::Objid; use crate::connections::ConnectionsDB; use crate::connections_rb::ConnectionsRb; diff --git a/crates/daemon/src/connections_wt.rs b/crates/daemon/src/connections_wt.rs index b05e056c..f697191e 100644 --- a/crates/daemon/src/connections_wt.rs +++ b/crates/daemon/src/connections_wt.rs @@ -31,7 +31,7 @@ use bytes::Bytes; use moor_db_wiredtiger::{WiredTigerRelDb, WiredTigerRelTransaction, WiredTigerRelation}; use moor_kernel::tasks::sessions::SessionError; use moor_values::model::{CommitResult, ValSet}; -use moor_values::var::Objid; +use moor_values::Objid; use moor_values::{AsByteBuffer, DecodingError, EncodingError}; use rpc_common::RpcRequestError; @@ -484,7 +484,7 @@ impl ConnectionsDB for ConnectionsWT { mod tests { use std::sync::Arc; - use moor_values::var::Objid; + use moor_values::Objid; use crate::connections::ConnectionsDB; use crate::connections_wt::ConnectionsWT; diff --git a/crates/daemon/src/rpc_server.rs b/crates/daemon/src/rpc_server.rs index e335b1d8..1d3382f3 100644 --- a/crates/daemon/src/rpc_server.rs +++ b/crates/daemon/src/rpc_server.rs @@ -31,11 +31,11 @@ use moor_kernel::SchedulerClient; use moor_values::tasks::SchedulerError::CommandExecutionError; use moor_values::tasks::{CommandError, NarrativeEvent, TaskId}; use moor_values::util::parse_into_words; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_objid, v_string}; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Variant; use moor_values::SYSTEM_OBJECT; +use moor_values::{v_objid, v_string}; use rpc_common::RpcResponse::{LoginResult, NewConnection}; use rpc_common::{ AuthToken, BroadcastEvent, ClientToken, ConnectType, ConnectionEvent, RpcRequest, diff --git a/crates/daemon/src/rpc_session.rs b/crates/daemon/src/rpc_session.rs index 2029922f..4acd5eab 100644 --- a/crates/daemon/src/rpc_session.rs +++ b/crates/daemon/src/rpc_session.rs @@ -20,7 +20,7 @@ use uuid::Uuid; use moor_kernel::tasks::sessions::{Session, SessionError}; use moor_values::tasks::NarrativeEvent; -use moor_values::var::Objid; +use moor_values::Objid; use crate::rpc_server::RpcServer; diff --git a/crates/db-wiredtiger/src/wtrel/rel_transaction.rs b/crates/db-wiredtiger/src/wtrel/rel_transaction.rs index e08ce568..e2f94fc3 100644 --- a/crates/db-wiredtiger/src/wtrel/rel_transaction.rs +++ b/crates/db-wiredtiger/src/wtrel/rel_transaction.rs @@ -582,7 +582,7 @@ mod tests { use moor_db::RelationalTransaction; use moor_values::model::{ObjSet, ValSet}; - use moor_values::var::Objid; + use moor_values::Objid; use TestRelation::{CompositeToOne, OneToOne, OneToOneSecondaryIndexed, Sequences}; use crate::wtrel::rel_db::WiredTigerRelDb; diff --git a/crates/db-wiredtiger/third-party/wiredtiger b/crates/db-wiredtiger/third-party/wiredtiger index 4b37cbe4..f8a6f387 160000 --- a/crates/db-wiredtiger/third-party/wiredtiger +++ b/crates/db-wiredtiger/third-party/wiredtiger @@ -1 +1 @@ -Subproject commit 4b37cbe4e3992389e0bfb903ba8ca88791f6e4ca +Subproject commit f8a6f387e6753237f69463af8d2b7e66bf5aecc9 diff --git a/crates/db/src/db_loader_client.rs b/crates/db/src/db_loader_client.rs index bc480a81..fcec48cd 100644 --- a/crates/db/src/db_loader_client.rs +++ b/crates/db/src/db_loader_client.rs @@ -25,9 +25,9 @@ use moor_values::model::{CommitResult, WorldStateError}; use moor_values::model::{HasUuid, PropPerms, ValSet}; use moor_values::model::{PropDef, PropDefs}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Var; use crate::db_worldstate::DbTxWorldState; use crate::loader::LoaderInterface; diff --git a/crates/db/src/db_worldstate.rs b/crates/db/src/db_worldstate.rs index ca523921..256400d9 100644 --- a/crates/db/src/db_worldstate.rs +++ b/crates/db/src/db_worldstate.rs @@ -29,11 +29,11 @@ use moor_values::model::{PropAttrs, PropFlag}; use moor_values::model::{PropDef, PropDefs}; use moor_values::model::{VerbDef, VerbDefs}; use moor_values::util::BitEnum; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_int, v_objid, Var}; -use moor_values::var::{v_listv, Objid}; +use moor_values::Objid; +use moor_values::Variant; use moor_values::NOTHING; +use moor_values::{v_int, v_objid, Var}; +use moor_values::{v_list, Symbol}; use crate::worldstate_transaction::WorldStateTransaction; @@ -234,7 +234,7 @@ impl WorldState for DbTxWorldState { return self.location_of(perms, obj).map(Var::from); } else if pname == *CONTENTS_SYM { let contents: Vec<_> = self.contents_of(perms, obj)?.iter().map(v_objid).collect(); - return Ok(v_listv(contents)); + return Ok(v_list(&contents)); } else if pname == *OWNER_SYM { return self.owner_of(obj).map(Var::from); } else if pname == *PROGRAMMER_SYM { @@ -355,7 +355,7 @@ impl WorldState for DbTxWorldState { let Variant::Str(name) = value.variant() else { return Err(WorldStateError::PropertyTypeMismatch); }; - self.tx.set_object_name(obj, name.to_string())?; + self.tx.set_object_name(obj, name.as_string())?; return Ok(()); } @@ -750,7 +750,13 @@ impl WorldState for DbTxWorldState { // Then grab aliases property. let aliases = match self.retrieve_property(perms, obj, *ALIASES_SYM) { Ok(a) => match a.variant() { - Variant::List(a) => a.iter().map(|v| v.to_string()).collect(), + Variant::List(a) => a + .iter() + .map(|v| match v.variant() { + Variant::Str(s) => s.as_string(), + _ => "".to_string(), + }) + .collect(), _ => { vec![] } diff --git a/crates/db/src/loader.rs b/crates/db/src/loader.rs index 3cdee37a..d5d459da 100644 --- a/crates/db/src/loader.rs +++ b/crates/db/src/loader.rs @@ -24,8 +24,8 @@ use moor_values::model::{CommitResult, WorldStateError}; use moor_values::model::{ObjAttrs, PropPerms}; use moor_values::model::{PropDef, PropDefs}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Var; /// Interface exposed to be used by the textdump loader. Overlap of functionality with what /// WorldState could provide, but potentially different constraints/semantics (e.g. no perms checks) diff --git a/crates/db/src/relational_worldstate.rs b/crates/db/src/relational_worldstate.rs index 234a9c83..9d830dcb 100644 --- a/crates/db/src/relational_worldstate.rs +++ b/crates/db/src/relational_worldstate.rs @@ -24,9 +24,9 @@ use moor_values::model::{ WorldStateError, }; use moor_values::util::BitEnum; -use moor_values::var::Symbol; -use moor_values::var::{v_none, Objid, Var}; +use moor_values::Symbol; use moor_values::NOTHING; +use moor_values::{v_none, Objid, Var}; use std::collections::{HashMap, HashSet, VecDeque}; use uuid::Uuid; diff --git a/crates/db/src/worldstate_tests.rs b/crates/db/src/worldstate_tests.rs index a23947e5..6536f28e 100644 --- a/crates/db/src/worldstate_tests.rs +++ b/crates/db/src/worldstate_tests.rs @@ -23,10 +23,10 @@ use moor_values::model::{CommitResult, WorldStateError}; use moor_values::model::{HasUuid, Named}; use moor_values::model::{ObjAttrs, PropFlag, ValSet}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::{v_int, v_str}; +use moor_values::Objid; +use moor_values::Symbol; use moor_values::NOTHING; +use moor_values::{v_int, v_str}; pub fn perform_test_create_object(begin_tx: F) where diff --git a/crates/db/src/worldstate_transaction.rs b/crates/db/src/worldstate_transaction.rs index ebb5f910..15120711 100644 --- a/crates/db/src/worldstate_transaction.rs +++ b/crates/db/src/worldstate_transaction.rs @@ -24,9 +24,9 @@ use moor_values::model::{ObjSet, PropPerms}; use moor_values::model::{PropDef, PropDefs}; use moor_values::model::{VerbDef, VerbDefs}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Var; /// A trait defining a generic interface to a database for storing the per-attribute values /// of our objects and their properties and verbs. Used by DbTxWorldState. diff --git a/crates/kernel/benches/vm_benches.rs b/crates/kernel/benches/vm_benches.rs index 0d223a7b..a91a306d 100644 --- a/crates/kernel/benches/vm_benches.rs +++ b/crates/kernel/benches/vm_benches.rs @@ -35,8 +35,8 @@ 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::var::{List, Symbol}; -use moor_values::{AsByteBuffer, NOTHING, SYSTEM_OBJECT}; +use moor_values::{AsByteBuffer, Var, NOTHING, SYSTEM_OBJECT}; +use moor_values::Symbol; fn create_worldstate() -> WiredTigerDB { let (ws_source, _) = WiredTigerDB::open(None); @@ -51,7 +51,7 @@ fn create_worldstate() -> WiredTigerDB { pub fn prepare_call_verb( world_state: &mut dyn WorldState, verb_name: &str, - args: List, + args: Vec, max_ticks: usize, ) -> VmHost { let mut vm_host = VmHost::new(0, 20, max_ticks, Duration::from_secs(15)); @@ -95,7 +95,7 @@ fn prepare_vm_execution( BinaryType::LambdaMoo18X, ) .unwrap(); - let vm_host = prepare_call_verb(tx.as_mut(), "test", List::new(), max_ticks); + let vm_host = prepare_call_verb(tx.as_mut(), "test", vec![], max_ticks); assert_eq!(tx.commit().unwrap(), CommitResult::Success); vm_host } diff --git a/crates/kernel/src/builtins/bf_list_sets.rs b/crates/kernel/src/builtins/bf_list_sets.rs index 00bf9333..33a0901b 100644 --- a/crates/kernel/src/builtins/bf_list_sets.rs +++ b/crates/kernel/src/builtins/bf_list_sets.rs @@ -15,16 +15,16 @@ use std::ops::BitOr; use moor_compiler::offset_for_builtin; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_TYPE}; -use moor_values::var::Variant; -use moor_values::var::{v_empty_list, v_int, v_list, v_string}; -use moor_values::var::{v_listv, Error}; +use moor_values::Error::{E_ARGS, E_INVARG, E_TYPE}; +use moor_values::{ + v_empty_list, v_int, v_list, v_list_iter, v_string, IndexMode, Sequence, VarType, +}; +use moor_values::{Error, Variant}; use onig::{Region, SearchOptions, SyntaxOperator}; use crate::bf_declare; use crate::builtins::BfRet::Ret; use crate::builtins::{BfCallState, BfErr, BfRet, BuiltinFunction}; -use crate::vm::moo_execute::one_to_zero_index; fn bf_is_member(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 2 { @@ -35,7 +35,7 @@ fn bf_is_member(bf_args: &mut BfCallState<'_>) -> Result { // is not *really* a correct place for it, but `bf_list_sets_and_maps_too_i_guess.rs` is a bit silly. match container.variant() { Variant::List(list) => { - if list.contains_case_sensitive(value) { + if list.index_in(value, true).map_err(BfErr::Code)?.is_some() { Ok(Ret(v_int(1))) } else { Ok(Ret(v_int(0))) @@ -43,7 +43,7 @@ fn bf_is_member(bf_args: &mut BfCallState<'_>) -> Result { } Variant::Map(map) => Ok(Ret(v_int( map.iter() - .position(|(_item_key, item_value)| value.eq_case_sensitive(item_value)) + .position(|(_item_key, item_value)| value.eq_case_sensitive(&item_value)) .map(|pos| pos + 1) .unwrap_or(0) as i64, ))), @@ -56,48 +56,38 @@ fn bf_listinsert(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() < 2 || bf_args.args.len() > 3 { return Err(BfErr::Code(E_ARGS)); } - let len = bf_args.args.len(); - let value = bf_args.args[1].clone(); - if len == 2 { - let list = &mut bf_args.args[0]; - let Variant::List(list) = list.variant_mut() else { - return Err(BfErr::Code(E_TYPE)); - }; - Ok(Ret(list.push(value))) - } else { - let index = bf_args.args[2].clone(); - let list = &mut bf_args.args[0]; - let Variant::List(list) = list.variant_mut() else { - return Err(BfErr::Code(E_TYPE)); - }; - let index = match one_to_zero_index(&index) { - Ok(i) => i, - Err(e) => return Err(BfErr::Code(e)), - }; - Ok(Ret(list.insert(index as isize, value))) + let value = &bf_args.args[1]; + let list = &bf_args.args[0]; + if list.type_code() != VarType::TYPE_LIST { + return Err(BfErr::Code(E_TYPE)); + } + // If two args, treat as push. If three, treat as insert. + if bf_args.args.len() == 2 { + return Ok(Ret(list.push(value).map_err(BfErr::Code)?)); } + let index = &bf_args.args[2]; + let res = list.insert(index, value, IndexMode::OneBased); + Ok(Ret(res.map_err(BfErr::Code)?)) } + bf_declare!(listinsert, bf_listinsert); fn bf_listappend(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() < 2 || bf_args.args.len() > 3 { return Err(BfErr::Code(E_ARGS)); } - let value = bf_args.args[1].clone(); - let list = &mut bf_args.args[0]; - let Variant::List(mut list) = list.variant().clone() else { + let value = &bf_args.args[1]; + let list = &bf_args.args[0]; + if list.type_code() != VarType::TYPE_LIST { return Err(BfErr::Code(E_TYPE)); - }; - let new_list = if bf_args.args.len() == 2 { - list.push(value.clone()) - } else { - let index = bf_args.args[2].variant(); - let Variant::Int(index) = index else { - return Err(BfErr::Code(E_TYPE)); - }; - list.insert(*index as isize, value.clone()) - }; - Ok(Ret(new_list)) + } + // If two args, treat as push. If three, treat as insert. + if bf_args.args.len() == 2 { + return Ok(Ret(list.push(value).map_err(BfErr::Code)?)); + } + let index = &bf_args.args[2]; + let res = list.insert(index, value, IndexMode::ZeroBased); + Ok(Ret(res.map_err(BfErr::Code)?)) } bf_declare!(listappend, bf_listappend); @@ -106,15 +96,10 @@ fn bf_listdelete(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_ARGS)); } let index = bf_args.args[1].clone(); - let list = bf_args.args[0].variant_mut(); - let Variant::List(list) = list else { - return Err(BfErr::Code(E_TYPE)); - }; - let index = match one_to_zero_index(&index) { - Ok(i) => i, - Err(e) => return Err(BfErr::Code(e)), - }; - Ok(Ret(list.remove_at(index))) + let list = &bf_args.args[0]; + Ok(Ret(list + .remove_at(&index, IndexMode::OneBased) + .map_err(BfErr::Code)?)) } bf_declare!(listdelete, bf_listdelete); @@ -125,14 +110,12 @@ fn bf_listset(bf_args: &mut BfCallState<'_>) -> Result { let index = bf_args.args[2].clone(); let value = bf_args.args[1].clone(); let list = &mut bf_args.args[0]; - let Variant::List(ref mut list) = list.variant_mut() else { + if list.type_code() != VarType::TYPE_LIST { return Err(BfErr::Code(E_TYPE)); - }; - let index = match one_to_zero_index(&index) { - Ok(i) => i, - Err(e) => return Err(BfErr::Code(e)), - }; - Ok(Ret(list.set(index as usize, value.clone()))) + } + Ok(Ret(list + .index_set(&index, &value, IndexMode::OneBased) + .map_err(BfErr::Code)?)) } bf_declare!(listset, bf_listset); @@ -142,13 +125,10 @@ fn bf_setadd(bf_args: &mut BfCallState<'_>) -> Result { } let value = bf_args.args[1].clone(); let list = &mut bf_args.args[0]; - let Variant::List(ref mut list) = list.variant_mut() else { + let Variant::List(list) = list.variant() else { return Err(BfErr::Code(E_TYPE)); }; - if !list.contains(&value) { - return Ok(Ret(list.push(value.clone()))); - } - Ok(Ret(bf_args.args[0].clone())) + Ok(Ret(list.set_add(&value).map_err(BfErr::Code)?)) } bf_declare!(setadd, bf_setadd); @@ -157,11 +137,11 @@ fn bf_setremove(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_ARGS)); } let value = bf_args.args[1].clone(); - let list = bf_args.args[0].variant_mut(); - let Variant::List(ref mut list) = list else { + let list = bf_args.args[0].variant(); + let Variant::List(list) = list else { return Err(BfErr::Code(E_TYPE)); }; - Ok(Ret(list.setremove(&value))) + Ok(Ret(list.set_remove(&value).map_err(BfErr::Code)?)) } bf_declare!(setremove, bf_setremove); @@ -290,18 +270,21 @@ fn do_re_match(bf_args: &mut BfCallState<'_>, reverse: bool) -> Result>(), + .map(|(start, end)| v_list(&[v_int(*start as i64), v_int(*end as i64)])), ); Ok(Ret(v_list(&[ v_int(overall.0 as i64), @@ -399,7 +382,7 @@ fn bf_substitute(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_INVARG)); } - let (Some(a), Some(b)) = (subs.get(2), subs.get(3)) else { + let (Ok(a), Ok(b)) = (subs.index(2), subs.index(3)) else { return Err(BfErr::Code(E_INVARG)); }; let (Variant::List(subs), Variant::Str(source)) = (a.variant(), b.variant()) else { @@ -415,7 +398,7 @@ fn bf_substitute(bf_args: &mut BfCallState<'_>) -> Result { if sub.len() != 2 { return Err(BfErr::Code(E_INVARG)); } - let (Some(start), Some(end)) = (sub.get(0), sub.get(1)) else { + let (Ok(start), Ok(end)) = (sub.index(0), sub.index(1)) else { return Err(BfErr::Code(E_INVARG)); }; let (Variant::Int(start), Variant::Int(end)) = (start.variant(), end.variant()) else { @@ -424,7 +407,7 @@ fn bf_substitute(bf_args: &mut BfCallState<'_>) -> Result { mysubs.push((*start as isize, *end as isize)); } - match substitute(template.as_str(), &mysubs, source.as_str()) { + match substitute(&template.as_string(), &mysubs, &source.as_string()) { Ok(r) => Ok(Ret(v_string(r))), Err(e) => Err(BfErr::Code(e)), } diff --git a/crates/kernel/src/builtins/bf_maps.rs b/crates/kernel/src/builtins/bf_maps.rs index 632bdc4f..36f76b66 100644 --- a/crates/kernel/src/builtins/bf_maps.rs +++ b/crates/kernel/src/builtins/bf_maps.rs @@ -15,9 +15,9 @@ use crate::bf_declare; use crate::builtins::{BfCallState, BfErr, BfRet, BuiltinFunction}; use moor_compiler::offset_for_builtin; -use moor_values::var::Error::{E_ARGS, E_RANGE, E_TYPE}; -use moor_values::var::{v_bool, v_listv, Var, Variant}; - +use moor_values::Associative; +use moor_values::Error::{E_ARGS, E_RANGE, E_TYPE}; +use moor_values::{v_bool, v_list, Var, Variant}; /// Returns a copy of map with the value corresponding to key removed. If key is not a valid key, then E_RANGE is raised. fn bf_mapdelete(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 2 { @@ -35,7 +35,7 @@ fn bf_mapdelete(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_TYPE)); } - let (nm, Some(_)) = m.remove(&bf_args.args[1]) else { + let (nm, Some(_)) = m.remove(&bf_args.args[1], false) else { return Err(BfErr::Code(E_RANGE)); }; @@ -54,7 +54,7 @@ fn bf_mapkeys(bf_args: &mut BfCallState<'_>) -> Result { let keys: Vec = m.iter().map(|kv| kv.0.clone()).collect(); - Ok(BfRet::Ret(v_listv(keys))) + Ok(BfRet::Ret(v_list(&keys))) } bf_declare!(mapkeys, bf_mapkeys); @@ -69,7 +69,7 @@ fn bf_mapvalues(bf_args: &mut BfCallState<'_>) -> Result { let values: Vec = m.iter().map(|kv| kv.1.clone()).collect(); - Ok(BfRet::Ret(v_listv(values))) + Ok(BfRet::Ret(v_list(&values))) } bf_declare!(mapvalues, bf_mapvalues); @@ -89,9 +89,10 @@ fn bf_maphaskey(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_TYPE)); } - let v = m.get(&bf_args.args[1]); - - Ok(BfRet::Ret(v_bool(v.is_some()))) + let contains = m + .contains_key(&bf_args.args[1], false) + .map_err(BfErr::Code)?; + Ok(BfRet::Ret(v_bool(contains))) } bf_declare!(maphaskey, bf_maphaskey); diff --git a/crates/kernel/src/builtins/bf_num.rs b/crates/kernel/src/builtins/bf_num.rs index 1adab5be..5f3ddb78 100644 --- a/crates/kernel/src/builtins/bf_num.rs +++ b/crates/kernel/src/builtins/bf_num.rs @@ -16,9 +16,9 @@ use decorum::R64; use rand::Rng; use moor_compiler::offset_for_builtin; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_TYPE}; -use moor_values::var::Variant; -use moor_values::var::{v_float, v_int, v_str}; +use moor_values::Error::{E_ARGS, E_INVARG, E_TYPE}; +use moor_values::Variant; +use moor_values::{v_float, v_int, v_str}; use crate::bf_declare; use crate::builtins::BfRet::Ret; diff --git a/crates/kernel/src/builtins/bf_objects.rs b/crates/kernel/src/builtins/bf_objects.rs index 73b53bca..e7d3eb6f 100644 --- a/crates/kernel/src/builtins/bf_objects.rs +++ b/crates/kernel/src/builtins/bf_objects.rs @@ -20,12 +20,11 @@ use moor_values::model::Named; use moor_values::model::WorldStateError; use moor_values::model::{ObjFlag, ValSet}; use moor_values::util::BitEnum; -use moor_values::var::v_listv; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_NACC, E_PERM, E_TYPE}; -use moor_values::var::Symbol; -use moor_values::var::{v_bool, v_int, v_none, v_objid, v_str}; -use moor_values::var::{List, Variant}; -use moor_values::NOTHING; +use moor_values::Error::{E_ARGS, E_INVARG, E_NACC, E_PERM, E_TYPE}; +use moor_values::{v_bool, v_int, v_none, v_objid, v_str}; +use moor_values::{v_list, Sequence, Symbol}; +use moor_values::{v_list_iter, NOTHING}; +use moor_values::Variant; use crate::bf_declare; use crate::builtins::BfRet::{Ret, VmInstr}; @@ -109,7 +108,7 @@ fn bf_children(bf_args: &mut BfCallState<'_>) -> Result { .map_err(world_state_bf_err)?; let children = children.iter().map(v_objid).collect::>(); - Ok(Ret(v_listv(children))) + Ok(Ret(v_list(&children))) } bf_declare!(children, bf_children); @@ -170,7 +169,7 @@ fn bf_create(bf_args: &mut BfCallState<'_>) -> Result { location: new_obj, this: new_obj, player: bf_args.exec_state.top().player, - args: List::new(), + args: vec![], argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -261,7 +260,7 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result { } } } - let contents = v_listv(contents); + let contents = v_list(&contents); match bf_args.world_state.find_method_verb_on( bf_args.task_perms_who(), obj, @@ -280,7 +279,7 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result { location: obj, this: obj, player: bf_args.exec_state.top().player, - args: List::new(), + args: Vec::new(), argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -311,14 +310,14 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result { panic!("Invalid trampoline argument for bf_recycle"); }; 'inner: loop { - debug!(?obj, contents = ?contents, "Calling :exitfunc for objects contents"); if contents.is_empty() { let bf_frame = bf_args.bf_frame_mut(); bf_frame.bf_trampoline_arg = None; bf_frame.bf_trampoline = Some(BF_RECYCLE_TRAMPOLINE_DONE_MOVE); continue 'outer; } - let (head_obj, contents) = contents.pop_front(); + let (head_obj, contents) = + contents.pop_front().map_err(|_| BfErr::Code(E_INVARG))?; let Variant::Obj(head_obj) = head_obj.variant() else { panic!("Invalid trampoline argument for bf_recycle"); }; @@ -348,7 +347,7 @@ fn bf_recycle(bf_args: &mut BfCallState<'_>) -> Result { location: *head_obj, this: *head_obj, player: bf_args.exec_state.top().player, - args: List::from_slice(&[v_objid(obj)]), + args: vec![v_objid(obj)], argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -449,7 +448,7 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result { location: whereto, this: whereto, player: bf_args.exec_state.top().player, - args: List::from_slice(&[v_objid(what)]), + args: vec![v_objid(what)], argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -526,7 +525,7 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result { location: original_location, this: original_location, player: bf_args.exec_state.top().player, - args: List::from_slice(&[v_objid(what)]), + args: vec![v_objid(what)], argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -572,7 +571,7 @@ fn bf_move(bf_args: &mut BfCallState<'_>) -> Result { location: whereto, this: whereto, player: bf_args.exec_state.top().player, - args: List::from_slice(&[v_objid(what)]), + args: vec![v_objid(what)], argstr: "".to_string(), caller: bf_args.exec_state.top().this, }, @@ -620,7 +619,7 @@ fn bf_verbs(bf_args: &mut BfCallState<'_>) -> Result { .iter() .map(|v| v_str(v.names().first().unwrap())) .collect(); - Ok(Ret(v_listv(verbs))) + Ok(Ret(v_list(&verbs))) } bf_declare!(verbs, bf_verbs); @@ -640,7 +639,7 @@ fn bf_properties(bf_args: &mut BfCallState<'_>) -> Result { .properties(bf_args.task_perms_who(), *obj) .map_err(world_state_bf_err)?; let props: Vec<_> = props.iter().map(|p| v_str(p.name())).collect(); - Ok(Ret(v_listv(props))) + Ok(Ret(v_list(&props))) } bf_declare!(properties, bf_properties); @@ -696,9 +695,7 @@ fn bf_players(bf_args: &mut BfCallState<'_>) -> Result { } let players = bf_args.world_state.players().map_err(world_state_bf_err)?; - Ok(Ret(v_listv( - players.iter().map(v_objid).collect::>(), - ))) + Ok(Ret(v_list_iter(players.iter().map(v_objid)))) } bf_declare!(players, bf_players); diff --git a/crates/kernel/src/builtins/bf_properties.rs b/crates/kernel/src/builtins/bf_properties.rs index f4ca3752..cad2c3c2 100644 --- a/crates/kernel/src/builtins/bf_properties.rs +++ b/crates/kernel/src/builtins/bf_properties.rs @@ -15,11 +15,11 @@ use moor_compiler::offset_for_builtin; use moor_values::model::{PropAttrs, PropFlag}; use moor_values::util::BitEnum; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_TYPE}; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_bool, v_list, v_none, v_objid, v_string}; -use moor_values::var::{v_empty_list, List}; +use moor_values::Error::{E_ARGS, E_INVARG, E_TYPE}; +use moor_values::Variant; +use moor_values::{v_bool, v_list, v_none, v_objid, v_string}; +use moor_values::{v_empty_list, List}; +use moor_values::{Sequence, Symbol}; use crate::bf_declare; use crate::builtins::BfErr::Code; @@ -43,7 +43,7 @@ fn bf_property_info(bf_args: &mut BfCallState<'_>) -> Result { .get_property_info( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(prop_name.as_str()), + Symbol::mk_case_insensitive(&prop_name.as_string()), ) .map_err(world_state_bf_err)?; let owner = perms.owner(); @@ -66,7 +66,7 @@ fn bf_property_info(bf_args: &mut BfCallState<'_>) -> Result { bf_declare!(property_info, bf_property_info); enum InfoParseResult { - Fail(moor_values::var::Error), + Fail(moor_values::Error), Success(PropAttrs), } @@ -75,26 +75,26 @@ fn info_to_prop_attrs(info: &List) -> InfoParseResult { return InfoParseResult::Fail(E_ARGS); } - let owner = info.get(0).unwrap(); + let owner = info.index(0).unwrap(); let Variant::Obj(owner) = owner.variant() else { return InfoParseResult::Fail(E_TYPE); }; - let perms = info.get(1).unwrap(); + let perms = info.index(1).unwrap(); let Variant::Str(perms) = perms.variant() else { return InfoParseResult::Fail(E_TYPE); }; let name = if info.len() == 3 { - let name = info.get(2).unwrap(); + let name = info.index(2).unwrap(); let Variant::Str(name) = name.variant() else { return InfoParseResult::Fail(E_TYPE); }; - Some(name.to_string()) + Some(name.as_string()) } else { None }; let mut flags = BitEnum::new(); - for c in perms.as_str().chars() { + for c in perms.as_string().chars() { match c { 'r' => flags |= PropFlag::Read, 'w' => flags |= PropFlag::Write, @@ -138,7 +138,7 @@ fn bf_set_property_info(bf_args: &mut BfCallState<'_>) -> Result { .set_property_info( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(prop_name.as_str()), + Symbol::mk_case_insensitive(prop_name.as_string().as_str()), attrs, ) .map_err(world_state_bf_err)?; @@ -161,7 +161,7 @@ fn bf_is_clear_property(bf_args: &mut BfCallState<'_>) -> Result { .is_property_clear( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(prop_name.as_str()), + Symbol::mk_case_insensitive(prop_name.as_string().as_str()), ) .map_err(world_state_bf_err)?; Ok(Ret(v_bool(is_clear))) @@ -183,7 +183,7 @@ fn bf_clear_property(bf_args: &mut BfCallState<'_>) -> Result { .clear_property( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(prop_name.as_str()), + Symbol::mk_case_insensitive(prop_name.as_string().as_str()), ) .map_err(world_state_bf_err)?; Ok(Ret(v_empty_list())) @@ -218,7 +218,7 @@ fn bf_add_property(bf_args: &mut BfCallState<'_>) -> Result { bf_args.task_perms_who(), *location, *location, - Symbol::mk_case_insensitive(name.as_str()), + Symbol::mk_case_insensitive(name.as_string().as_str()), attrs.owner.unwrap(), attrs.flags.unwrap(), Some(value), @@ -243,7 +243,7 @@ fn bf_delete_property(bf_args: &mut BfCallState<'_>) -> Result { .delete_property( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(prop_name.as_str()), + Symbol::mk_case_insensitive(prop_name.as_string().as_str()), ) .map_err(world_state_bf_err)?; Ok(Ret(v_empty_list())) diff --git a/crates/kernel/src/builtins/bf_server.rs b/crates/kernel/src/builtins/bf_server.rs index f528591a..ee723860 100644 --- a/crates/kernel/src/builtins/bf_server.rs +++ b/crates/kernel/src/builtins/bf_server.rs @@ -24,18 +24,18 @@ use moor_compiler::compile; use moor_compiler::{offset_for_builtin, ArgCount, ArgType, Builtin, BUILTINS}; use moor_values::model::{ObjFlag, WorldStateError}; use moor_values::tasks::NarrativeEvent; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_INVIND, E_PERM, E_TYPE}; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_bool, v_int, v_list, v_none, v_objid, v_str, v_string, Var}; -use moor_values::var::{v_listv, Error}; +use moor_values::Error::{E_ARGS, E_INVARG, E_INVIND, E_PERM, E_TYPE}; +use moor_values::Symbol; +use moor_values::Variant; +use moor_values::{v_bool, v_int, v_list, v_none, v_objid, v_str, v_string, Var}; +use moor_values::{v_list_iter, Error}; use crate::bf_declare; use crate::builtins::BfRet::{Ret, VmInstr}; use crate::builtins::{world_state_bf_err, BfCallState, BfErr, BfRet, BuiltinFunction}; use crate::vm::ExecutionResult; use moor_values::tasks::TaskId; -use moor_values::var::VarType::TYPE_STR; +use moor_values::VarType::TYPE_STR; fn bf_noop(bf_args: &mut BfCallState<'_>) -> Result { error!( @@ -57,7 +57,7 @@ fn bf_notify(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 2 { return Err(BfErr::Code(E_ARGS)); } - if bf_args.args[1].type_id() != TYPE_STR { + if bf_args.args[1].type_code() != TYPE_STR { return Err(BfErr::Code(E_TYPE)); } } @@ -82,7 +82,9 @@ fn bf_notify(bf_args: &mut BfCallState<'_>) -> Result { let Variant::Str(content_type) = bf_args.args[2].variant() else { return Err(BfErr::Code(E_TYPE)); }; - Some(Symbol::mk_case_insensitive(content_type.as_str())) + Some(Symbol::mk_case_insensitive( + content_type.as_string().as_str(), + )) } else { None }; @@ -103,14 +105,13 @@ fn bf_connected_players(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_ARGS)); } - Ok(Ret(v_listv( + Ok(Ret(v_list_iter( bf_args .session .connected_players() .unwrap() .iter() - .map(|p| v_objid(*p)) - .collect::>(), + .map(|p| v_objid(*p)), ))) } bf_declare!(connected_players, bf_connected_players); @@ -168,28 +169,23 @@ fn bf_callers(bf_args: &mut BfCallState<'_>) -> Result { // We have to exempt ourselves from the callers list. let callers = bf_args.exec_state.callers()[1..].to_vec(); - Ok(Ret(v_listv( - callers - .iter() - .map(|c| { - let callers = vec![ - // this - v_objid(c.this), - // verb name - v_string(c.verb_name.to_string()), - // 'programmer' - v_objid(c.programmer), - // verb location - v_objid(c.definer), - // player - v_objid(c.player), - // line number - v_int(c.line_number as i64), - ]; - v_listv(callers) - }) - .collect::>(), - ))) + Ok(Ret(v_list_iter(callers.iter().map(|c| { + let callers = vec![ + // this + v_objid(c.this), + // verb name + v_string(c.verb_name.to_string()), + // 'programmer' + v_objid(c.programmer), + // verb location + v_objid(c.definer), + // player + v_objid(c.player), + // line number + v_int(c.line_number as i64), + ]; + v_list(&callers) + })))) } bf_declare!(callers, bf_callers); @@ -277,7 +273,7 @@ fn bf_shutdown(bf_args: &mut BfCallState<'_>) -> Result { let Variant::Str(msg) = bf_args.args[0].variant() else { return Err(BfErr::Code(E_TYPE)); }; - Some(msg.as_str().to_string()) + Some(msg.as_string()) }; bf_args @@ -354,7 +350,7 @@ fn bf_raise(bf_args: &mut BfCallState<'_>) -> Result { let Variant::Str(msg) = bf_args.args[1].variant() else { return Err(BfErr::Code(E_TYPE)); }; - Some(msg.to_string()) + Some(msg.as_string()) } else { None }; @@ -449,31 +445,28 @@ fn bf_queued_tasks(bf_args: &mut BfCallState<'_>) -> Result { // return in form: // {, , , , // , , , , } - let tasks: Vec<_> = tasks - .iter() - .map(|task| { - let task_id = v_int(task.task_id as i64); - let start_time = match task.start_time { - None => v_none(), - Some(start_time) => { - let time = start_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); - v_int(time.as_secs() as i64) - } - }; - let x = v_none(); - let y = v_none(); - let programmer = v_objid(task.permissions); - let verb_loc = v_objid(task.verb_definer); - let verb_name = v_str(task.verb_name.as_str()); - let line = v_int(task.line_number as i64); - let this = v_objid(task.this); - v_list(&[ - task_id, start_time, x, y, programmer, verb_loc, verb_name, line, this, - ]) - }) - .collect(); - - Ok(Ret(v_listv(tasks))) + let tasks = tasks.iter().map(|task| { + let task_id = v_int(task.task_id as i64); + let start_time = match task.start_time { + None => v_none(), + Some(start_time) => { + let time = start_time.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + v_int(time.as_secs() as i64) + } + }; + let x = v_none(); + let y = v_none(); + let programmer = v_objid(task.permissions); + let verb_loc = v_objid(task.verb_definer); + let verb_name = v_str(task.verb_name.as_str()); + let line = v_int(task.line_number as i64); + let this = v_objid(task.this); + v_list(&[ + task_id, start_time, x, y, programmer, verb_loc, verb_name, line, this, + ]) + }); + + Ok(Ret(v_list_iter(tasks))) } bf_declare!(queued_tasks, bf_queued_tasks); @@ -617,7 +610,7 @@ fn bf_call_function(bf_args: &mut BfCallState<'_>) -> Result { let args = &bf_args.args[1..]; // Find the function id for the given function name. - let func_name = Symbol::mk_case_insensitive(func_name.as_str()); + let func_name = Symbol::mk_case_insensitive(func_name.as_string().as_str()); let builtin = BUILTINS .find_builtin(func_name) .ok_or(BfErr::Code(E_ARGS))?; @@ -690,22 +683,16 @@ fn bf_function_info_to_list(bf: &Builtin) -> Var { ArgCount::Q(q) => v_int(q as i64), ArgCount::U => v_int(-1), }; - let types = bf - .types - .iter() - .map(|t| match t { - ArgType::Typed(ty) => v_int(*ty as i64), - ArgType::Any => v_int(-1), - ArgType::AnyNum => v_int(-2), - }) - .collect::>(); - - v_listv(vec![ - v_str(bf.name.as_str()), + let types = bf.types.iter().map(|t| match t { + ArgType::Typed(ty) => v_int(*ty as i64), + ArgType::Any => v_int(-1), + ArgType::AnyNum => v_int(-2), + }); + + v_list(&[v_str(bf.name.as_str()), min_args, max_args, - v_listv(types), - ]) + v_list_iter(types)]) } fn bf_function_info(bf_args: &mut BfCallState<'_>) -> Result { @@ -717,7 +704,7 @@ fn bf_function_info(bf_args: &mut BfCallState<'_>) -> Result { let Variant::Str(func_name) = bf_args.args[0].variant() else { return Err(BfErr::Code(E_TYPE)); }; - let func_name = Symbol::mk_case_insensitive(func_name.as_str()); + let func_name = Symbol::mk_case_insensitive(&func_name.as_string()); let bf = BUILTINS .find_builtin(func_name) .ok_or(BfErr::Code(E_ARGS))?; @@ -728,12 +715,11 @@ fn bf_function_info(bf_args: &mut BfCallState<'_>) -> Result { return Ok(Ret(desc)); } - let bf_list: Vec<_> = BUILTINS + let bf_list = BUILTINS .descriptions() .filter(|&bf| bf.implemented) - .map(bf_function_info_to_list) - .collect(); - Ok(Ret(v_listv(bf_list))) + .map(bf_function_info_to_list); + Ok(Ret(v_list_iter(bf_list))) } bf_declare!(function_info, bf_function_info); @@ -776,10 +762,10 @@ fn bf_eval(bf_args: &mut BfCallState<'_>) -> Result { match tramp { BF_SERVER_EVAL_TRAMPOLINE_START_INITIALIZE => { - let program_code = program_code.as_str(); - let program = match compile(program_code, bf_args.config.compile_options()) { + let program_code = program_code.as_string(); + let program = match compile(&program_code, bf_args.config.compile_options()) { Ok(program) => program, - Err(e) => return Ok(Ret(v_listv(vec![v_int(0), v_string(e.to_string())]))), + Err(e) => return Ok(Ret(v_list(&[v_int(0), v_string(e.to_string())]))), }; let bf_frame = bf_args.bf_frame_mut(); bf_frame.bf_trampoline = Some(BF_SERVER_EVAL_TRAMPOLINE_RESUME); @@ -794,7 +780,7 @@ fn bf_eval(bf_args: &mut BfCallState<'_>) -> Result { BF_SERVER_EVAL_TRAMPOLINE_RESUME => { // Value must be on in our activation's "return value" let value = bf_args.exec_state.top().frame.return_value(); - Ok(Ret(v_listv(vec![v_bool(true), value]))) + Ok(Ret(v_list(&[v_bool(true), value]))) } _ => { panic!("Invalid trampoline value for bf_eval: {}", tramp); diff --git a/crates/kernel/src/builtins/bf_strings.rs b/crates/kernel/src/builtins/bf_strings.rs index 8a3ae962..865adef4 100644 --- a/crates/kernel/src/builtins/bf_strings.rs +++ b/crates/kernel/src/builtins/bf_strings.rs @@ -17,9 +17,9 @@ use rand::distributions::Alphanumeric; use rand::Rng; use moor_compiler::offset_for_builtin; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_TYPE}; -use moor_values::var::Variant; -use moor_values::var::{v_int, v_str, v_string}; +use moor_values::Error::{E_ARGS, E_INVARG, E_TYPE}; +use moor_values::Variant; +use moor_values::{v_int, v_str, v_string}; use crate::bf_declare; use crate::builtins::BfRet::Ret; @@ -68,7 +68,13 @@ fn bf_strsub(bf_args: &mut BfCallState<'_>) -> Result { ); match (subject, what, with) { (Variant::Str(subject), Variant::Str(what), Variant::Str(with)) => Ok(Ret(v_str( - strsub(subject.as_str(), what.as_str(), with.as_str(), case_matters).as_str(), + strsub( + subject.as_string().as_str(), + what.as_string().as_str(), + with.as_string().as_str(), + case_matters, + ) + .as_str(), ))), _ => Err(BfErr::Code(E_TYPE)), } @@ -114,8 +120,8 @@ fn bf_index(bf_args: &mut BfCallState<'_>) -> Result { let (subject, what) = (bf_args.args[0].variant(), bf_args.args[1].variant()); match (subject, what) { (Variant::Str(subject), Variant::Str(what)) => Ok(Ret(v_int(str_index( - subject.as_str(), - what.as_str(), + subject.as_string().as_str(), + what.as_string().as_str(), case_matters, )))), _ => Err(BfErr::Code(E_TYPE)), @@ -138,8 +144,8 @@ fn bf_rindex(bf_args: &mut BfCallState<'_>) -> Result { let (subject, what) = (bf_args.args[0].variant(), bf_args.args[1].variant()); match (subject, what) { (Variant::Str(subject), Variant::Str(what)) => Ok(Ret(v_int(str_rindex( - subject.as_str(), - what.as_str(), + subject.as_string().as_str(), + what.as_string().as_str(), case_matters, )))), _ => Err(BfErr::Code(E_TYPE)), @@ -153,9 +159,9 @@ fn bf_strcmp(bf_args: &mut BfCallState<'_>) -> Result { } let (str1, str2) = (bf_args.args[0].variant(), bf_args.args[1].variant()); match (str1, str2) { - (Variant::Str(str1), Variant::Str(str2)) => { - Ok(Ret(v_int(str1.as_str().cmp(str2.as_str()) as i64))) - } + (Variant::Str(str1), Variant::Str(str2)) => Ok(Ret(v_int( + str1.as_string().as_str().cmp(str2.as_string().as_str()) as i64, + ))), _ => Err(BfErr::Code(E_TYPE)), } } @@ -187,10 +193,10 @@ fn bf_crypt(bf_args: &mut BfCallState<'_>) -> Result { let Variant::Str(salt) = bf_args.args[1].variant() else { return Err(BfErr::Code(E_TYPE)); }; - String::from(salt.as_str()) + String::from(salt.as_string().as_str()) }; if let Variant::Str(text) = bf_args.args[0].variant() { - let crypted = pwhash::unix::crypt(text.as_str(), salt.as_str()).unwrap(); + let crypted = pwhash::unix::crypt(text.as_string().as_str(), salt.as_str()).unwrap(); Ok(Ret(v_string(crypted))) } else { Err(BfErr::Code(E_TYPE)) @@ -204,7 +210,7 @@ fn bf_string_hash(bf_args: &mut BfCallState<'_>) -> Result { } match bf_args.args[0].variant() { Variant::Str(s) => { - let hash_digest = md5::Md5::digest(s.as_str().as_bytes()); + let hash_digest = md5::Md5::digest(s.as_string().as_bytes()); Ok(Ret(v_str( format!("{:x}", hash_digest).to_uppercase().as_str(), ))) diff --git a/crates/kernel/src/builtins/bf_values.rs b/crates/kernel/src/builtins/bf_values.rs index a2eb5511..1e68acde 100644 --- a/crates/kernel/src/builtins/bf_values.rs +++ b/crates/kernel/src/builtins/bf_values.rs @@ -13,19 +13,20 @@ // use md5::Digest; -use moor_compiler::offset_for_builtin; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_TYPE}; -use moor_values::var::Variant; -use moor_values::var::{v_bool, v_float, v_int, v_obj, v_str}; -use moor_values::AsByteBuffer; +use moor_compiler::{offset_for_builtin, to_literal}; +use moor_values::Error::{E_ARGS, E_INVARG, E_TYPE}; +use moor_values::Variant; +use moor_values::{v_bool, v_float, v_int, v_obj, v_str}; +use moor_values::{AsByteBuffer, Sequence}; use crate::bf_declare; use crate::builtins::BfRet::Ret; use crate::builtins::{world_state_bf_err, BfCallState, BfErr, BfRet, BuiltinFunction}; +use moor_values::Associative; fn bf_typeof(bf_args: &mut BfCallState<'_>) -> Result { let arg = &bf_args.args[0]; - Ok(Ret(v_int(arg.type_id() as i64))) + Ok(Ret(v_int(arg.type_code() as i64))) } bf_declare!(typeof, bf_typeof); @@ -36,7 +37,7 @@ fn bf_tostr(bf_args: &mut BfCallState<'_>) -> Result { Variant::None => result.push_str("None"), Variant::Int(i) => result.push_str(&i.to_string()), Variant::Float(f) => result.push_str(format!("{:?}", f).as_str()), - Variant::Str(s) => result.push_str(s.as_str()), + Variant::Str(s) => result.push_str(s.as_string().as_str()), Variant::Obj(o) => result.push_str(&o.to_string()), Variant::List(_) => result.push_str("{list}"), Variant::Map(_) => result.push_str("[map]"), @@ -51,7 +52,7 @@ fn bf_toliteral(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 1 { return Err(BfErr::Code(E_ARGS)); } - let literal = bf_args.args[0].to_literal(); + let literal = to_literal(&bf_args.args[0]); Ok(Ret(v_str(literal.as_str()))) } bf_declare!(toliteral, bf_toliteral); @@ -65,7 +66,7 @@ fn bf_toint(bf_args: &mut BfCallState<'_>) -> Result { Variant::Float(f) => Ok(Ret(v_int(*f as i64))), Variant::Obj(o) => Ok(Ret(v_int(o.0))), Variant::Str(s) => { - let i = s.as_str().parse::(); + let i = s.as_string().as_str().parse::(); match i { Ok(i) => Ok(Ret(v_int(i as i64))), Err(_) => Ok(Ret(v_int(0))), @@ -84,15 +85,15 @@ fn bf_toobj(bf_args: &mut BfCallState<'_>) -> Result { match bf_args.args[0].variant() { Variant::Int(i) => Ok(Ret(v_obj(*i))), Variant::Float(f) => Ok(Ret(v_obj(*f as i64))), - Variant::Str(s) if s.as_str().starts_with('#') => { - let i = s.as_str()[1..].parse::(); + Variant::Str(s) if s.as_string().as_str().starts_with('#') => { + let i = s.as_string().as_str()[1..].parse::(); match i { Ok(i) => Ok(Ret(v_obj(i))), Err(_) => Ok(Ret(v_obj(0))), } } Variant::Str(s) => { - let i = s.as_str().parse::(); + let i = s.as_string().as_str().parse::(); match i { Ok(i) => Ok(Ret(v_obj(i))), Err(_) => Ok(Ret(v_obj(0))), @@ -111,7 +112,7 @@ fn bf_tofloat(bf_args: &mut BfCallState<'_>) -> Result { Variant::Int(i) => Ok(Ret(v_float(*i as f64))), Variant::Float(f) => Ok(Ret(v_float(*f))), Variant::Str(s) => { - let f = s.as_str().parse::(); + let f = s.as_string().as_str().parse::(); match f { Ok(f) => Ok(Ret(v_float(f))), Err(_) => Ok(Ret(v_float(0.0))), @@ -127,11 +128,8 @@ fn bf_equal(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 2 { return Err(BfErr::Code(E_ARGS)); } - let result = match (bf_args.args[0].variant(), bf_args.args[1].variant()) { - (Variant::Str(s1), Variant::Str(s2)) => s1.as_str() == s2.as_str().to_lowercase(), - (Variant::Map(m1), Variant::Map(m2)) => m1.eq_case_sensitive(m2), - _ => bf_args.args[0] == bf_args.args[1], - }; + let (a1, a2) = (&bf_args.args[0], &bf_args.args[1]); + let result = a1.eq_case_sensitive(a2); Ok(Ret(v_bool(result))) } bf_declare!(equal, bf_equal); @@ -149,7 +147,7 @@ fn bf_value_hash(bf_args: &mut BfCallState<'_>) -> Result { if bf_args.args.len() != 1 { return Err(BfErr::Code(E_ARGS)); } - let s = bf_args.args[0].to_literal(); + let s = to_literal(&bf_args.args[0]); let hash_digest = md5::Md5::digest(s.as_bytes()); Ok(Ret(v_str( format!("{:x}", hash_digest).to_uppercase().as_str(), diff --git a/crates/kernel/src/builtins/bf_verbs.rs b/crates/kernel/src/builtins/bf_verbs.rs index 79c219f4..d1d90636 100644 --- a/crates/kernel/src/builtins/bf_verbs.rs +++ b/crates/kernel/src/builtins/bf_verbs.rs @@ -15,12 +15,12 @@ use strum::EnumCount; use tracing::{error, warn}; -use moor_compiler::compile; use moor_compiler::offset_for_builtin; use moor_compiler::program_to_tree; use moor_compiler::unparse; use moor_compiler::GlobalName; use moor_compiler::Program; +use moor_compiler::{compile, to_literal}; use moor_values::model::ObjFlag; use moor_values::model::VerbDef; use moor_values::model::WorldStateError; @@ -28,14 +28,14 @@ use moor_values::model::{ArgSpec, VerbArgsSpec}; use moor_values::model::{BinaryType, VerbAttrs, VerbFlag}; use moor_values::model::{HasUuid, Named}; use moor_values::util::BitEnum; -use moor_values::var::Error::{E_ARGS, E_INVARG, E_INVIND, E_PERM, E_TYPE, E_VERBNF}; -use moor_values::var::List; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_empty_list, v_list, v_none, v_objid, v_str, v_string, Var}; -use moor_values::var::{v_listv, Error}; -use moor_values::AsByteBuffer; +use moor_values::Error::{E_ARGS, E_INVARG, E_INVIND, E_PERM, E_TYPE, E_VERBNF}; +use moor_values::List; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Variant; +use moor_values::{v_empty_list, v_list, v_none, v_objid, v_str, v_string, Var}; +use moor_values::{v_list_iter, Error}; +use moor_values::{AsByteBuffer, Sequence}; use crate::bf_declare; use crate::builtins::BfRet::Ret; @@ -65,7 +65,7 @@ fn bf_verb_info(bf_args: &mut BfCallState<'_>) -> Result { .get_verb( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(verb_desc.as_str()), + Symbol::mk_case_insensitive(&verb_desc.as_string()), ) .map_err(world_state_bf_err)?, Variant::Int(verb_index) => { @@ -112,7 +112,7 @@ bf_declare!(verb_info, bf_verb_info); fn get_verbdef(obj: Objid, verbspec: Var, bf_args: &BfCallState<'_>) -> Result { let verbspec_result = match verbspec.variant() { Variant::Str(verb_desc) => { - let verb_desc = Symbol::mk_case_insensitive(verb_desc.as_str()); + let verb_desc = Symbol::mk_case_insensitive(&verb_desc.as_string()); bf_args .world_state .get_verb(bf_args.task_perms_who(), obj, verb_desc) @@ -144,13 +144,13 @@ fn parse_verb_info(info: &List) -> Result { return Err(E_INVARG); } match ( - info.get(0).unwrap().variant(), - info.get(1).unwrap().variant(), - info.get(2).unwrap().variant(), + info.index(0).unwrap().variant(), + info.index(1).unwrap().variant(), + info.index(2).unwrap().variant(), ) { (Variant::Obj(owner), Variant::Str(perms_str), Variant::Str(names)) => { let mut perms = BitEnum::new(); - for c in perms_str.as_str().chars() { + for c in perms_str.as_string().chars() { match c { 'r' => perms |= VerbFlag::Read, 'w' => perms |= VerbFlag::Write, @@ -162,7 +162,7 @@ fn parse_verb_info(info: &List) -> Result { // Split the names string into a list of symbols let name_strings = names - .as_str() + .as_string() .split(' ') .map(Symbol::mk_case_insensitive) .collect::>(); @@ -212,7 +212,7 @@ fn bf_set_verb_info(bf_args: &mut BfCallState<'_>) -> Result { .update_verb( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(verb_name.as_str()), + Symbol::mk_case_insensitive(&verb_name.as_string()), update_attrs, ) .map_err(world_state_bf_err)?; @@ -271,15 +271,15 @@ fn parse_verb_args(verbinfo: &List) -> Result { return Err(E_ARGS); } match ( - verbinfo.get(0).unwrap().variant(), - verbinfo.get(1).unwrap().variant(), - verbinfo.get(2).unwrap().variant(), + verbinfo.index(0).unwrap().variant(), + verbinfo.index(1).unwrap().variant(), + verbinfo.index(2).unwrap().variant(), ) { (Variant::Str(dobj_str), Variant::Str(prep_str), Variant::Str(iobj_str)) => { let (Some(dobj), Some(prep), Some(iobj)) = ( - ArgSpec::from_string(dobj_str.as_str()), - parse_preposition_spec(prep_str.as_str()), - ArgSpec::from_string(iobj_str.as_str()), + ArgSpec::from_string(&dobj_str.as_string()), + parse_preposition_spec(&prep_str.as_string()), + ArgSpec::from_string(&iobj_str.as_string()), ) else { return Err(E_INVARG); }; @@ -329,7 +329,7 @@ fn bf_set_verb_args(bf_args: &mut BfCallState<'_>) -> Result { .update_verb( bf_args.task_perms_who(), *obj, - Symbol::mk_case_insensitive(verb_name.as_str()), + Symbol::mk_case_insensitive(&verb_name.as_string()), update_attrs, ) .map_err(world_state_bf_err)?; @@ -425,9 +425,7 @@ fn bf_verb_code(bf_args: &mut BfCallState<'_>) -> Result { return Err(BfErr::Code(E_INVARG)); } }; - Ok(Ret(v_listv( - unparsed.iter().map(|s| v_str(s)).collect::>(), - ))) + Ok(Ret(v_list_iter(unparsed.iter().map(|s| v_str(s))))) } bf_declare!(verb_code, bf_verb_code); @@ -477,7 +475,7 @@ fn bf_set_verb_code(bf_args: &mut BfCallState<'_>) -> Result { Variant::Str(line) => line, _ => return Err(BfErr::Code(E_TYPE)), }; - code_string.push_str(line.as_str()); + code_string.push_str(&line.as_string()); code_string.push('\n'); } // Now try to compile... @@ -658,7 +656,7 @@ fn bf_disassemble(bf_args: &mut BfCallState<'_>) -> Result { // Write literals indexed by their offset # disassembly.push(v_str("LITERALS:")); for (i, l) in program.literals.iter().enumerate() { - disassembly.push(v_string(format!("{: >3}: {}", i, l.to_literal()))); + disassembly.push(v_string(format!("{: >3}: {}", i, to_literal(l)))); } // Write jump labels indexed by their offset & showing position & optional name @@ -697,7 +695,7 @@ fn bf_disassemble(bf_args: &mut BfCallState<'_>) -> Result { disassembly.push(v_string(format!("{: >3}: {:?}{}", i, op, line_no_string))); } - Ok(Ret(v_listv(disassembly))) + Ok(Ret(v_list(&disassembly))) } bf_declare!(disassemble, bf_disassemble); diff --git a/crates/kernel/src/builtins/mod.rs b/crates/kernel/src/builtins/mod.rs index f46608e1..0bc33b83 100644 --- a/crates/kernel/src/builtins/mod.rs +++ b/crates/kernel/src/builtins/mod.rs @@ -20,10 +20,10 @@ use moor_compiler::{BuiltinId, BUILTINS}; use moor_values::model::Perms; use moor_values::model::WorldState; use moor_values::model::WorldStateError; -use moor_values::var::Error; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Var; +use moor_values::Error; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Var; use crate::builtins::bf_list_sets::register_bf_list_sets; use crate::builtins::bf_maps::register_bf_maps; diff --git a/crates/kernel/src/matching/match_env.rs b/crates/kernel/src/matching/match_env.rs index b84af44e..7bf3f6a3 100644 --- a/crates/kernel/src/matching/match_env.rs +++ b/crates/kernel/src/matching/match_env.rs @@ -14,7 +14,7 @@ use moor_values::model::WorldStateError; use moor_values::model::{ObjSet, ValSet}; -use moor_values::var::Objid; +use moor_values::Objid; use moor_values::{AMBIGUOUS, FAILED_MATCH, NOTHING}; use crate::tasks::command_parse::ParseMatcher; @@ -148,7 +148,7 @@ impl ParseMatcher for MatchEnvironmentParseMatcher { #[cfg(test)] mod tests { - use moor_values::var::Objid; + use moor_values::Objid; use moor_values::{FAILED_MATCH, NOTHING}; use crate::matching::match_env::{ diff --git a/crates/kernel/src/matching/mock_matching_env.rs b/crates/kernel/src/matching/mock_matching_env.rs index db75219b..f7fdd2fb 100644 --- a/crates/kernel/src/matching/mock_matching_env.rs +++ b/crates/kernel/src/matching/mock_matching_env.rs @@ -16,7 +16,7 @@ use std::collections::{HashMap, HashSet}; use moor_values::model::WorldStateError; use moor_values::model::{ObjSet, ValSet}; -use moor_values::var::Objid; +use moor_values::Objid; use moor_values::NOTHING; use crate::matching::match_env::MatchEnvironment; diff --git a/crates/kernel/src/matching/ws_match_env.rs b/crates/kernel/src/matching/ws_match_env.rs index ea438b4a..15e60cb6 100644 --- a/crates/kernel/src/matching/ws_match_env.rs +++ b/crates/kernel/src/matching/ws_match_env.rs @@ -15,7 +15,7 @@ use moor_values::model::ObjSet; use moor_values::model::WorldState; use moor_values::model::WorldStateError; -use moor_values::var::Objid; +use moor_values::Objid; use crate::matching::match_env::MatchEnvironment; diff --git a/crates/kernel/src/tasks/command_parse.rs b/crates/kernel/src/tasks/command_parse.rs index 4c58ee09..2d45fa3a 100644 --- a/crates/kernel/src/tasks/command_parse.rs +++ b/crates/kernel/src/tasks/command_parse.rs @@ -19,8 +19,8 @@ use bincode::{Decode, Encode}; use moor_values::model::WorldStateError; use moor_values::model::{PrepSpec, Preposition}; use moor_values::util; -use moor_values::var::Objid; -use moor_values::var::{v_str, Var}; +use moor_values::Objid; +use moor_values::{v_str, Var}; #[derive(Clone, Eq, PartialEq, Debug, Decode, Encode)] pub struct ParsedCommand { @@ -188,7 +188,7 @@ where mod tests { use moor_values::model::Preposition; use moor_values::util::parse_into_words; - use moor_values::var::v_str; + use moor_values::v_str; use moor_values::FAILED_MATCH; use crate::matching::match_env::MatchEnvironmentParseMatcher; diff --git a/crates/kernel/src/tasks/mod.rs b/crates/kernel/src/tasks/mod.rs index 833037a5..3b46de6c 100644 --- a/crates/kernel/src/tasks/mod.rs +++ b/crates/kernel/src/tasks/mod.rs @@ -18,8 +18,8 @@ use std::time::SystemTime; use bincode::{Decode, Encode}; use moor_compiler::Program; -use moor_values::var::{List, Objid}; -use moor_values::var::{Symbol, Var}; +use moor_values::Objid; +use moor_values::{Symbol, Var}; pub use crate::tasks::tasks_db::{NoopTasksDb, TasksDb, TasksDbError}; use crate::vm::Fork; @@ -75,7 +75,7 @@ pub struct VerbCall { pub location: Objid, pub this: Objid, pub player: Objid, - pub args: List, + pub args: Vec, pub argstr: String, pub caller: Objid, } @@ -124,9 +124,9 @@ pub mod vm_test_utils { use moor_compiler::Program; use moor_values::model::WorldState; - use moor_values::var::Symbol; - use moor_values::var::{List, Objid, Var}; + use moor_values::Symbol; use moor_values::SYSTEM_OBJECT; + use moor_values::{Objid, Var}; use crate::builtins::BuiltinRegistry; use crate::config::Config; @@ -217,7 +217,7 @@ pub mod vm_test_utils { location: SYSTEM_OBJECT, this: SYSTEM_OBJECT, player: SYSTEM_OBJECT, - args: List::from_slice(&args), + args, argstr: "".to_string(), caller: SYSTEM_OBJECT, }, @@ -244,7 +244,7 @@ pub mod scheduler_test_utils { use std::time::Duration; use moor_values::tasks::{CommandError, SchedulerError}; - use moor_values::var::{Error::E_VERBNF, Objid, Var}; + use moor_values::{Error::E_VERBNF, Objid, Var}; use super::TaskHandle; use crate::config::Config; @@ -316,7 +316,7 @@ pub enum TaskStart { player: Objid, vloc: Objid, verb: Symbol, - args: List, + args: Vec, argstr: String, }, /// The scheduler is telling the task to run a task that was forked from another task. diff --git a/crates/kernel/src/tasks/scheduler.rs b/crates/kernel/src/tasks/scheduler.rs index a2d159d5..92dc8b59 100644 --- a/crates/kernel/src/tasks/scheduler.rs +++ b/crates/kernel/src/tasks/scheduler.rs @@ -34,11 +34,11 @@ use moor_values::model::{CommitResult, Perms}; use moor_values::tasks::{ AbortLimitReason, CommandError, SchedulerError, TaskId, VerbProgramError, }; -use moor_values::var::Error::{E_INVARG, E_PERM}; -use moor_values::var::Symbol; -use moor_values::var::{v_err, v_int, v_none, v_string, List, Var}; -use moor_values::var::{Objid, Variant}; +use moor_values::Error::{E_INVARG, E_PERM}; +use moor_values::Symbol; +use moor_values::{v_err, v_int, v_none, v_string, Var}; use moor_values::{AsByteBuffer, SYSTEM_OBJECT}; +use moor_values::{Objid, Variant}; use crate::builtins::BuiltinRegistry; use crate::config::Config; @@ -392,7 +392,7 @@ impl Scheduler { player, vloc, verb, - args: List::from_slice(&args), + args, argstr, }); let task_id = self.next_task_id; @@ -461,7 +461,7 @@ impl Scheduler { player, vloc: SYSTEM_OBJECT, verb: *DO_OUT_OF_BAND_COMMAND, - args: List::from_slice(&args), + args, argstr, }); let task_id = self.next_task_id; diff --git a/crates/kernel/src/tasks/scheduler_client.rs b/crates/kernel/src/tasks/scheduler_client.rs index c880de5e..d7342434 100644 --- a/crates/kernel/src/tasks/scheduler_client.rs +++ b/crates/kernel/src/tasks/scheduler_client.rs @@ -20,8 +20,8 @@ use tracing::{instrument, trace}; use uuid::Uuid; use moor_compiler::{compile, Program}; -use moor_values::var::Symbol; -use moor_values::var::{Objid, Var}; +use moor_values::Symbol; +use moor_values::{Objid, Var}; use crate::config::Config; use crate::tasks::sessions::Session; diff --git a/crates/kernel/src/tasks/sessions.rs b/crates/kernel/src/tasks/sessions.rs index 4e015a84..841b47f6 100644 --- a/crates/kernel/src/tasks/sessions.rs +++ b/crates/kernel/src/tasks/sessions.rs @@ -18,7 +18,7 @@ use thiserror::Error; use uuid::Uuid; use moor_values::tasks::NarrativeEvent; -use moor_values::var::Objid; +use moor_values::Objid; /// The interface for managing the user I/O connection side of state, exposed by the scheduler to /// the VM during execution and by the host server to the scheduler. diff --git a/crates/kernel/src/tasks/suspension.rs b/crates/kernel/src/tasks/suspension.rs index 2c658d30..80d919c2 100644 --- a/crates/kernel/src/tasks/suspension.rs +++ b/crates/kernel/src/tasks/suspension.rs @@ -23,7 +23,7 @@ use bincode::{BorrowDecode, Decode, Encode}; use tracing::{debug, error, info, warn}; use uuid::Uuid; -use moor_values::var::{Objid, Var}; +use moor_values::{Objid, Var}; use crate::tasks::sessions::{NoopClientSession, Session, SessionFactory}; use crate::tasks::task::Task; diff --git a/crates/kernel/src/tasks/task.rs b/crates/kernel/src/tasks/task.rs index 1ff90759..fca8bc54 100644 --- a/crates/kernel/src/tasks/task.rs +++ b/crates/kernel/src/tasks/task.rs @@ -40,9 +40,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::var::Symbol; -use moor_values::var::{v_int, v_str}; -use moor_values::var::{List, Objid}; +use moor_values::Symbol; +use moor_values::{v_int, v_str}; +use moor_values::Objid; use moor_values::{NOTHING, SYSTEM_OBJECT}; use crate::builtins::BuiltinRegistry; @@ -436,13 +436,13 @@ impl Task { } Ok(verb_info) => { let arguments = parse_into_words(command); - let arguments = arguments.iter().map(|s| v_str(s)).collect::>(); + let args = arguments.iter().map(|s| v_str(s)).collect::>(); let verb_call = VerbCall { verb_name: Symbol::mk("do_command"), location: SYSTEM_OBJECT, this: SYSTEM_OBJECT, player, - args: List::from_slice(&arguments), + args, argstr: command.to_string(), caller: SYSTEM_OBJECT, }; @@ -534,7 +534,7 @@ impl Task { location: target, this: target, player, - args: List::from_slice(&parsed_command.args), + args: parsed_command.args.clone(), argstr: parsed_command.argstr.clone(), caller: player, }; @@ -653,9 +653,9 @@ mod tests { }; use moor_values::tasks::{CommandError, Event, TaskId}; use moor_values::util::BitEnum; - use moor_values::var::Error::E_DIV; - use moor_values::var::Symbol; - use moor_values::var::{v_int, v_str}; + use moor_values::Error::E_DIV; + use moor_values::Symbol; + use moor_values::{v_int, v_str}; use moor_values::{AsByteBuffer, NOTHING, SYSTEM_OBJECT}; use crate::builtins::BuiltinRegistry; diff --git a/crates/kernel/src/tasks/task_scheduler_client.rs b/crates/kernel/src/tasks/task_scheduler_client.rs index 57e59209..26968da0 100644 --- a/crates/kernel/src/tasks/task_scheduler_client.rs +++ b/crates/kernel/src/tasks/task_scheduler_client.rs @@ -18,9 +18,9 @@ use crossbeam_channel::Sender; use moor_values::model::Perms; use moor_values::tasks::{AbortLimitReason, CommandError, Exception, NarrativeEvent, TaskId}; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Symbol; +use moor_values::Var; use crate::tasks::task::Task; use crate::tasks::TaskDescription; diff --git a/crates/kernel/src/tasks/vm_host.rs b/crates/kernel/src/tasks/vm_host.rs index fc6afda8..d4d800e0 100644 --- a/crates/kernel/src/tasks/vm_host.rs +++ b/crates/kernel/src/tasks/vm_host.rs @@ -31,11 +31,11 @@ use moor_values::model::VerbInfo; use moor_values::model::WorldState; use moor_values::model::{BinaryType, ObjFlag}; use moor_values::tasks::{AbortLimitReason, Exception, TaskId}; -use moor_values::var::Error::E_MAXREC; -use moor_values::var::Var; -use moor_values::var::{v_none, Symbol}; -use moor_values::var::{List, Objid}; use moor_values::AsByteBuffer; +use moor_values::Error::E_MAXREC; +use moor_values::Var; +use moor_values::{v_none, Symbol}; +use moor_values::Objid; use crate::builtins::BuiltinRegistry; use crate::config::Config; @@ -306,7 +306,7 @@ impl VmHost { // After this we will loop around and check the result. result = self.vm_exec_state.call_builtin_function( bf_offset, - List::from_slice(&args), + args, &exec_params, world_state, session.clone(), @@ -458,8 +458,8 @@ impl VmHost { pub fn reset_time(&mut self) { self.vm_exec_state.start_time = Some(SystemTime::now()); } - pub fn args(&self) -> List { - self.vm_exec_state.top().args.clone() + pub fn args(&self) -> &Vec { + &self.vm_exec_state.top().args } } diff --git a/crates/kernel/src/textdump/load_db.rs b/crates/kernel/src/textdump/load_db.rs index 6e284c9d..9198f625 100644 --- a/crates/kernel/src/textdump/load_db.rs +++ b/crates/kernel/src/textdump/load_db.rs @@ -29,8 +29,8 @@ use moor_values::model::VerbFlag; use moor_values::model::{ArgSpec, PrepSpec, VerbArgsSpec}; use moor_values::model::{ObjAttrs, ObjFlag}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Var; use moor_values::{AsByteBuffer, NOTHING}; use crate::textdump::read::TextdumpReaderError; diff --git a/crates/kernel/src/textdump/mod.rs b/crates/kernel/src/textdump/mod.rs index b74bc70d..52f16df9 100644 --- a/crates/kernel/src/textdump/mod.rs +++ b/crates/kernel/src/textdump/mod.rs @@ -18,8 +18,8 @@ use std::collections::BTreeMap; pub use load_db::{read_textdump, textdump_load}; -use moor_values::var::Objid; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Var; pub use read::TextdumpReader; pub use write::TextdumpWriter; pub use write_db::make_textdump; diff --git a/crates/kernel/src/textdump/read.rs b/crates/kernel/src/textdump/read.rs index 4d99e20b..94b6199a 100644 --- a/crates/kernel/src/textdump/read.rs +++ b/crates/kernel/src/textdump/read.rs @@ -21,9 +21,9 @@ use tracing::info; use moor_compiler::Label; use moor_values::model::CompileError; use moor_values::model::WorldStateError; -use moor_values::var::{v_err, v_float, v_int, v_none, v_objid, v_str, Var, VarType}; -use moor_values::var::{v_listv, Error}; -use moor_values::var::{v_map_pairs, Objid}; +use moor_values::Objid; +use moor_values::{v_err, v_float, v_int, v_none, v_objid, v_str, Var, VarType}; +use moor_values::{v_list, v_map, Error}; use crate::textdump::{EncodingMode, Object, Propval, Textdump, Verb, Verbdef}; @@ -148,7 +148,7 @@ impl TextdumpReader { VarType::TYPE_LIST => { let l_size = self.read_num()?; let v: Vec = (0..l_size).map(|_l| self.read_var().unwrap()).collect(); - v_listv(v) + v_list(&v) } VarType::TYPE_MAP => { let num_pairs = self.read_num()?; @@ -159,7 +159,7 @@ impl TextdumpReader { (key, value) }) .collect(); - v_map_pairs(&pairs) + v_map(&pairs) } VarType::TYPE_NONE => v_none(), VarType::TYPE_FLOAT => v_float(self.read_float()?), diff --git a/crates/kernel/src/textdump/write.rs b/crates/kernel/src/textdump/write.rs index 01eb55f5..acb2a545 100644 --- a/crates/kernel/src/textdump/write.rs +++ b/crates/kernel/src/textdump/write.rs @@ -12,14 +12,14 @@ // this program. If not, see . // +use moor_values::{Objid, Sequence}; +use moor_values::{Var, VarType, Variant}; use std::collections::BTreeMap; use std::io; -use moor_values::var::Objid; -use moor_values::var::{Var, VarType, Variant}; - use crate::textdump::read::TYPE_CLEAR; use crate::textdump::{EncodingMode, Object, Propval, Textdump, Verb, Verbdef}; +use moor_values::Associative; pub struct TextdumpWriter { writer: W, @@ -61,8 +61,8 @@ impl TextdumpWriter { EncodingMode::ISO8859_1 => { // let encoding = encoding_rs::WINDOWS_1252; - let s = s.as_str(); - let s = encoding.encode(s); + let s = s.as_string(); + let s = encoding.encode(&s); let written = self.writer.write(&s.0).unwrap(); assert_eq!(written, s.0.len()); } @@ -84,8 +84,8 @@ impl TextdumpWriter { Variant::Map(m) => { writeln!(self.writer, "{}\n{}", VarType::TYPE_MAP as i64, m.len())?; for (k, v) in m.iter() { - self.write_var(k, false)?; - self.write_var(v, false)?; + self.write_var(&k, false)?; + self.write_var(&v, false)?; } } Variant::None => { diff --git a/crates/kernel/src/textdump/write_db.rs b/crates/kernel/src/textdump/write_db.rs index 7739133f..e304b2f8 100644 --- a/crates/kernel/src/textdump/write_db.rs +++ b/crates/kernel/src/textdump/write_db.rs @@ -20,8 +20,8 @@ use moor_values::model::{ArgSpec, PrepSpec, ValSet, VerbArgsSpec}; use moor_values::model::{BinaryType, VerbFlag}; use moor_values::model::{HasUuid, Named}; use moor_values::util::BitEnum; -use moor_values::var::v_none; -use moor_values::var::Objid; +use moor_values::v_none; +use moor_values::Objid; use moor_values::{AsByteBuffer, NOTHING}; use crate::textdump::{ diff --git a/crates/kernel/src/vm/activation.rs b/crates/kernel/src/vm/activation.rs index 6d4e45c8..ce3e033f 100644 --- a/crates/kernel/src/vm/activation.rs +++ b/crates/kernel/src/vm/activation.rs @@ -25,11 +25,11 @@ use moor_values::model::VerbDef; use moor_values::model::VerbInfo; use moor_values::model::{BinaryType, VerbFlag}; use moor_values::util::BitEnum; -use moor_values::var::Objid; -use moor_values::var::Symbol; -use moor_values::var::{v_empty_list, v_int, v_objid, v_str, v_string, Var, VarType}; -use moor_values::var::{v_empty_str, Error, List, Variant}; +use moor_values::Symbol; use moor_values::NOTHING; +use moor_values::{v_empty_list, v_int, v_objid, v_str, v_string, Var, VarType}; +use moor_values::{v_empty_str, Error}; +use moor_values::{v_list, Objid}; use crate::tasks::command_parse::ParsedCommand; use crate::vm::moo_frame::MooStackFrame; @@ -52,7 +52,7 @@ pub(crate) struct Activation { /// The object that is the 'player' role; that is, the active user of this task. pub(crate) player: Objid, /// The arguments to the verb or bf being called. - pub(crate) args: List, + pub(crate) args: Vec, /// The name of the verb that is currently being executed. pub(crate) verb_name: Symbol, /// The extended information about the verb that is currently being executed. @@ -169,10 +169,7 @@ impl Activation { GlobalName::verb, v_str(verb_call_request.call.verb_name.as_str()), ); - frame.set_global_variable( - GlobalName::args, - Var::new(Variant::List(verb_call_request.call.args.clone())), - ); + frame.set_global_variable(GlobalName::args, v_list(&verb_call_request.call.args)); // From the command, if any... if let Some(ref command) = verb_call_request.command { @@ -263,7 +260,7 @@ impl Activation { verb_info, verb_name: *EVAL_SYMBOL, command: None, - args: List::new(), + args: vec![], permissions, } } @@ -271,7 +268,7 @@ impl Activation { pub fn for_bf_call( bf_id: BuiltinId, bf_name: Symbol, - args: List, + args: Vec, _verb_flags: BitEnum, player: Objid, ) -> Self { diff --git a/crates/kernel/src/vm/exec_state.rs b/crates/kernel/src/vm/exec_state.rs index 5996c59a..0f688a0e 100644 --- a/crates/kernel/src/vm/exec_state.rs +++ b/crates/kernel/src/vm/exec_state.rs @@ -17,9 +17,9 @@ use std::time::{Duration, SystemTime}; use bincode::{Decode, Encode}; use daumtils::PhantomUnsync; -use moor_values::var::Var; -use moor_values::var::{Objid, Symbol}; +use moor_values::Var; use moor_values::NOTHING; +use moor_values::{Objid, Symbol}; use crate::vm::activation::{Activation, Frame}; use moor_values::tasks::TaskId; diff --git a/crates/kernel/src/vm/mod.rs b/crates/kernel/src/vm/mod.rs index 486808c6..f300e917 100644 --- a/crates/kernel/src/vm/mod.rs +++ b/crates/kernel/src/vm/mod.rs @@ -26,7 +26,7 @@ pub use exec_state::VMExecState; use moor_compiler::{BuiltinId, Name}; use moor_compiler::{Offset, Program}; use moor_values::model::VerbInfo; -use moor_values::var::{Objid, Var}; +use moor_values::{Objid, Var}; pub use vm_call::VerbExecutionRequest; pub use vm_unwind::FinallyReason; diff --git a/crates/kernel/src/vm/moo_execute.rs b/crates/kernel/src/vm/moo_execute.rs index 35d7da85..390b3720 100644 --- a/crates/kernel/src/vm/moo_execute.rs +++ b/crates/kernel/src/vm/moo_execute.rs @@ -17,21 +17,22 @@ use std::time::Duration; use tracing::debug; -use moor_compiler::{Op, ScatterLabel}; -use moor_values::model::WorldState; -use moor_values::model::WorldStateError; -use moor_values::var::Error::{E_ARGS, E_DIV, E_INVARG, E_INVIND, E_RANGE, E_TYPE, E_VARNF}; -use moor_values::var::Symbol; -use moor_values::var::Variant; -use moor_values::var::{v_bool, v_empty_list, v_err, v_int, v_list, v_none, v_obj, v_objid, Var}; -use moor_values::var::{v_empty_map, v_float}; -use moor_values::var::{v_listv, Error}; - 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_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::Variant; +use moor_values::{ + v_bool, v_empty_list, v_empty_map, v_err, v_float, v_int, v_list, v_none, v_obj, v_objid, + IndexMode, Sequence, +}; +use moor_values::{Symbol, VarType}; macro_rules! binary_bool_op { ( $f:ident, $op:tt ) => { @@ -57,18 +58,6 @@ macro_rules! binary_var_op { }; } -#[inline] -pub(crate) fn one_to_zero_index(v: &Var) -> Result { - let Variant::Int(index) = v.variant() else { - return Err(E_TYPE); - }; - let index = index - 1; - if index < 0 { - return Err(E_RANGE); - } - Ok(index as usize) -} - /// Main VM opcode execution for MOO stack frames. The actual meat of the MOO virtual machine. pub fn moo_frame_execute( exec_params: &VmExecParams, @@ -201,7 +190,7 @@ pub fn moo_frame_execute( // Track iteration count for range; set id to current list element for the count, // then increment the count, rewind the program counter to the top of the loop, and // continue. - f.set_env(id, l.get(count).unwrap().clone()); + f.set_env(id, l.index(count).unwrap().clone()); f.poke(0, v_int((count + 1) as i64)); } Op::ForRange { @@ -299,62 +288,51 @@ pub fn moo_frame_execute( Op::ImmEmptyList => f.push(v_empty_list()), Op::ListAddTail => { let (tail, list) = (f.pop(), f.peek_top_mut()); - let Variant::List(ref mut list) = list.variant_mut() else { + if list.type_code() != VarType::TYPE_LIST { f.pop(); return state.push_error(E_TYPE); - }; - + } // TODO: quota check SVO_MAX_LIST_CONCAT -> E_QUOTA in list add and append - let result = list.push(tail); - f.poke(0, result); + let result = list.push(&tail); + match result { + Ok(v) => { + f.poke(0, v); + } + Err(e) => { + f.pop(); + return state.push_error(e); + } + } } Op::ListAppend => { let (tail, list) = (f.pop(), f.peek_top_mut()); - let Variant::List(list) = list.variant_mut() else { + // Don't allow strings here. + if tail.type_code() != list.type_code() || list.type_code() != VarType::TYPE_LIST { f.pop(); - - return state.push_error(E_TYPE); - }; - - let Variant::List(tail) = tail.take_variant() else { - f.pop(); - return state.push_error(E_TYPE); - }; - + } let new_list = list.append(&tail); - f.poke(0, new_list); + match new_list { + Ok(v) => { + f.poke(0, v); + } + Err(e) => { + f.pop(); + return state.push_error(e); + } + } } Op::IndexSet => { let (rhs, index, lhs) = (f.pop(), f.pop(), f.peek_top_mut()); - match lhs.variant() { - Variant::Map(m) => { - if matches!(index.variant(), Variant::Map(_) | Variant::List(_)) { - f.pop(); - return state.push_error(E_TYPE); - } else { - let nmap = m.insert(index, rhs.clone()); - f.poke(0, nmap); - } + let result = lhs.index_set(&index, &rhs, IndexMode::OneBased); + match result { + Ok(v) => { + f.poke(0, v); } - _ => { - let i = match one_to_zero_index(&index) { - Ok(i) => i, - Err(e) => { - f.pop(); - return state.push_error(e); - } - }; - match lhs.index_set(i, rhs) { - Ok(v) => { - f.poke(0, v); - } - Err(e) => { - f.pop(); - return state.push_error(e); - } - } + Err(e) => { + f.pop(); + return state.push_error(e); } } } @@ -367,16 +345,16 @@ pub fn moo_frame_execute( } Op::MapInsert => { let (value, key, map) = (f.pop(), f.pop(), f.peek_top_mut()); - if matches!(key.variant(), Variant::Map(_) | Variant::List(_)) { - f.pop(); - return state.push_error(E_TYPE); + let result = map.index_set(&key, &value, IndexMode::OneBased); + match result { + Ok(v) => { + f.poke(0, v); + } + Err(e) => { + f.pop(); + return state.push_error(e); + } } - let Variant::Map(map) = map.variant_mut() else { - f.pop(); - return state.push_error(E_TYPE); - }; - let result = map.insert(key, value); - f.poke(0, result); } Op::PutTemp => { f.temp = f.peek_top().clone(); @@ -406,12 +384,16 @@ pub fn moo_frame_execute( } Op::In => { let (lhs, rhs) = (f.pop(), f.peek_top()); - let r = lhs.index_in(rhs); - if let Variant::Err(e) = r.variant() { - f.pop(); - return state.push_error(*e); + let r = lhs.index_in(rhs, false, IndexMode::OneBased); + match r { + Ok(v) => { + f.poke(0, v); + } + Err(e) => { + f.pop(); + return state.push_error(e); + } } - f.poke(0, r); } Op::Mul => { binary_var_op!(self, f, state, mul); @@ -484,114 +466,49 @@ pub fn moo_frame_execute( } Op::PushRef => { let (index, value) = f.peek2(); - - match value.variant() { - Variant::Map(m) => match m.get(index) { - Some(v) => { - f.push(v.clone()); - } - None => { - f.pop(); - return state.push_error(E_RANGE); - } - }, - _ => { - let index = match one_to_zero_index(index) { - Ok(i) => i, - Err(e) => return state.push_error(e), - }; - match value.index(index) { - Err(e) => return state.push_error(e), - Ok(v) => f.push(v), - } + let result = value.index(index, IndexMode::OneBased); + match result { + Ok(v) => f.push(v), + Err(e) => { + f.pop(); + return state.push_error(e); } } } Op::Ref => { let (index, value) = (f.pop(), f.peek_top()); - match value.variant() { - Variant::Map(m) => { - let v = match m.get(&index) { - Some(v) => v.clone(), - None => { - f.pop(); - return state.push_error(E_RANGE); - } - }; - f.poke(0, v); - } - _ => { - let index = match one_to_zero_index(&index) { - Ok(i) => i, - Err(e) => { - f.pop(); - return state.push_error(e); - } - }; - - match value.index(index) { - Err(e) => { - f.pop(); - return state.push_error(e); - } - Ok(v) => f.poke(0, v), - } + let result = value.index(&index, IndexMode::OneBased); + match result { + Ok(v) => f.poke(0, v), + Err(e) => { + f.pop(); + return state.push_error(e); } } } Op::RangeRef => { let (to, from, base) = (f.pop(), f.pop(), f.peek_top()); - match base.variant() { - Variant::Map(m) => { - let r = m.range(from, to); - f.poke(0, r); - } - _ => { - match (to.variant(), from.variant()) { - (Variant::Int(to), Variant::Int(from)) => { - match base.range(*from, *to) { - Err(e) => { - f.pop(); - return state.push_error(e); - } - Ok(v) => f.poke(0, v), - } - } - (_, _) => return state.push_error(E_TYPE), - }; - } + let result = base.range(&from, &to, IndexMode::OneBased); + if let Err(e) = result { + f.pop(); + return state.push_error(e); } + f.poke(0, result.unwrap()); } Op::RangeSet => { let (value, to, from, base) = (f.pop(), f.pop(), f.pop(), f.peek_top()); - - match base.variant() { - Variant::Map(_) => { - f.pop(); - return state.push_error(E_TYPE); - } - _ => match (to.variant(), from.variant()) { - (Variant::Int(to), Variant::Int(from)) => { - match base.range_set(value, *from, *to) { - Err(e) => { - f.pop(); - return state.push_error(e); - } - Ok(v) => f.poke(0, v), - } - } - _ => { - f.pop(); - return state.push_error(E_TYPE); - } - }, + let result = base.range_set(&from, &to, &value, IndexMode::OneBased); + if let Err(e) = result { + f.pop(); + return state.push_error(e); } + f.poke(0, result.unwrap()); } Op::Length(offset) => { let v = f.peek_abs(offset.0 as usize); match v.len() { - Ok(l) => f.push(l), + Ok(l) => f.push(v_int(l as i64)), Err(e) => return state.push_error(e), } } @@ -605,7 +522,7 @@ pub fn moo_frame_execute( let Variant::Obj(obj) = obj.variant() else { return state.push_error(E_INVIND); }; - let propname = Symbol::mk_case_insensitive(propname.as_str()); + let propname = Symbol::mk_case_insensitive(&propname.as_string()); let result = world_state.retrieve_property(a.permissions, *obj, propname); match result { Ok(v) => { @@ -629,7 +546,7 @@ pub fn moo_frame_execute( let Variant::Obj(obj) = obj.variant() else { return state.push_error(E_INVIND); }; - let propname = Symbol::mk_case_insensitive(propname.as_str()); + let propname = Symbol::mk_case_insensitive(&propname.as_string()); let result = world_state.retrieve_property(a.permissions, *obj, propname); match result { Ok(v) => { @@ -654,7 +571,7 @@ pub fn moo_frame_execute( } }; - let propname = Symbol::mk_case_insensitive(propname.as_str()); + let propname = Symbol::mk_case_insensitive(&propname.as_string()); let update_result = world_state.update_property(a.permissions, *obj, propname, &rhs.clone()); @@ -711,7 +628,7 @@ pub fn moo_frame_execute( return state.push_error(E_TYPE); } }; - let verb = Symbol::mk_case_insensitive(verb.as_str()); + let verb = Symbol::mk_case_insensitive(&verb.as_string()); return state.prepare_call_verb(world_state, *obj, verb, args.clone()); } Op::Return => { @@ -732,7 +649,7 @@ pub fn moo_frame_execute( }; return state.call_builtin_function( *id, - args.clone(), + args.iter().collect(), exec_params, world_state, session, @@ -896,7 +813,7 @@ pub fn moo_frame_execute( }; v.push(rest.clone()); } - let rest = v_listv(v); + let rest = v_list(&v); f.set_env(id, rest); } ScatterLabel::Required(id) => { diff --git a/crates/kernel/src/vm/moo_frame.rs b/crates/kernel/src/vm/moo_frame.rs index ce17c4d9..fa570e36 100644 --- a/crates/kernel/src/vm/moo_frame.rs +++ b/crates/kernel/src/vm/moo_frame.rs @@ -21,8 +21,8 @@ use daumtils::{BitArray, Bitset16}; use crate::vm::FinallyReason; use moor_compiler::Name; use moor_compiler::{GlobalName, Label, Op, Program}; -use moor_values::var::Error::E_VARNF; -use moor_values::var::{v_none, Error, Var}; +use moor_values::Error::E_VARNF; +use moor_values::{v_none, Error, Var}; /// The MOO stack-frame specific portions of the activation: /// the value stack, local variables, program, program counter, handler stack, etc. diff --git a/crates/kernel/src/vm/vm_call.rs b/crates/kernel/src/vm/vm_call.rs index fe122cc7..52300062 100644 --- a/crates/kernel/src/vm/vm_call.rs +++ b/crates/kernel/src/vm/vm_call.rs @@ -16,14 +16,14 @@ use std::sync::Arc; use tracing::trace; -use moor_compiler::{BuiltinId, Program, BUILTINS}; +use moor_compiler::{to_literal, BuiltinId, Program, BUILTINS}; use moor_values::model::VerbInfo; use moor_values::model::WorldState; use moor_values::model::WorldStateError; -use moor_values::var::v_int; -use moor_values::var::Error::{E_INVIND, E_PERM, E_VERBNF}; -use moor_values::var::Symbol; -use moor_values::var::{List, Objid}; +use moor_values::Error::{E_INVIND, E_PERM, E_VERBNF}; +use moor_values::Symbol; +use moor_values::{v_int, Var}; +use moor_values::{List, Objid}; use crate::builtins::{BfCallState, BfErr, BfRet}; use crate::tasks::command_parse::ParsedCommand; @@ -34,9 +34,9 @@ use crate::vm::vm_unwind::FinallyReason; use crate::vm::{ExecutionResult, Fork}; use crate::vm::{VMExecState, VmExecParams}; -pub(crate) fn args_literal(args: &List) -> String { +pub(crate) fn args_literal(args: &[Var]) -> String { args.iter() - .map(|v| v.to_literal()) + .map(to_literal) .collect::>() .join(", ") } @@ -79,7 +79,7 @@ impl VMExecState { location: this, this, player: self.top().player, - args, + args: args.iter().collect(), // caller her is current-activation 'this', not activation caller() ... // unless we're a builtin, in which case we're #-1. argstr: "".to_string(), @@ -163,7 +163,7 @@ impl VMExecState { location: parent, this: self.top().this, player: self.top().player, - args: args.clone(), + args: args.iter().collect(), argstr: "".to_string(), caller, }; @@ -223,7 +223,7 @@ impl VMExecState { pub(crate) fn call_builtin_function( &mut self, bf_id: BuiltinId, - args: List, + args: Vec, exec_args: &VmExecParams, world_state: &mut dyn WorldState, session: Arc, @@ -255,7 +255,7 @@ impl VMExecState { world_state, session: session.clone(), // TODO: avoid copy here by using List inside BfCallState - args: args.iter().collect(), + args, task_scheduler_client: exec_args.task_scheduler_client.clone(), config: exec_args.config.clone(), }; @@ -304,7 +304,7 @@ impl VMExecState { world_state, session: sessions, // TODO: avoid copy here by using List inside BfCallState - args: args.iter().collect(), + args, task_scheduler_client: exec_args.task_scheduler_client.clone(), config: exec_args.config.clone(), }; diff --git a/crates/kernel/src/vm/vm_test.rs b/crates/kernel/src/vm/vm_test.rs index cc2d4b40..d4cf526a 100644 --- a/crates/kernel/src/vm/vm_test.rs +++ b/crates/kernel/src/vm/vm_test.rs @@ -21,11 +21,11 @@ mod tests { use moor_values::model::{BinaryType, VerbFlag}; use moor_values::model::{WorldState, WorldStateSource}; use moor_values::util::BitEnum; - use moor_values::var::Error::E_DIV; - use moor_values::var::{ - v_bool, v_empty_list, v_err, v_int, v_list, v_none, v_obj, v_objid, v_str, Var, + use moor_values::Error::E_DIV; + use moor_values::Objid; + 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, }; - use moor_values::var::{v_map_pairs, Objid}; use moor_values::NOTHING; use moor_values::{AsByteBuffer, SYSTEM_OBJECT}; @@ -39,7 +39,7 @@ mod tests { use moor_compiler::{compile, UnboundNames}; use moor_compiler::{CompileOptions, Names}; use moor_db_wiredtiger::WiredTigerDB; - use moor_values::var::Symbol; + use moor_values::Symbol; use test_case::test_case; fn mk_program(main_vector: Vec, literals: Vec, var_names: Names) -> Program { @@ -1126,11 +1126,11 @@ mod tests { #[test_case("return -9223372036854775808;", v_int(i64::MIN); "minint")] #[test_case("return [ 1 -> 2][1];", v_int(2); "map index")] #[test_case("return [ 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 4][1..3];", - v_map_pairs(&[(v_int(1),v_int(2)), (v_int(2),v_int(3))]); "map range")] + v_map(&[(v_int(1),v_int(2)), (v_int(2),v_int(3))]); "map range")] #[test_case(r#"m = [ 1 -> "one", 2 -> "two", 3 -> "three" ]; m[1] = "abc"; return m;"#, - v_map_pairs(&[(v_int(1),v_str("abc")), (v_int(2),v_str("two")), (v_int(3),v_str("three"))]); "map assignment")] + v_map(&[(v_int(1),v_str("abc")), (v_int(2),v_str("two")), (v_int(3),v_str("three"))]); "map assignment")] #[test_case("return [ 0 -> 1, 1 -> 2, 2 -> 3, 3 -> 4][1..$];", - v_map_pairs(&[(v_int(1),v_int(2)), (v_int(2),v_int(3),), (v_int(3),v_int(4))]); "map range to end")] + v_map(&[(v_int(1),v_int(2)), (v_int(2),v_int(3),), (v_int(3),v_int(4))]); "map range to end")] #[test_case("l = {1,2,3}; l[2..3] = {6, 7, 8, 9}; return l;", v_list(&[v_int(1), v_int(6), v_int(7), v_int(8), v_int(9)]); "list assignment to range")] fn test_run(program: &str, expected_result: Var) { @@ -1145,4 +1145,22 @@ mod tests { ); assert_eq!(result, Ok(expected_result)); } + + #[test] + fn test_list_assignment_to_range() { + let program = r#"l = {1,2,3}; l[2..3] = {6, 7, 8, 9}; return l;"#; + 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, + Ok(v_list(&[v_int(1), v_int(6), v_int(7), v_int(8), v_int(9)])) + ); + } } diff --git a/crates/kernel/src/vm/vm_unwind.rs b/crates/kernel/src/vm/vm_unwind.rs index c7ce468e..397ad1ff 100644 --- a/crates/kernel/src/vm/vm_unwind.rs +++ b/crates/kernel/src/vm/vm_unwind.rs @@ -17,10 +17,9 @@ use moor_compiler::{Label, Offset, BUILTINS}; use moor_values::model::Named; use moor_values::model::VerbFlag; use moor_values::tasks::Exception; -use moor_values::var::v_listv; -use moor_values::var::{v_err, v_int, v_list, v_none, v_objid, v_str, Var}; -use moor_values::var::{Error, ErrorPack}; use moor_values::NOTHING; +use moor_values::{v_err, v_int, v_list, v_none, v_objid, v_str, Var}; +use moor_values::{Error, ErrorPack}; use tracing::trace; use crate::vm::activation::{Activation, Frame}; @@ -74,7 +73,7 @@ impl VMExecState { } }; - stack_list.push(v_listv(traceback_entry)); + stack_list.push(v_list(&traceback_entry)); } stack_list } diff --git a/crates/kernel/tests/textdump.rs b/crates/kernel/tests/textdump.rs index 11025c0f..0e70b6d7 100644 --- a/crates/kernel/tests/textdump.rs +++ b/crates/kernel/tests/textdump.rs @@ -34,8 +34,8 @@ mod test { use moor_values::model::WorldStateSource; use moor_values::model::{CommitResult, ValSet}; use moor_values::model::{HasUuid, Named}; - use moor_values::var::Objid; - use moor_values::var::Symbol; + use moor_values::Objid; + use moor_values::Symbol; use moor_values::{AsByteBuffer, SYSTEM_OBJECT}; fn get_minimal_db() -> File { diff --git a/crates/kernel/testsuite/common/mod.rs b/crates/kernel/testsuite/common/mod.rs index 0f413a21..64397dbd 100644 --- a/crates/kernel/testsuite/common/mod.rs +++ b/crates/kernel/testsuite/common/mod.rs @@ -37,8 +37,8 @@ use moor_values::model::Named; use moor_values::model::VerbArgsSpec; use moor_values::model::WorldStateSource; use moor_values::model::{BinaryType, VerbFlag}; -use moor_values::var::Objid; -use moor_values::var::Symbol; +use moor_values::Objid; +use moor_values::Symbol; use moor_values::{AsByteBuffer, SYSTEM_OBJECT}; #[allow(dead_code)] diff --git a/crates/kernel/testsuite/moot/map.moot b/crates/kernel/testsuite/moot/map.moot index 09e908fb..63d646b3 100644 --- a/crates/kernel/testsuite/moot/map.moot +++ b/crates/kernel/testsuite/moot/map.moot @@ -127,8 +127,10 @@ "[map]" // Note: item order is different here than in Stunt (but stable). That's *probably* fine. // Seems like ordering difference between different key types. +// Note that stunt's is: +// ""[5 -> 5, #-1 -> #-1, 3.14 -> 3.14, "1" -> {}, "2" -> []]"" ; return toliteral($tmp); -"[#-1 -> #-1, \"1\" -> {}, \"2\" -> [], 5 -> 5, 3.14 -> 3.14]" +"[#-1 -> #-1, 5 -> 5, 3.14 -> 3.14, \"1\" -> {}, \"2\" -> []]" // test_that_assignment_copies ; x = [$nothing -> $nothing, "2" -> [], "1" -> {}, 5 -> 5, 3.14 -> 3.14]; y = x; return x == y && "yes" || "no"; diff --git a/crates/kernel/testsuite/moot_suite.rs b/crates/kernel/testsuite/moot_suite.rs index 4bfe76c2..6183c077 100644 --- a/crates/kernel/testsuite/moot_suite.rs +++ b/crates/kernel/testsuite/moot_suite.rs @@ -23,6 +23,7 @@ use eyre::Context; #[cfg(feature = "relbox")] use common::create_relbox_db; use common::{create_wiredtiger_db, testsuite_dir}; +use moor_compiler::to_literal; use moor_db::Database; use moor_kernel::tasks::sessions::{SessionError, SessionFactory}; use moor_kernel::tasks::NoopTasksDb; @@ -36,7 +37,7 @@ use moor_kernel::{ SchedulerClient, }; use moor_moot::{execute_moot_test, MootRunner}; -use moor_values::var::{v_none, Objid, Var}; +use moor_values::{v_none, Objid, Var}; mod common; @@ -102,11 +103,11 @@ impl MootRunner for SchedulerMootRunner { unimplemented!("Not supported on SchedulerMootRunner"); } - fn read_eval_result(&mut self, player: Objid) -> eyre::Result> { + fn read_eval_result(&mut self, player: Objid) -> eyre::Result> { Ok(self .eval_result .take() - .inspect(|var| eprintln!("{player} << {var}"))) + .inspect(|var| eprintln!("{player} << {}", to_literal(var)))) } } diff --git a/crates/moot/src/lib.rs b/crates/moot/src/lib.rs index 5ce0e73a..11d386ff 100644 --- a/crates/moot/src/lib.rs +++ b/crates/moot/src/lib.rs @@ -25,7 +25,7 @@ use std::{ }; use eyre::{eyre, ContextCompat, WrapErr}; -use moor_values::var::Objid; +use moor_values::Objid; use pretty_assertions::assert_eq; diff --git a/crates/rpc-common/src/lib.rs b/crates/rpc-common/src/lib.rs index 18133bb3..4b86a0a2 100644 --- a/crates/rpc-common/src/lib.rs +++ b/crates/rpc-common/src/lib.rs @@ -14,8 +14,8 @@ use bincode::{Decode, Encode}; use moor_values::tasks::{NarrativeEvent, SchedulerError}; -use moor_values::var::Objid; -use moor_values::var::Var; +use moor_values::Objid; +use moor_values::Var; use std::time::SystemTime; use thiserror::Error; diff --git a/crates/telnet-host/Cargo.toml b/crates/telnet-host/Cargo.toml index f1303b08..27dc032a 100644 --- a/crates/telnet-host/Cargo.toml +++ b/crates/telnet-host/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true description = "A server which presents a classic LambdaMOO-style line-based TCP interface for interacting with a moor daemon." [dependencies] +moor-compiler = { path = "../compiler" } moor-moot = { path = "../moot" } moor-values = { path = "../values" } rpc-async-client = { path = "../rpc-async-client" } diff --git a/crates/telnet-host/src/telnet.rs b/crates/telnet-host/src/telnet.rs index 993e6f4b..bb6fb206 100644 --- a/crates/telnet-host/src/telnet.rs +++ b/crates/telnet-host/src/telnet.rs @@ -22,18 +22,10 @@ use eyre::Context; use futures_util::stream::{SplitSink, SplitStream}; use futures_util::SinkExt; use futures_util::StreamExt; -use termimad::MadSkin; -use tmq::subscribe::Subscribe; -use tmq::{request, subscribe}; -use tokio::net::{TcpListener, TcpStream}; -use tokio::select; -use tokio_util::codec::{Framed, LinesCodec}; -use tracing::{debug, error, info, trace, warn}; -use uuid::Uuid; - +use moor_compiler::to_literal; use moor_values::tasks::{AbortLimitReason, CommandError, Event, SchedulerError, VerbProgramError}; use moor_values::util::parse_into_words; -use moor_values::var::{Objid, Symbol, Variant}; +use moor_values::{Objid, Symbol, Variant}; use rpc_async_client::pubsub_client::{broadcast_recv, events_recv}; use rpc_async_client::rpc_client::RpcSendClient; use rpc_common::RpcRequest::ConnectionEstablish; @@ -42,6 +34,14 @@ use rpc_common::{ RpcResult, BROADCAST_TOPIC, }; use rpc_common::{RpcRequest, RpcResponse}; +use termimad::MadSkin; +use tmq::subscribe::Subscribe; +use tmq::{request, subscribe}; +use tokio::net::{TcpListener, TcpStream}; +use tokio::select; +use tokio_util::codec::{Framed, LinesCodec}; +use tracing::{debug, error, info, trace, warn}; +use uuid::Uuid; /// Out of band messages are prefixed with this string, e.g. for MCP clients. const OUT_OF_BAND_PREFIX: &str = "#$#"; @@ -124,7 +124,7 @@ impl TelnetConnection { // literal form (for e.g. lists, objrefs, etc) match msg.variant() { Variant::Str(msg_text) => { - let formatted = output_format(msg_text.as_str(), content_type); + let formatted = output_format(&msg_text.as_string(), content_type); self.write .send(formatted) .await @@ -136,7 +136,7 @@ impl TelnetConnection { trace!("Non-string in list output"); continue; }; - let formatted = output_format(line.as_str(), content_type); + let formatted = output_format(&line.as_string(), content_type); self.write .send(formatted) .await @@ -145,7 +145,7 @@ impl TelnetConnection { } _ => { self.write - .send(msg.to_literal()) + .send(to_literal(&msg)) .await .with_context(|| "Unable to send message to client")?; } diff --git a/crates/values/Cargo.toml b/crates/values/Cargo.toml index bd055ced..f10b834b 100644 --- a/crates/values/Cargo.toml +++ b/crates/values/Cargo.toml @@ -18,6 +18,8 @@ bytes.workspace = true daumtils.workspace = true decorum.workspace = true enum-primitive-derive.workspace = true +flatbuffers.workspace = true +flexbuffers.workspace = true im.workspace = true itertools.workspace = true lazy_static.workspace = true diff --git a/crates/values/src/lib.rs b/crates/values/src/lib.rs index ddfed29d..cc5dd796 100644 --- a/crates/values/src/lib.rs +++ b/crates/values/src/lib.rs @@ -19,13 +19,18 @@ pub use encode::{ BINCODE_CONFIG, }; -use crate::var::Objid; +pub use var::{ + v_bool, v_empty_list, v_empty_map, v_empty_str, v_err, v_float, v_floatr, 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, +}; +pub use var::{Error, Objid, Symbol, VarType}; mod encode; pub mod model; pub mod tasks; pub mod util; -pub mod var; +mod var; /// When encoding or decoding types to/from data or network, this is a version tag put into headers /// for validity / version checking. diff --git a/crates/values/src/model/defset.rs b/crates/values/src/model/defset.rs index 10f1e3a1..639e1c9d 100644 --- a/crates/values/src/model/defset.rs +++ b/crates/values/src/model/defset.rs @@ -14,8 +14,8 @@ use crate::encode::{DecodingError, EncodingError}; use crate::model::ValSet; -use crate::var::Symbol; use crate::AsByteBuffer; +use crate::Symbol; use bytes::BufMut; use bytes::Bytes; use itertools::Itertools; diff --git a/crates/values/src/model/mod.rs b/crates/values/src/model/mod.rs index e8c58c4b..a5d409ff 100644 --- a/crates/values/src/model/mod.rs +++ b/crates/values/src/model/mod.rs @@ -41,7 +41,7 @@ mod verbdef; mod verbs; mod world_state; -use crate::var::Symbol; +use crate::Symbol; pub use world_state::WorldStateError; /// The result code from a commit/complete operation on the world's state. diff --git a/crates/values/src/model/objects.rs b/crates/values/src/model/objects.rs index 8a48ace5..aca88cb5 100644 --- a/crates/values/src/model/objects.rs +++ b/crates/values/src/model/objects.rs @@ -21,7 +21,7 @@ use bytes::Bytes; use enum_primitive_derive::Primitive; use crate::util::BitEnum; -use crate::var::Objid; +use crate::Objid; #[derive(Debug, Ord, PartialOrd, Copy, Clone, Eq, PartialEq, Hash, Primitive, Encode, Decode)] pub enum ObjFlag { diff --git a/crates/values/src/model/objset.rs b/crates/values/src/model/objset.rs index 21fb5898..57efeff6 100644 --- a/crates/values/src/model/objset.rs +++ b/crates/values/src/model/objset.rs @@ -14,8 +14,8 @@ use crate::encode::{DecodingError, EncodingError}; use crate::model::ValSet; -use crate::var::Objid; use crate::AsByteBuffer; +use crate::Objid; use bytes::BufMut; use bytes::Bytes; use itertools::Itertools; diff --git a/crates/values/src/model/permissions.rs b/crates/values/src/model/permissions.rs index a649f790..19ca2a42 100644 --- a/crates/values/src/model/permissions.rs +++ b/crates/values/src/model/permissions.rs @@ -17,7 +17,7 @@ use crate::model::props::PropFlag; use crate::model::verbs::VerbFlag; use crate::model::{PropPerms, WorldStateError}; use crate::util::BitEnum; -use crate::var::Objid; +use crate::Objid; /// Combination of who a set of permissions is for, and what permissions they have. #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/crates/values/src/model/propdef.rs b/crates/values/src/model/propdef.rs index dccec959..658beb62 100644 --- a/crates/values/src/model/propdef.rs +++ b/crates/values/src/model/propdef.rs @@ -14,8 +14,8 @@ use crate::encode::{DecodingError, EncodingError}; use crate::model::defset::{Defs, HasUuid, Named}; -use crate::var::Objid; -use crate::var::Symbol; +use crate::Objid; +use crate::Symbol; use crate::{AsByteBuffer, DATA_LAYOUT_VERSION}; use binary_layout::{binary_layout, Field}; use bytes::Bytes; @@ -144,9 +144,9 @@ mod tests { use crate::model::defset::HasUuid; use crate::model::propdef::{PropDef, PropDefs}; use crate::model::ValSet; - use crate::var::Objid; - use crate::var::Symbol; use crate::AsByteBuffer; + use crate::Objid; + use crate::Symbol; use bytes::Bytes; use uuid::Uuid; diff --git a/crates/values/src/model/props.rs b/crates/values/src/model/props.rs index d0429a40..436df83f 100644 --- a/crates/values/src/model/props.rs +++ b/crates/values/src/model/props.rs @@ -13,8 +13,8 @@ // use crate::util::BitEnum; -use crate::var::Objid; -use crate::var::Var; +use crate::Objid; +use crate::Var; use crate::{AsByteBuffer, DecodingError, EncodingError}; use binary_layout::binary_layout; use bincode::{Decode, Encode}; diff --git a/crates/values/src/model/verbdef.rs b/crates/values/src/model/verbdef.rs index 4a725580..eb02670c 100644 --- a/crates/values/src/model/verbdef.rs +++ b/crates/values/src/model/verbdef.rs @@ -18,8 +18,8 @@ use crate::model::r#match::VerbArgsSpec; use crate::model::verbs::{BinaryType, VerbFlag}; use crate::util::verbname_cmp; use crate::util::BitEnum; -use crate::var::Objid; -use crate::var::Symbol; +use crate::Objid; +use crate::Symbol; use crate::{AsByteBuffer, DATA_LAYOUT_VERSION}; use binary_layout::{binary_layout, Field}; use bytes::BufMut; @@ -208,8 +208,8 @@ mod tests { use crate::model::verbs::VerbFlag; use crate::model::ValSet; use crate::util::BitEnum; - use crate::var::Objid; use crate::AsByteBuffer; + use crate::Objid; use bytes::Bytes; #[test] diff --git a/crates/values/src/model/verbs.rs b/crates/values/src/model/verbs.rs index 63137482..9f73e947 100644 --- a/crates/values/src/model/verbs.rs +++ b/crates/values/src/model/verbs.rs @@ -15,8 +15,8 @@ use crate::encode::{DecodingError, EncodingError}; use crate::model::r#match::VerbArgsSpec; use crate::util::BitEnum; -use crate::var::Objid; -use crate::var::Symbol; +use crate::Objid; +use crate::Symbol; use binary_layout::LayoutAs; use bincode::{Decode, Encode}; use enum_primitive_derive::Primitive; diff --git a/crates/values/src/model/world_state.rs b/crates/values/src/model/world_state.rs index 3eb9fd0c..aaa586ef 100644 --- a/crates/values/src/model/world_state.rs +++ b/crates/values/src/model/world_state.rs @@ -27,9 +27,9 @@ use crate::model::verbs::{BinaryType, VerbAttrs, VerbFlag}; use crate::model::{CommitResult, PropPerms}; use crate::model::{ObjAttr, Vid}; use crate::util::BitEnum; -use crate::var::Symbol; -use crate::var::Var; -use crate::var::{Error, Objid}; +use crate::Symbol; +use crate::Var; +use crate::{Error, Objid}; /// Errors related to the world state and operations on it. #[derive(Error, Debug, Eq, PartialEq, Clone, Decode, Encode)] diff --git a/crates/values/src/tasks/errors.rs b/crates/values/src/tasks/errors.rs index ebeb2771..5f9ddcd3 100644 --- a/crates/values/src/tasks/errors.rs +++ b/crates/values/src/tasks/errors.rs @@ -14,7 +14,7 @@ use crate::model::{CompileError, WorldStateError}; use crate::tasks::TaskId; -use crate::var::{Error, Var}; +use crate::{Error, Var}; use bincode::{Decode, Encode}; use std::fmt::Display; use std::time::Duration; diff --git a/crates/values/src/tasks/events.rs b/crates/values/src/tasks/events.rs index 8ee36c36..b3c5bc3d 100644 --- a/crates/values/src/tasks/events.rs +++ b/crates/values/src/tasks/events.rs @@ -12,7 +12,7 @@ // this program. If not, see . // -use crate::var::{Objid, Symbol, Var}; +use crate::{Objid, Symbol, Var}; use bincode::{Decode, Encode}; use std::time::SystemTime; diff --git a/crates/values/src/var/list.rs b/crates/values/src/var/list.rs index 220eea81..1844a4bb 100644 --- a/crates/values/src/var/list.rs +++ b/crates/values/src/var/list.rs @@ -12,146 +12,207 @@ // this program. If not, see . // -use std::fmt::{Display, Formatter, Result as FmtResult}; -use std::hash::{Hash, Hasher}; - -use bincode::{Decode, Encode}; -use bytes::Bytes; - -#[allow(unused_imports)] -use crate::var::list_impl_buffer::ListImplBuffer; -#[allow(unused_imports)] -use crate::var::list_impl_vector::ListImplVector; - +use crate::v_list_iter; +use crate::var::storage::VarBuffer; +use crate::var::var::Var; use crate::var::variant::Variant; -use crate::var::Var; -use crate::{AsByteBuffer, DecodingError, EncodingError}; - -#[cfg(feature = "list_impl_buffer")] -type ListImpl = ListImplBuffer; - -#[cfg(not(feature = "list_impl_buffer"))] -type ListImpl = ListImplVector; - -#[derive(Clone, Debug, Encode, Decode)] -pub struct List(ListImpl); +use crate::var::Error::E_RANGE; +use crate::var::Sequence; +use crate::var::{Error, VarType}; +use bytes::Bytes; +use flexbuffers::{BuilderOptions, Reader, VectorReader}; +use num_traits::ToPrimitive; +use std::cmp::max; +use std::hash::Hash; + +#[derive(Clone)] +pub struct List { + // Reader must be boxed to avoid overfilling the stack. + pub reader: Box>, +} impl List { - pub fn new() -> Self { - Self(ListImpl::new()) - } - - pub fn len(&self) -> usize { - self.0.len() + pub fn build(values: &[Var]) -> Var { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_LIST as u8); + let mut lv = vb.start_vector(); + for v in values { + let v = v.variant(); + v.push_item(&mut lv); + } + lv.end_vector(); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) } - pub fn is_empty(&self) -> bool { - self.0.is_empty() + pub fn iter(&self) -> impl Iterator + '_ { + (0..self.len()).map(move |i| self.index(i).unwrap()) } - pub fn get(&self, index: usize) -> Option { - self.0.get(index) + /// Remove the first found instance of `item` from the list. + pub fn set_remove(&self, item: &Var) -> Result { + let mut found = false; + let set_removed_iter = self.iter().filter_map(|v| { + if v == *item && !found { + found = true; + None + } else { + Some(v) + } + }); + Ok(v_list_iter(set_removed_iter)) } - pub fn from_slice(vec: &[Var]) -> List { - Self(ListImpl::from_slice(vec)) + /// Add `item` to the list but only if it's not already there. + pub fn set_add(&self, item: &Var) -> Result { + // Is the item already in the list? If so, just clone. + if self.iter().any(|v| v == *item) { + return Ok(Var(Variant::List(self.clone()))); + } + let set_added_iter = self.iter().chain(std::iter::once(item.clone())); + Ok(v_list_iter(set_added_iter)) } - // expensive because we need to extend both buffer length and the offsets length... - pub fn push(&mut self, v: Var) -> Var { - Var::new(Variant::List(Self(self.0.push(v)))) + pub fn pop_front(&self) -> Result<(Var, Var), Error> { + if self.is_empty() { + return Err(E_RANGE); + } + let mut iter = self.iter(); + let first = iter.next().unwrap(); + let rest = v_list_iter(iter); + Ok((first, rest)) } +} - pub fn pop_front(&self) -> (Var, Var) { - let results = self.0.pop_front(); - (results.0, Var::new(Variant::List(Self(results.1)))) +impl Sequence for List { + fn is_empty(&self) -> bool { + self.reader.len() == 0 } - pub fn append(&mut self, other: &Self) -> Var { - Var::new(Variant::List(Self(self.0.append(other.0.clone())))) + fn len(&self) -> usize { + self.reader.len() } - pub fn remove_at(&mut self, index: usize) -> Var { - Var::new(Variant::List(Self(self.0.remove_at(index)))) + fn contains(&self, value: &Var, case_sensitive: bool) -> Result { + for v in self.iter() { + if case_sensitive { + if v.eq_case_sensitive(value) { + return Ok(true); + } + } else if v == *value { + return Ok(true); + } + } + Ok(false) } - - /// Remove the first found instance of the given value from the list. - #[must_use] - pub fn setremove(&mut self, value: &Var) -> Var { - Var::new(Variant::List(Self(self.0.setremove(value)))) + fn index_in(&self, value: &Var, case_sensitive: bool) -> Result, Error> { + for (i, v) in self.iter().enumerate() { + if case_sensitive { + if v.eq_case_sensitive(value) { + return Ok(Some(i)); + } + } else if v == *value { + return Ok(Some(i)); + } + } + Ok(None) } - pub fn insert(&mut self, index: isize, value: Var) -> Var { - Var::new(Variant::List(Self(self.0.insert(index, value)))) + fn index(&self, index: usize) -> Result { + if index >= self.reader.len() { + return Err(E_RANGE); + } + let result = self.reader.index(index).unwrap(); + let v = Variant::from_reader(result); + let v = Var(v); + Ok(v) } - pub fn set(&mut self, index: usize, value: Var) -> Var { - Var::new(Variant::List(Self(self.0.set(index, value)))) + fn index_set(&self, index: usize, value: &Var) -> Result { + if index >= self.reader.len() { + return Err(E_RANGE); + } + let replaced_iter = self + .iter() + .enumerate() + .map(|(i, v)| if i == index { value.clone() } else { v }); + Ok(v_list_iter(replaced_iter)) } - // Case insensitive - pub fn contains(&self, v: &Var) -> bool { - self.iter().any(|item| item.eq(v)) + fn push(&self, value: &Var) -> Result { + let with_added = self.iter().chain(std::iter::once(value.clone())); + Ok(Var::mk_list_iter(with_added)) } - pub fn iter(&self) -> impl Iterator + '_ { - (0..self.len()).map(move |i| self.get(i).unwrap()) + fn insert(&self, index: usize, value: &Var) -> Result { + let inserted_iter = self + .iter() + .take(index) + .chain(std::iter::once(value.clone())) + .chain(self.iter().skip(index)); + Ok(v_list_iter(inserted_iter)) } - pub fn contains_case_sensitive(&self, v: &Var) -> bool { - if let Variant::Str(s) = v.variant() { - for item in self.iter() { - if let Variant::Str(s2) = item.variant() { - if s.as_str() == s2.as_str() { - return true; - } - } - } - return false; + fn range(&self, from: isize, to: isize) -> Result { + let len = self.len() as isize; + if to < from { + return Ok(Var::mk_list(&[])); } - self.contains(v) - } -} - -impl From for Vec { - fn from(value: List) -> Self { - let len = value.len(); - let mut result = Vec::with_capacity(len); - for i in 0..len { - result.push(value.get(i).unwrap()); + if from > len + 1 || to > len { + return Err(E_RANGE); } - result + let (from, to) = (max(from, 0) as usize, to as usize); + let range_iter = self.iter().skip(from).take(to - from + 1); + Ok(Var::mk_list_iter(range_iter)) } -} -impl AsByteBuffer for List { - fn size_bytes(&self) -> usize { - self.0.size_bytes() - } - - fn with_byte_buffer R>(&self, mut f: F) -> Result { - self.0.with_byte_buffer(|buf| f(buf)) + fn range_set(&self, from: isize, to: isize, with: &Var) -> Result { + let with_val = match with.variant() { + Variant::List(s) => s, + _ => return Err(Error::E_TYPE), + }; + + let base_len = self.len(); + let from = from.to_usize().unwrap_or(0); + let to = to.to_usize().unwrap_or(0); + if to + 1 > base_len { + return Err(E_RANGE); + } + // Iterator taking up to `from` + let base_iter = self.iter().take(from); + // Iterator for with_val... + let with_iter = with_val.iter(); + // Iterator from after to, up to the end + let end_iter = self.iter().skip(to + 1); + let new_iter = base_iter.chain(with_iter).chain(end_iter); + Ok(v_list_iter(new_iter)) } - fn make_copy_as_vec(&self) -> Result, EncodingError> { - self.0.make_copy_as_vec() - } + fn append(&self, other: &Var) -> Result { + let other = match other.variant() { + Variant::List(l) => l, + _ => return Err(Error::E_TYPE), + }; - fn from_bytes(bytes: Bytes) -> Result - where - Self: Sized, - { - Ok(Self(ListImpl::from_bytes(bytes)?)) + let combined_iter = self.iter().chain(other.iter()); + Ok(Var::mk_list_iter(combined_iter)) } - fn as_bytes(&self) -> Result { - self.0.as_bytes() - } -} + fn remove_at(&self, index: usize) -> Result { + if index >= self.len() { + return Err(E_RANGE); + } -impl Default for List { - fn default() -> Self { - Self::new() + let new = self + .iter() + .enumerate() + .filter_map(|(i, v)| if i == index { None } else { Some(v) }); + Ok(Var::mk_list_iter(new)) } } @@ -160,23 +221,22 @@ impl PartialEq for List { if self.len() != other.len() { return false; } - for (a, b) in self.iter().zip(other.iter()) { - if !a.eq(&b) { + + // elements comparison + for i in 0..self.len() { + let a = self.index(i).unwrap(); + let b = other.index(i).unwrap(); + if a != b { return false; } } + true } } + impl Eq for List {} -impl Hash for List { - fn hash(&self, state: &mut H) { - for item in self.iter() { - item.hash(state); - } - } -} impl PartialOrd for List { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -185,73 +245,385 @@ impl PartialOrd for List { impl Ord for List { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let len = self.len(); - if len != other.len() { - return len.cmp(&other.len()); + if self.len() != other.len() { + return self.len().cmp(&other.len()); } - for (a, b) in self.iter().zip(other.iter()) { + // elements comparison + for i in 0..self.len() { + let a = self.index(i).unwrap(); + let b = other.index(i).unwrap(); match a.cmp(&b) { std::cmp::Ordering::Equal => continue, x => return x, } } + std::cmp::Ordering::Equal } } -impl From> for List { - fn from(value: Vec) -> Self { - Self::from_slice(&value) +impl Hash for List { + fn hash(&self, state: &mut H) { + for item in self.iter() { + item.hash(state); + } } } -impl From<&[Var]> for List { - fn from(value: &[Var]) -> Self { - Self::from_slice(value) +impl FromIterator for Var { + fn from_iter>(iter: T) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_LIST as u8); + let mut lv = vb.start_vector(); + for v in iter { + let v = v.variant(); + v.push_item(&mut lv); + } + lv.end_vector(); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) } } +#[cfg(test)] +mod tests { + use crate::v_bool; + use crate::var::var::{v_empty_list, v_int, v_list, v_str, Var}; + use crate::var::variant::Variant; + use crate::var::Error; + use crate::var::Error::{E_RANGE, E_TYPE}; + use crate::var::{IndexMode, Sequence}; -impl Display for List { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, "{{")?; - let mut first = true; - for v in self.iter() { - if !first { - write!(f, ", ")?; + #[test] + fn test_list_pack_unpack_index() { + let l = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + + match l.variant() { + Variant::List(l) => { + assert_eq!(l.len(), 3); } - first = false; - write!(f, "{v}")?; + _ => panic!("Expected list, got {:?}", l.variant()), + } + eprintln!("List: {:?}", l.variant()); + let r = l.index(&Var::mk_integer(1), IndexMode::ZeroBased).unwrap(); + let r = r.variant(); + match r { + Variant::Int(i) => assert_eq!(*i, 2), + _ => panic!("Expected integer, got {:?}", r), } - write!(f, "}}") } -} -#[cfg(test)] -mod tests { - use crate::var::list::List; - use crate::var::{v_int, v_list, v_string}; + #[test] + fn test_list_equality_inequality() { + let l1 = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + let l2 = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + let l3 = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(4)]); + let l4 = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2)]); + let l5 = Var::mk_list(&[ + Var::mk_integer(1), + Var::mk_integer(2), + Var::mk_integer(3), + Var::mk_integer(4), + ]); + + assert_eq!(l1, l2); + assert_ne!(l1, l3); + assert_ne!(l1, l4); + assert_ne!(l1, l5); + } #[test] - pub fn weird_moo_insert_scenarios() { - // MOO supports negative indexes, which just floor to 0... - let mut list = List::from_slice(&[v_int(1), v_int(2), v_int(3)]); + fn test_list_is_funcs() { + let l = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + assert!(l.is_true()); + assert!(l.is_sequence()); + assert!(!l.is_associative()); + assert!(!l.is_scalar()); + assert_eq!(l.len().unwrap(), 3); + assert!(!l.is_empty().unwrap()); + + let l = Var::mk_list(&[]); + assert!(!l.is_true()); + assert!(l.is_sequence()); + assert!(!l.is_associative()); + assert!(!l.is_scalar()); + assert_eq!(l.len().unwrap(), 0); + assert!(l.is_empty().unwrap()); + } + + #[test] + fn test_list_index() { + let l = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + let r = l.index(&Var::mk_integer(1), IndexMode::ZeroBased).unwrap(); + let r = r.variant(); + match r { + Variant::Int(i) => assert_eq!(*i, 2), + _ => panic!("Expected integer, got {:?}", r), + } + } + + #[test] + fn test_list_index_set() { + let l = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + let r = l + .index_set( + &Var::mk_integer(1), + &Var::mk_integer(42), + IndexMode::ZeroBased, + ) + .unwrap(); + let r = r.variant(); + match r { + Variant::List(l) => { + let r = l.index(1).unwrap(); + let r = r.variant(); + match r { + Variant::Int(i) => assert_eq!(*i, 42), + _ => panic!("Expected integer, got {:?}", r), + } + } + _ => panic!("Expected list, got {:?}", r), + } + + let fail_bad_index = l.index_set( + &Var::mk_integer(10), + &Var::mk_integer(42), + IndexMode::ZeroBased, + ); + assert!(fail_bad_index.is_err()); + assert_eq!(fail_bad_index.unwrap_err(), crate::var::Error::E_RANGE); + } + + #[test] + fn test_list_set_remove() { + let l = Var::mk_list(&[ + Var::mk_integer(1), + Var::mk_integer(2), + Var::mk_integer(3), + Var::mk_integer(2), + ]); + // Only works on list variants. + let l = match l.variant() { + Variant::List(l) => l, + _ => panic!("Expected list"), + }; + // This will only remove the first instance of 2... + let removed = l.set_remove(&Var::mk_integer(2)).unwrap(); + let removed_v = match removed.variant() { + Variant::List(l) => l, + _ => panic!("Expected list"), + }; + // should now b e [1, 3, 2] + assert_eq!(removed_v.len(), 3); + assert_eq!( + removed, + Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(3), Var::mk_integer(2)]) + ); + } + + #[test] + fn test_list_set_add() { + let l = Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]); + // Only works on list variants. + let l = match l.variant() { + Variant::List(l) => l, + _ => panic!("Expected list"), + }; + // This will only add the first instance of 2... + let added = l.set_add(&Var::mk_integer(2)).unwrap(); + let added_v = match added.variant() { + Variant::List(l) => l, + _ => panic!("Expected list"), + }; + // should still be [1, 2, 3] + assert_eq!(added_v.len(), 3); + assert_eq!( + added, + Var::mk_list(&[Var::mk_integer(1), Var::mk_integer(2), Var::mk_integer(3)]) + ); + + // now add 4 + let added = l.set_add(&Var::mk_integer(4)).unwrap(); + let added_v = match added.variant() { + Variant::List(l) => l, + _ => panic!("Expected list"), + }; + // should now be [1, 2, 3, 4] + assert_eq!(added_v.len(), 4); + assert_eq!( + added, + Var::mk_list(&[ + Var::mk_integer(1), + Var::mk_integer(2), + Var::mk_integer(3), + Var::mk_integer(4) + ]) + ); + } + + #[test] + fn test_list_range() -> Result<(), Error> { + // test on integer list + let int_list = v_list(&[1.into(), 2.into(), 3.into(), 4.into(), 5.into()]); + assert_eq!( + int_list.range(&v_int(2), &v_int(4), IndexMode::ZeroBased)?, + v_list(&[3.into(), 4.into(), 5.into()]) + ); + + let int_list = v_list(&[1.into(), 2.into(), 3.into(), 4.into(), 5.into()]); + assert_eq!( + int_list.range(&v_int(3), &v_int(5), IndexMode::OneBased)?, + v_list(&[3.into(), 4.into(), 5.into()]) + ); + + // range with upper higher than lower, moo returns empty list for this (!) + let empty_list = v_empty_list(); + assert_eq!( + empty_list.range(&v_int(1), &v_int(0), IndexMode::ZeroBased), + Ok(v_empty_list()) + ); + // test on out of range + let int_list = v_list(&[1.into(), 2.into(), 3.into()]); + assert_eq!( + int_list.range(&v_int(2), &v_int(4), IndexMode::ZeroBased), + Err(E_RANGE) + ); + // test on type mismatch + let var_int = v_int(10); assert_eq!( - list.insert(-1, v_int(0)), - v_list(&[v_int(0), v_int(1), v_int(2), v_int(3)]) + var_int.range(&v_int(1), &v_int(5), IndexMode::ZeroBased), + Err(E_TYPE) ); - // MOO supports indexes beyond length of the list, which just append to the end... - let mut list = List::from_slice(&[v_int(1), v_int(2), v_int(3)]); + let list = v_list(&[v_int(0), v_int(0)]); assert_eq!( - list.insert(100, v_int(0)), - v_list(&[v_int(1), v_int(2), v_int(3), v_int(0)]) + list.range(&v_int(1), &v_int(2), IndexMode::OneBased)?, + v_list(&[v_int(0), v_int(0)]) ); + Ok(()) + } + + #[test] + fn test_list_range_set() { + let base = v_list(&[v_int(1), v_int(2), v_int(3), v_int(4)]); + + // {1,2,3,4}[1..2] = {"a", "b", "c"} => {1, "a", "b", "c", 4} + let value = v_list(&[v_str("a"), v_str("b"), v_str("c")]); + let expected = v_list(&[v_int(1), v_str("a"), v_str("b"), v_str("c"), v_int(4)]); + let result = base.range_set(&v_int(2), &v_int(3), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + + // {1,2,3,4}[1..2] = {"a"} => {1, "a", 4} + let value = v_list(&[v_str("a")]); + let expected = v_list(&[v_int(1), v_str("a"), v_int(4)]); + let result = base.range_set(&v_int(2), &v_int(3), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + + // {1,2,3,4}[1..2] = {} => {1,4} + let value = v_empty_list(); + let expected = v_list(&[v_int(1), v_int(4)]); + let result = base.range_set(&v_int(2), &v_int(3), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + + // {1,2,3,4}[1..2] = {"a", "b"} => {1, "a", "b", 4} + let value = v_list(&[v_str("a"), v_str("b")]); + let expected = v_list(&[v_int(1), v_str("a"), v_str("b"), v_int(4)]); + let result = base.range_set(&v_int(2), &v_int(3), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + } + + #[test] + fn test_list_range_set2() { + let base = v_list(&[v_int(1), v_int(2), v_int(3), v_int(4)]); + let with_val = v_list(&[v_int(3), v_int(4)]); + let expected = v_list(&[v_int(3), v_int(4), v_int(3), v_int(4)]); + let result = base.range_set(&v_int(1), &v_int(2), &with_val, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + } + + #[test] + fn test_list_push() { + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let r = l.push(&v_int(4)).unwrap(); + assert_eq!(r, v_list(&[v_int(1), v_int(2), v_int(3), v_int(4)])); + } + + #[test] + fn test_list_append() { + let l1 = v_list(&[v_int(1), v_int(2), v_int(3)]); + let l2 = v_list(&[v_int(4), v_int(5), v_int(6)]); + let l3 = v_list(&[v_int(1), v_int(2), v_int(3), v_int(4), v_int(5), v_int(6)]); + assert_eq!(l1.append(&l2), Ok(l3)); + } + + #[test] + fn test_list_remove_at() { + let l = v_list(&[v_int(1), v_int(2), v_int(3), v_int(4)]); + let r = l.remove_at(&v_int(1), IndexMode::ZeroBased).unwrap(); + assert_eq!(r, v_list(&[v_int(1), v_int(3), v_int(4)])); + } + + #[test] + fn test_list_contains() { + // Case sensitive and case-insensitive tests + let l = v_list(&[v_str("a"), v_str("b"), v_str("c")]); + assert_eq!(l.contains(&v_str("a"), true), Ok(v_bool(true))); + assert_eq!(l.contains(&v_str("A"), false), Ok(v_bool(true))); + assert_eq!(l.contains(&v_str("A"), true), Ok(v_bool(false))); + } + + #[test] + fn test_index_in() { + let l = v_list(&[v_str("a"), v_str("b"), v_str("c")]); + assert_eq!( + l.index_in(&v_str("a"), false, IndexMode::OneBased).unwrap(), + v_int(1) + ); + assert_eq!( + l.index_in(&v_str("A"), false, IndexMode::OneBased).unwrap(), + v_int(1) + ); + assert_eq!( + l.index_in(&v_str("A"), true, IndexMode::OneBased).unwrap(), + v_int(0) + ); + + assert_eq!( + l.index_in(&v_str("A"), true, IndexMode::ZeroBased).unwrap(), + v_int(-1) + ); + } + + #[test] + fn test_list_case_sensitive_compare() { + let a = v_list(&[v_str("a"), v_str("b"), v_str("c")]); + let b = v_list(&[v_str("A"), v_str("B"), v_str("C")]); + + assert!(!a.eq_case_sensitive(&b)); + assert!(a == b); } #[test] - pub fn list_display() { - let list = List::from_slice(&[v_int(1), v_string("foo".into()), v_int(3)]); - assert_eq!(format!("{list}"), "{1, \"foo\", 3}"); + fn test_list_insert() { + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let r = l.insert(&v_int(0), &v_int(0), IndexMode::OneBased).unwrap(); + assert_eq!(r, v_list(&[v_int(0), v_int(1), v_int(2), v_int(3)])); + + // Insert to the end + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let r = l.insert(&v_int(1), &v_int(1), IndexMode::OneBased).unwrap(); + assert_eq!(r, v_list(&[v_int(1), v_int(1), v_int(2), v_int(3)])); + + // Out of range just goes to the end + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let r = l + .insert(&v_int(10), &v_int(10), IndexMode::OneBased) + .unwrap(); + assert_eq!(r, v_list(&[v_int(1), v_int(2), v_int(3), v_int(10)])); } } diff --git a/crates/values/src/var/list_impl_buffer.rs b/crates/values/src/var/list_impl_buffer.rs deleted file mode 100644 index 0fb95faa..00000000 --- a/crates/values/src/var/list_impl_buffer.rs +++ /dev/null @@ -1,665 +0,0 @@ -// 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 . -// - -use std::cmp::min; - -use bincode::de::{BorrowDecoder, Decoder}; -use bincode::enc::Encoder; -use bincode::error::{DecodeError, EncodeError}; -use bincode::{BorrowDecode, Decode, Encode}; -use bytes::Bytes; - -use crate::var::variant::Variant; -use crate::var::{v_empty_list, Var}; -use crate::{AsByteBuffer, DecodingError, EncodingError}; - -#[derive(Clone, Debug)] -pub struct ListImplBuffer(Bytes); - -fn offsets_end_pos(buf: &[u8]) -> usize { - u32::from_le_bytes(buf[0..4].try_into().unwrap()) as usize -} - -fn offset_at(buf: &[u8], index: usize) -> usize { - u32::from_le_bytes(buf[4 + index * 4..4 + (index + 1) * 4].try_into().unwrap()) as usize -} - -impl ListImplBuffer { - pub fn new() -> Self { - Self(Bytes::from(Vec::new())) - } - - pub fn len(&self) -> usize { - let l = self.0.len(); - if l == 0 || l == 4 { - return 0; - } - - let slc = self.0.as_ref(); - let offsets_end = offsets_end_pos(slc); - let offsets_len = offsets_end - 4; - // The offsets table is 4 bytes per offset. - offsets_len >> 2 - } - - pub fn is_empty(&self) -> bool { - let l = self.0.len(); - if l == 0 || l == 4 { - return true; - } - false - } - - pub fn get(&self, index: usize) -> Option { - let len = self.len(); - if index >= len { - return None; - } - - let slc = self.0.as_ref(); - let offsets_end = offsets_end_pos(slc); - - // The offsets table is 4 bytes per offset. - let data_offset = offset_at(slc, index); - - let data_section = &self.0.slice(offsets_end..); - - // If this is the last item, we can just slice to the end of the data section. - if index == len - 1 { - let slice_ref = data_section.slice(data_offset..); - return Some(Var::from_bytes(slice_ref).expect("could not decode var")); - } - - // Otherwise, we need to slice from this offset to the next offset. - let next_offset = offset_at(slc, index + 1); - - // Note that the offsets are relative to the start of the data section. - let data = data_section.slice(data_offset..next_offset); - Var::from_bytes(data.clone()).ok() - } - - pub fn from_slice(vec: &[Var]) -> Self { - let mut data = Vec::new(); - - let mut relative_offset: u32 = 0; - let mut offsets = Vec::with_capacity(vec.len() * 4); - for v in vec.iter() { - offsets.extend_from_slice(&relative_offset.to_le_bytes()); - let vsr = v.as_bytes().unwrap(); - let bytes = vsr.as_ref(); - data.extend_from_slice(bytes); - relative_offset += bytes.len() as u32; - } - - let mut result = Vec::with_capacity(4 + offsets.len() + data.len()); - result.extend_from_slice(&(offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&offsets); - result.extend_from_slice(&data); - - Self(Bytes::from(result)) - } - - pub fn push(&self, v: Var) -> Self { - let len = self.len(); - - let data_sr = v.as_bytes().unwrap(); - - // Special case if we're empty. - if len == 0 { - let mut new_offsets = Vec::with_capacity(4); - let offset: u32 = 0; - new_offsets.extend_from_slice(&offset.to_le_bytes()); - - let mut result = Vec::with_capacity(4 + new_offsets.len() + data_sr.len()); - result.extend_from_slice(&8u32.to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(data_sr.as_ref()); - - return Self(Bytes::from(result)); - } - - let slc = self.0.as_ref(); - let offsets_end = offsets_end_pos(slc); - - let existing_offset_table = &slc[4..offsets_end]; - let existing_data = &slc[offsets_end..]; - - // Add the new offset to the offsets table. The new offset is end of the old data section, - // that is, the length of the whole buffer. - let mut new_offsets = Vec::with_capacity(existing_offset_table.len() + 4); - new_offsets.extend_from_slice(existing_offset_table); - let new_offset = existing_data.len() as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - - // Add the new data to the data section. - let mut new_data = Vec::with_capacity(existing_data.len() + data_sr.len()); - new_data.extend_from_slice(existing_data); - new_data.extend_from_slice(data_sr.as_ref()); - - // Update offsets end - let new_offsets_end = new_offsets.len() as u32 + 4; - - // Result is new_offsets_len + new_offsets + new_data - let mut result = Vec::with_capacity(4 + new_offsets.len() + new_data.len()); - result.extend_from_slice(&new_offsets_end.to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(&new_data); - - Self(Bytes::from(result)) - } - - pub fn pop_front(&self) -> (Var, Self) { - let len = self.len(); - if len == 0 { - return (v_empty_list(), self.clone()); - } - - if len == 1 { - return (self.get(0).unwrap(), ListImplBuffer::new()); - } - - let slc = self.0.as_ref(); - let offsets_end = offsets_end_pos(slc); - - // Get the offset table - let offsets_table = &slc[4..offsets_end]; - - // Splice off the data section after the first item - let data_section = &self.0.slice(offsets_end..); - let first_offset = offset_at(slc, 0); - let next_offset = offset_at(slc, 1); - let length = next_offset - first_offset; - let data = data_section.slice(first_offset..next_offset); - - // Now rebuild the offset table, subtracting the length of the first item - let mut new_offsets = Vec::with_capacity(offsets_table.len() - 4); - for i in 1..len { - let offset = offset_at(slc, i); - let new_offset = (offset - length) as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - } - - // Now reconstruct - let mut result = Vec::with_capacity(4 + new_offsets.len() + data.len()); - result.extend_from_slice(&(new_offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(data_section.slice(next_offset..).as_ref()); - - (Var::from_bytes(data).unwrap(), Self(Bytes::from(result))) - } - - pub fn append(&self, other: Self) -> Self { - let len = self.len(); - if len == 0 { - return other.clone(); - } - - let other_len = other.len(); - if other_len == 0 { - return self.clone(); - } - - // Find the starts of the two data sections - let slc = self.0.as_ref(); - let oth_slc = other.0.as_ref(); - - let data_start_self = offsets_end_pos(slc); - let data_start_other = offsets_end_pos(oth_slc); - - // Get their data sections - let data_self = &slc[data_start_self..]; - let data_other = &oth_slc[data_start_other..]; - - // Get the two offsets tables - let offset_table_self = &slc[4..data_start_self]; - let offset_table_other = &oth_slc[4..data_start_other]; - - let self_offset_len = offset_table_self.len(); - - // Construct a new offset table, leaving self intact and then adjusting other. - let mut new_offset_table = Vec::with_capacity(self_offset_len + offset_table_other.len()); - new_offset_table.extend_from_slice(offset_table_self); - for i in 0..other_len { - let offset = offset_at(oth_slc, i); - let new_offset = (offset + data_self.len()) as u32; - new_offset_table.extend_from_slice(&new_offset.to_le_bytes()); - } - - let mut result = - Vec::with_capacity(4 + new_offset_table.len() + data_self.len() + data_other.len()); - result.extend_from_slice(&(new_offset_table.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offset_table); - result.extend_from_slice(data_self); - result.extend_from_slice(data_other); - - Self(Bytes::from(result)) - } - - pub fn remove_at(&self, index: usize) -> Self { - let len = self.len(); - if len == 0 { - return self.clone(); - } - - if len == 1 { - return ListImplBuffer::new(); - } - - // This will involve rebuilding both the offsets and data sections. - let slc = self.0.as_ref(); - let old_data_start = offsets_end_pos(slc); - - let old_data = &slc[old_data_start..]; - let old_offsets = &slc[4..old_data_start]; - - let remove_item_offset = - u32::from_le_bytes(old_offsets[index * 4..(index + 1) * 4].try_into().unwrap()) - as usize; - let remove_item_length = if index == len - 1 { - old_data.len() - remove_item_offset - } else { - let next_offset = offset_at(slc, index + 1); - next_offset - remove_item_offset - }; - - let mut new_offsets = Vec::with_capacity(old_offsets.len() - 4); - let mut new_data = Vec::with_capacity(old_data.len() - remove_item_length); - - // Iterate to generate - for i in 0..len { - if i == index { - continue; - } - - let offset = offset_at(slc, i); - let data = if i == len - 1 { - &old_data[offset..] - } else { - let next_offset = offset_at(slc, i + 1); - &old_data[offset..next_offset] - }; - let new_offset = new_data.len() as u32; - new_data.extend_from_slice(data); - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - } - - let mut result = Vec::with_capacity(4 + new_offsets.len() + new_data.len()); - result.extend_from_slice(&(new_offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(&new_data); - Self(Bytes::from(result)) - } - - /// Remove the first found instance of the given value from the list. - #[must_use] - pub fn setremove(&self, value: &Var) -> Self { - let len = self.len(); - if len == 0 { - return self.clone(); - } - - if len == 1 { - if self.get(0).unwrap().eq(value) { - return ListImplBuffer::new(); - } - return self.clone(); - } - - // This will involve rebuilding both the offsets and data sections. - let slc = self.0.as_ref(); - let old_data_start = offsets_end_pos(slc); - - let old_data = &self.0.slice(old_data_start..); - let old_offsets = &slc[4..old_data_start]; - - let mut new_offsets = Vec::with_capacity(old_offsets.len() - 4); - let mut new_data = Vec::with_capacity(old_data.len()); - - // Iterate to generate - let mut found = false; - for i in 0..len { - let offset = offset_at(slc, i); - let data = if i == len - 1 { - old_data.slice(offset..) - } else { - let next_offset = offset_at(slc, i + 1); - old_data.slice(offset..next_offset) - }; - let v = Var::from_bytes(data.clone()).unwrap(); - if !found && v.eq(value) { - found = true; - continue; - } - - let new_offset = new_data.len() as u32; - new_data.extend_from_slice(data.as_ref()); - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - } - - let mut result = Vec::with_capacity(4 + new_offsets.len() + new_data.len()); - result.extend_from_slice(&(new_offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(&new_data); - Self(Bytes::from(result)) - } - - pub fn insert(&self, index: isize, value: Var) -> Self { - let index = if index < 0 { - 0 - } else { - min(index as usize, self.len()) - }; - - // Special case if inserting at end, it's just push - if index == self.len() { - return self.push(value); - } - - // Special case if we're empty. - if self.is_empty() { - return Self::from_slice(&[value]); - } - - // Accumulate up to the insertion point, building the new offsets and data sections. - // Then add the new item, and then add the rest of the items. - let slc = self.0.as_ref(); - let old_data_start = offsets_end_pos(slc); - let old_data = &slc[old_data_start..]; - let old_offsets = &slc[4..old_data_start]; - - let mut new_offsets = Vec::with_capacity(old_offsets.len() + 4); - let mut new_data = Vec::with_capacity(old_data.len() + value.as_bytes().unwrap().len()); - - for i in 0..self.len() { - if i == index { - let new_offset = new_data.len() as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - new_data.extend_from_slice(value.as_bytes().unwrap().as_ref()); - } - let offset = offset_at(slc, i); - let length = if i == self.len() - 1 { - old_data.len() - offset - } else { - let next_offset = offset_at(slc, i + 1); - next_offset - offset - }; - let new_offset = new_data.len() as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - new_data.extend_from_slice(&old_data[offset..offset + length]); - } - - let mut result = Vec::with_capacity(4 + new_offsets.len() + new_data.len()); - result.extend_from_slice(&(new_offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(&new_data); - Self(Bytes::from(result)) - } - - pub fn set(&self, index: usize, value: Var) -> Self { - let len = self.len(); - if index >= len { - return self.clone(); - } - - // This will involve rebuilding both the offsets and data sections. - let slc = self.0.as_ref(); - let old_data_start = offsets_end_pos(slc); - - let old_data = &self.0.slice(old_data_start..); - let old_offsets = &slc[4..old_data_start]; - - let mut new_offsets = Vec::with_capacity(old_offsets.len()); - let mut new_data = Vec::with_capacity(old_data.len()); - - // Iterate to generate - for i in 0..len { - let offset = offset_at(slc, i); - let data = if i == len - 1 { - old_data.slice(offset..) - } else { - let next_offset = offset_at(slc, i + 1); - old_data.slice(offset..next_offset) - }; - if i == index { - let new_offset = new_data.len() as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - new_data.extend_from_slice(value.as_bytes().unwrap().as_ref()); - } else { - let new_offset = new_data.len() as u32; - new_offsets.extend_from_slice(&new_offset.to_le_bytes()); - new_data.extend_from_slice(data.as_ref()); - } - } - - let mut result = Vec::with_capacity(4 + new_offsets.len() + new_data.len()); - result.extend_from_slice(&(new_offsets.len() as u32 + 4).to_le_bytes()); - result.extend_from_slice(&new_offsets); - result.extend_from_slice(&new_data); - Self(Bytes::from(result)) - } - - // Case insensitive - pub fn contains(&self, v: &Var) -> bool { - self.iter().any(|item| item.eq(v)) - } - - pub fn iter(&self) -> impl Iterator + '_ { - (0..self.len()).map(move |i| self.get(i).unwrap()) - } - - pub fn contains_case_sensitive(&self, v: &Var) -> bool { - if let Variant::Str(s) = v.variant() { - for item in self.iter() { - if let Variant::Str(s2) = item.variant() { - if s.as_str() == s2.as_str() { - return true; - } - } - } - return false; - } - self.contains(v) - } -} - -impl AsByteBuffer for ListImplBuffer { - fn size_bytes(&self) -> usize { - self.0.len() - } - - fn with_byte_buffer R>(&self, mut f: F) -> Result { - Ok(f(self.0.as_ref())) - } - - fn make_copy_as_vec(&self) -> Result, EncodingError> { - Ok(self.0.as_ref().to_vec()) - } - - fn from_bytes(bytes: Bytes) -> Result - where - Self: Sized, - { - Ok(Self(bytes)) - } - - fn as_bytes(&self) -> Result { - Ok(self.0.clone()) - } -} - -impl Encode for ListImplBuffer { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - self.0.as_ref().encode(encoder) - } -} - -impl Decode for ListImplBuffer { - fn decode(decoder: &mut D) -> Result { - let vec = Vec::::decode(decoder)?; - Ok(Self(Bytes::from(vec))) - } -} - -impl<'de> BorrowDecode<'de> for ListImplBuffer { - fn borrow_decode>(decoder: &mut D) -> Result { - let vec = Vec::::borrow_decode(decoder)?; - Ok(Self(Bytes::from(vec))) - } -} - -impl From> for ListImplBuffer { - fn from(value: Vec) -> Self { - Self::from_slice(&value) - } -} - -impl From<&[Var]> for ListImplBuffer { - fn from(value: &[Var]) -> Self { - Self::from_slice(value) - } -} - -#[cfg(test)] -mod tests { - use crate::var::list_impl_buffer::ListImplBuffer; - use crate::var::{v_int, v_string}; - - #[test] - pub fn list_make_get() { - let l = ListImplBuffer::new(); - assert_eq!(l.len(), 0); - assert!(l.is_empty()); - // MOO is a bit weird here, it returns None for out of bounds. - assert_eq!(l.get(0), None); - - let l = ListImplBuffer::from_slice(&[v_int(1)]); - assert_eq!(l.len(), 1); - assert!(!l.is_empty()); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), None); - - let l = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3)]); - assert_eq!(l.len(), 3); - assert!(!l.is_empty()); - - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), Some(v_int(2))); - assert_eq!(l.get(2), Some(v_int(3))); - } - - #[test] - pub fn list_push() { - let l = ListImplBuffer::new(); - let l = l.push(v_int(1)); - - assert_eq!(l.len(), 1); - let l = l.push(v_int(2)); - assert_eq!(l.len(), 2); - let l = l.push(v_int(3)); - assert_eq!(l.len(), 3); - - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(2), Some(v_int(3))); - assert_eq!(l.get(1), Some(v_int(2))); - } - - #[test] - fn list_pop_front() { - let l = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(1)); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(2)); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(3)); - assert_eq!(l.len(), 0); - } - - #[test] - fn test_list_append() { - let l1 = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let l2 = ListImplBuffer::from_slice(&[v_int(4), v_int(5), v_int(6)]); - let l = l1.append(l2); - assert_eq!(l.len(), 6); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(5), Some(v_int(6))); - } - - #[test] - fn test_list_remove() { - let l = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3)]); - - let l = l.remove_at(1); - assert_eq!(l.len(), 2); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(0), Some(v_int(1))); - } - - #[test] - fn test_list_setremove() { - let l = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3), v_int(2)]); - let l = l.setremove(&v_int(2)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(2), Some(v_int(2))); - - // setremove til empty - let l = ListImplBuffer::from_slice(&[v_int(1)]); - let l = l.setremove(&v_int(1)); - assert_eq!(l.len(), 0); - assert_eq!(l.get(0), None); - } - - #[test] - fn test_list_insert() { - let l = ListImplBuffer::new(); - let l = l.insert(0, v_int(4)); - assert_eq!(l.len(), 1); - assert_eq!(l.get(0), Some(v_int(4))); - - let l = l.insert(0, v_int(3)); - assert_eq!(l.len(), 2); - assert_eq!(l.get(0), Some(v_int(3))); - assert_eq!(l.get(1), Some(v_int(4))); - - let l = l.insert(-1, v_int(5)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(0), Some(v_int(5))); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(2), Some(v_int(4))); - } - - #[test] - fn test_list_set() { - let l = ListImplBuffer::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let l = l.set(1, v_int(4)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(1), Some(v_int(4))); - } - - #[test] - fn test_list_contains_case_insenstive() { - let l = ListImplBuffer::from_slice(&[v_string("foo".into()), v_string("bar".into())]); - assert!(l.contains(&v_string("FOO".into()))); - assert!(l.contains(&v_string("BAR".into()))); - } - - #[test] - fn test_list_contains_case_senstive() { - let l = ListImplBuffer::from_slice(&[v_string("foo".into()), v_string("bar".into())]); - assert!(!l.contains_case_sensitive(&v_string("FOO".into()))); - assert!(!l.contains_case_sensitive(&v_string("BAR".into()))); - } -} diff --git a/crates/values/src/var/list_impl_vector.rs b/crates/values/src/var/list_impl_vector.rs deleted file mode 100644 index b10d8a42..00000000 --- a/crates/values/src/var/list_impl_vector.rs +++ /dev/null @@ -1,412 +0,0 @@ -// 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 . -// - -use std::cmp::min; -use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; -use std::sync::Arc; - -use crate::BincodeAsByteBufferExt; -use bincode::{Decode, Encode}; - -use crate::var::variant::Variant; -use crate::var::{v_empty_list, Var}; - -#[derive(Clone, Debug, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct ListImplVector { - // TODO: Implement our own zero-copy list type and get rid of bincoding - // To support nested content, would require an offsets table at the front, etc. - // Take a look at how flatbufers, capnproto, and other zero-copy serialization formats do this. - inner: Arc>, -} - -impl ListImplVector { - #[must_use] - pub fn new() -> Self { - Self { - inner: Arc::new(Vec::new()), - } - } - - #[must_use] - pub fn from_vec(vec: Vec) -> Self { - Self { - inner: Arc::new(vec), - } - } - - pub fn from_slice(vec: &[Var]) -> Self { - Self { - inner: Arc::new(vec.to_vec()), - } - } - - #[must_use] - pub fn push(&mut self, v: Var) -> Self { - // If there's only one copy of us, mutate that directly. - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - vec.push(v); - self.clone() - } - None => { - let mut new_vec = (*self.inner).clone(); - new_vec.push(v); - Self::from_vec(new_vec) - } - } - } - - /// Take the first item from the front, and return (item, `new_list`) - #[must_use] - pub fn pop_front(&self) -> (Var, Self) { - if self.inner.is_empty() { - return (v_empty_list(), Self::new()); - } - let mut new_list = (*self.inner).clone(); - let item = new_list.remove(0); - (item.clone(), Self::from_vec(new_list)) - } - - #[must_use] - pub fn append(&mut self, other: Self) -> Self { - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - vec.extend_from_slice(&other.inner); - self.clone() - } - None => { - let mut new_list = (*self.inner).clone(); - new_list.extend_from_slice(&other.inner); - Self::from_vec(new_list) - } - } - } - - #[must_use] - pub fn remove_at(&mut self, index: usize) -> Self { - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - vec.remove(index); - self.clone() - } - None => { - let mut new_list = (*self.inner).clone(); - new_list.remove(index); - Self::from_vec(new_list) - } - } - } - - /// Remove the first found instance of the given value from the list. - #[must_use] - pub fn setremove(&mut self, value: &Var) -> Self { - if self.inner.is_empty() { - return self.clone(); - } - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - for i in 0..vec.len() { - if vec[i].eq(value) { - vec.remove(i); - break; - } - } - self.clone() - } - None => { - let mut new_list = Vec::with_capacity(self.inner.len() - 1); - let mut found = false; - for v in self.inner.iter() { - if !found && v.eq(value) { - found = true; - continue; - } - new_list.push(v.clone()); - } - Self::from_vec(new_list) - } - } - } - - #[must_use] - pub fn insert(&mut self, index: isize, v: Var) -> Self { - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - let index = if index < 0 { - 0 - } else { - min(index as usize, vec.len()) - }; - vec.insert(index, v); - self.clone() - } - None => { - let mut new_list = Vec::with_capacity(self.inner.len() + 1); - let index = if index < 0 { - 0 - } else { - min(index as usize, self.inner.len()) - }; - new_list.extend_from_slice(&self.inner[..index]); - new_list.push(v); - new_list.extend_from_slice(&self.inner[index..]); - Self::from_vec(new_list) - } - } - } - - #[must_use] - pub fn len(&self) -> usize { - self.inner.len() - } - - #[must_use] - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - // "in" operator is case insensitive... - #[must_use] - pub fn contains(&self, v: &Var) -> bool { - self.inner.contains(v) - } - - // but bf_is_member is not... sigh. - #[must_use] - pub fn contains_case_sensitive(&self, v: &Var) -> bool { - if let Variant::Str(s) = v.variant() { - for item in self.inner.iter() { - if let Variant::Str(s2) = item.variant() { - if s.as_str() == s2.as_str() { - return true; - } - } - } - return false; - } - self.inner.contains(v) - } - - #[must_use] - pub fn get(&self, index: usize) -> Option { - self.inner.get(index).cloned() - } - - #[must_use] - pub fn set(&mut self, index: usize, value: Var) -> Self { - match Arc::get_mut(&mut self.inner) { - Some(vec) => { - vec[index] = value; - self.clone() - } - None => { - let mut new_vec = (*self.inner).clone(); - new_vec[index] = value; - Self::from_vec(new_vec) - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.inner.iter() - } -} - -impl From for Vec { - fn from(val: ListImplVector) -> Self { - val.inner[..].to_vec() - } -} - -impl Default for ListImplVector { - fn default() -> Self { - Self::new() - } -} - -impl Index for ListImplVector { - type Output = Var; - - fn index(&self, index: usize) -> &Self::Output { - &self.inner[index] - } -} - -impl Index> for ListImplVector { - type Output = [Var]; - - fn index(&self, index: Range) -> &Self::Output { - &self.inner[index] - } -} - -impl Index> for ListImplVector { - type Output = [Var]; - - fn index(&self, index: RangeFrom) -> &Self::Output { - &self.inner[index] - } -} - -impl Index> for ListImplVector { - type Output = [Var]; - - fn index(&self, index: RangeTo) -> &Self::Output { - &self.inner[index] - } -} - -impl Index for ListImplVector { - type Output = [Var]; - - fn index(&self, index: RangeFull) -> &Self::Output { - &self.inner[index] - } -} - -impl BincodeAsByteBufferExt for ListImplVector {} - -#[cfg(test)] -mod tests { - use crate::var::list_impl_vector::ListImplVector; - use crate::var::{v_int, v_string}; - - #[test] - pub fn list_make_get() { - let l = ListImplVector::new(); - assert_eq!(l.len(), 0); - assert!(l.is_empty()); - // MOO is a bit weird here, it returns None for out of bounds. - assert_eq!(l.get(0), None); - - let l = ListImplVector::from_slice(&[v_int(1)]); - assert_eq!(l.len(), 1); - assert!(!l.is_empty()); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), None); - - let l = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3)]); - assert_eq!(l.len(), 3); - assert!(!l.is_empty()); - - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), Some(v_int(2))); - assert_eq!(l.get(2), Some(v_int(3))); - } - - #[test] - pub fn list_push() { - let mut l = ListImplVector::new(); - let mut l = l.push(v_int(1)); - - assert_eq!(l.len(), 1); - let mut l = l.push(v_int(2)); - assert_eq!(l.len(), 2); - let l = l.push(v_int(3)); - assert_eq!(l.len(), 3); - - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(2), Some(v_int(3))); - assert_eq!(l.get(1), Some(v_int(2))); - } - - #[test] - fn list_pop_front() { - let l = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(1)); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(2)); - let (item, l) = l.pop_front(); - assert_eq!(item, v_int(3)); - assert_eq!(l.len(), 0); - } - - #[test] - fn test_list_append() { - let mut l1 = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let l2 = ListImplVector::from_slice(&[v_int(4), v_int(5), v_int(6)]); - let l = l1.append(l2); - assert_eq!(l.len(), 6); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(5), Some(v_int(6))); - } - - #[test] - fn test_list_remove() { - let mut l = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3)]); - - let l = l.remove_at(1); - assert_eq!(l.len(), 2); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(0), Some(v_int(1))); - } - - #[test] - fn test_list_setremove() { - let mut l = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3), v_int(2)]); - let l = l.setremove(&v_int(2)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(0), Some(v_int(1))); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(2), Some(v_int(2))); - - // setremove til empty - let mut l = ListImplVector::from_slice(&[v_int(1)]); - let l = l.setremove(&v_int(1)); - assert_eq!(l.len(), 0); - assert_eq!(l.get(0), None); - } - - #[test] - fn test_list_insert() { - let mut l = ListImplVector::new(); - let mut l = l.insert(0, v_int(4)); - assert_eq!(l.len(), 1); - assert_eq!(l.get(0), Some(v_int(4))); - - let mut l = l.insert(0, v_int(3)); - assert_eq!(l.len(), 2); - assert_eq!(l.get(0), Some(v_int(3))); - assert_eq!(l.get(1), Some(v_int(4))); - - let l = l.insert(-1, v_int(5)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(0), Some(v_int(5))); - assert_eq!(l.get(1), Some(v_int(3))); - assert_eq!(l.get(2), Some(v_int(4))); - } - - #[test] - fn test_list_set() { - let mut l = ListImplVector::from_slice(&[v_int(1), v_int(2), v_int(3)]); - let l = l.set(1, v_int(4)); - assert_eq!(l.len(), 3); - assert_eq!(l.get(1), Some(v_int(4))); - } - - #[test] - fn test_list_contains_case_insensitive() { - let l = ListImplVector::from_slice(&[v_string("foo".into()), v_string("bar".into())]); - assert!(l.contains(&v_string("FOO".into()))); - assert!(l.contains(&v_string("BAR".into()))); - } - - #[test] - fn test_list_contains_case_sensitive() { - let l = ListImplVector::from_slice(&[v_string("foo".into()), v_string("bar".into())]); - assert!(!l.contains_case_sensitive(&v_string("FOO".into()))); - assert!(!l.contains_case_sensitive(&v_string("BAR".into()))); - } -} diff --git a/crates/values/src/var/map.rs b/crates/values/src/var/map.rs index 97d5ada2..5f612382 100644 --- a/crates/values/src/var/map.rs +++ b/crates/values/src/var/map.rs @@ -12,162 +12,709 @@ // this program. If not, see . // -use crate::var::{Var, Variant}; -use crate::BincodeAsByteBufferExt; -use bincode::de::{BorrowDecoder, Decoder}; -use bincode::enc::Encoder; -use bincode::error::{DecodeError, EncodeError}; -use bincode::{BorrowDecode, Decode, Encode}; -use std::fmt::{Display, Formatter}; - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct Map(im::OrdMap); -impl Default for Map { - fn default() -> Self { - Self::new() - } +use crate::var::storage::VarBuffer; +use crate::var::var::Var; +use crate::var::variant::Variant; +use crate::var::Associative; +use crate::var::Error::{E_RANGE, E_TYPE}; +use crate::var::{Error, VarType}; +use bytes::Bytes; +use flexbuffers::{BuilderOptions, Reader, VectorReader}; +use std::cmp::Ordering; +use std::hash::Hash; + +#[derive(Clone)] +pub struct Map { + // Reader must be boxed to avoid overfilling the stack. + pub reader: Box>, } impl Map { - pub fn new() -> Self { - Self(im::OrdMap::new()) + // Construct from an Iterator of paris + pub(crate) fn build<'a, I: Iterator>(pairs: I) -> Var { + // Our maps don't use the flexbuffers map type because that only allows strings for keys. + // Instead, we just use a vector of pairs, sorted, so binary search can be used to find + // keys in O(log n) time. + // Construction, however, is O(n) because we need to insert the pairs in sorted order. + // And make a copy, to boot. + let mut sorted: Vec<_> = pairs.collect(); + sorted.sort(); + + Self::build_presorted(sorted.into_iter()) } - pub fn from_pairs(pairs: &[(Var, Var)]) -> Self { - Self(pairs.iter().cloned().collect()) + pub(crate) fn build_presorted<'a, I: Iterator>(pairs: I) -> Var { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_MAP as u8); + let mut mv = vb.start_vector(); + for (k, v) in pairs { + k.variant().push_item(&mut mv); + v.variant().push_item(&mut mv); + } + mv.end_vector(); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) } - fn from_map(map: im::OrdMap) -> Self { - Self(map) + pub fn iter(&self) -> impl Iterator + '_ { + (0..self.len()).map(move |i| { + let k = self.reader.idx(i * 2); + let v = self.reader.idx(i * 2 + 1); + let key = Variant::from_reader(k); + let value = Variant::from_reader(v); + (Var(key), Var(value)) + }) } - pub fn len(&self) -> usize { - self.0.len() + fn binary_search<'a, F: Fn(&'a Var, &Var) -> Ordering>( + &self, + f: F, + c: &'a Var, + ) -> Option { + let n = self.reader.len() / 2; + let mut low = 0; + let mut high = (n as isize) - 1; + while low <= high { + let mid = (low + high) / 2; + let k = self.reader.idx((mid * 2) as usize); + let k = Variant::from_reader(k); + let v = Var(k); + match f(c, &v) { + Ordering::Less => high = mid - 1, + Ordering::Greater => low = mid + 1, + Ordering::Equal => return Some(mid as usize), + } + } + + None } +} - pub fn is_empty(&self) -> bool { - self.0.is_empty() +impl PartialEq for Map { + fn eq(&self, other: &Self) -> bool { + if self.len() != other.len() { + return false; + } + + // elements comparison using iterator + for (a, b) in self.iter().zip(other.iter()) { + if a != b { + return false; + } + } + + true } +} - pub fn get(&self, key: &Var) -> Option<&Var> { - self.0.get(key) +impl Associative for Map { + fn is_empty(&self) -> bool { + self.reader.len() == 0 } - /// Copy-on-write insert. - /// Add a key-value pair to the map, returning a new map with the pair added. - pub fn insert(&self, key: Var, value: Var) -> Var { - let mut new_map = self.0.clone(); - new_map.insert(key, value); - Var::new(Variant::Map(Map::from_map(new_map))) + fn len(&self) -> usize { + self.reader.len() / 2 } - /// Return a map which is subset of this map where the keys lay between `start` and `end`, - /// (exclusive of `end`, inclusive of `start`.) - pub fn range(&self, start: Var, end: Var) -> Var { - let mut new_map = im::OrdMap::new(); - let range = self.0.range(start..end); - for (key, value) in range { - new_map.insert(key.clone(), value.clone()); + fn index(&self, key: &Var) -> Result { + let n = self.reader.len() / 2; + + if n == 0 { + return Err(E_RANGE); } - Var::new(Variant::Map(Map::from_map(new_map))) + // Items are in sorted order, so we can binary search. + let Some(pos) = self.binary_search(|a, b| a.cmp(b), key) else { + return Err(E_RANGE); + }; + + let v = self.reader.idx((pos * 2) + 1); + let v = Variant::from_reader(v); + let v = Var(v); + Ok(v) } - /// Replace the specified range in the map. `from` and `to` must be valid keys in the map, and - /// `to` must be a Map. All tuples between from and to are removed, and the values from `to` - /// are inserted in their place. Unlike above, the whole range is inclusive. - pub fn range_set(&self, start: Var, end: Var, to: &Map) -> Var { - let mut new_map = self.0.clone(); - let range = self.0.range(start..=end); - for (key, _) in range { - new_map.remove(key); + fn index_in(&self, key: &Var, case_sensitive: bool) -> Result, Error> { + // Check the values in the key-value pairs and return the index of the first match. + // Linear O(N) operation. + for i in 0..self.len() { + let v = self.reader.idx((i * 2) + 1); + let v = Variant::from_reader(v); + let v = Var(v); + let matches = if case_sensitive { + v.eq_case_sensitive(key) + } else { + v.eq(key) + }; + if matches { + return Ok(Some(i)); + } } - let new_map = new_map.union(to.0.clone()); - Var::new(Variant::Map(Map::from_map(new_map))) + Ok(None) } - /// Return a map with `key` removed, if it exists. If it does not exist, return the original map. - /// The removed value is returned as the second element of the tuple. - pub fn remove(&self, key: &Var) -> (Var, Option) { - let mut removed = self.0.clone(); - let removed_value = removed.remove(key); - let nm = Var::new(Variant::Map(Map::from_map(removed))); - (nm, removed_value) + fn index_set(&self, key: &Var, value: &Var) -> Result { + // Stunt has a restriction that non-scalars cannot be keys (unless they're strings). + // So we enforce that here, even though it's not strictly necessary. + if !key.is_scalar() && !key.is_string() { + return Err(E_TYPE); + } + + // If the key is already in the map, we replace the value. + // Otherwise, we add a new key-value pair, which requires re-sorting... + // So no matter what, this is an expensive O(N) operation, requiring multiple copies. + // We'll just build a new, vector, and then pass the iterator into the build function. + + // TODO: find a way to construct chained iterators for this instead... + + let mut new_vec = Vec::with_capacity(self.len() + 1); + let mut found = false; + for (k, v) in self.iter() { + if k == *key { + new_vec.push((key.clone(), value.clone())); + found = true; + } else { + new_vec.push((k, v)); + } + } + if !found { + new_vec.push((key.clone(), value.clone())); + } + Ok(Self::build(new_vec.iter())) } - pub fn iter(&self) -> impl Iterator { - self.0.iter() + /// Return the range of key-value pairs between the two keys. + fn range(&self, from: &Var, to: &Var) -> Result { + // Find start with binary search. + let start = match self.binary_search(|a, b| a.cmp(b), from) { + Some(pos) => pos, + None => return Err(E_RANGE), + }; + + // Now scan forward to find the end. + let mut new_vec = Vec::new(); + let to = to.variant(); + for i in start..self.len() { + let k = self.reader.idx(i * 2); + let k = Variant::from_reader(k); + let order = k.cmp(to); + if order == Ordering::Greater || order == Ordering::Equal { + break; + } + let v = self.reader.idx(i * 2 + 1); + let v = Variant::from_reader(v); + new_vec.push((Var(k), Var(v))); + } + + Ok(Self::build_presorted(new_vec.iter())) } - pub fn eq_case_sensitive(&self, other: &Map) -> bool { - // TODO: Surely there's a smarter way of doing this with a single well-aimed call to - // some API on self.0. - for (k1, v1) in self.iter() { - if !other - .iter() - .any(|(k2, v2)| k1.eq_case_sensitive(k2) && v1.eq_case_sensitive(v2)) - { - return false; + fn range_set(&self, _from: &Var, _to: &Var, _with: &Var) -> Result { + // We reject range assignment, because it's tricky to get right, and Stunt does weird + // things. But code to do it is here, if we ever need it. + return Err(E_TYPE); + + #[allow(unreachable_code)] + { + let with = match _with.variant() { + Variant::Map(m) => m, + _ => return Err(E_TYPE), + }; + + let mut new_pairs = Vec::with_capacity(self.len() + with.len()); + for (k, v) in self.iter() { + if k.eq(_from) + || k.cmp(_from) == Ordering::Greater && k.cmp(_to) == Ordering::Less + || k.eq(_to) + { + continue; + } + + new_pairs.push((k, v)); + } + for (k, v) in with.iter() { + new_pairs.push((k, v)); } + + Ok(Self::build(new_pairs.iter())) } - for (k2, _) in other.iter() { - // We already handled value mismatches and (self has, other hasn't) - // Just need to handle (self hasn't, other has) here - if !self.iter().any(|(k1, _)| k1.eq_case_sensitive(k2)) { - return false; + } + + fn keys(&self) -> Vec { + let mut keys = Vec::new(); + for i in 0..self.len() { + let k = self.reader.idx(i * 2); + let key = Variant::from_reader(k); + keys.push(Var(key)); + } + keys + } + + fn values(&self) -> Vec { + let mut values = Vec::new(); + for i in 0..self.len() { + let v = self.reader.idx(i * 2 + 1); + let value = Variant::from_reader(v); + values.push(Var(value)); + } + values + } + + fn contains_key(&self, key: &Var, case_sensitive: bool) -> Result { + let n = self.reader.len() / 2; + + if n == 0 { + return Ok(false); + } + let cmp = |a: &Var, b: &Var| { + if case_sensitive { + b.cmp_case_sensitive(a) + } else { + b.cmp(a) + } + }; + let result = self.binary_search(cmp, key).is_some(); + Ok(result) + } + + /// Return this map with the key/value pair removed. + /// Return the new map and the value that was removed, if any + fn remove(&self, key: &Var, case_sensitive: bool) -> (Var, Option) { + // Return a copy of self without the key, and the value that was removed, if any. + let mut new_pairs = Vec::with_capacity(self.len()); + let mut removed = None; + for (k, v) in self.iter() { + let matches = if case_sensitive { + k.cmp_case_sensitive(key) == Ordering::Equal + } else { + k == *key + }; + if matches { + removed = Some(v); + } else { + new_pairs.push((k, v)); } } - true + (Self::build(new_pairs.iter()), removed) } } -impl BincodeAsByteBufferExt for Map {} +impl Eq for Map {} -impl Encode for Map { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - // There's no bincode impl for im::HashMap, so we'll encode the pairs as a list of tuples. - // Starting with the count of the number of pairs. - self.len().encode(encoder)?; - for (key, value) in self.iter() { - key.encode(encoder)?; - value.encode(encoder)?; - } - Ok(()) +impl PartialOrd for Map { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) } } -impl Decode for Map { - fn decode(decoder: &mut D) -> Result { - let tuple_count = usize::decode(decoder)?; - let mut pair_vec = Vec::with_capacity(tuple_count); - for _ in 0..tuple_count { - let key = Var::decode(decoder)?; - let value = Var::decode(decoder)?; - pair_vec.push((key, value)); +impl Ord for Map { + fn cmp(&self, other: &Self) -> Ordering { + if self.len() != other.len() { + return self.len().cmp(&other.len()); } - Ok(Map::from_pairs(&pair_vec)) + + // elements comparison + for (a, b) in self.iter().zip(other.iter()) { + match a.cmp(&b) { + Ordering::Equal => continue, + x => return x, + } + } + + Ordering::Equal } } -impl<'de> BorrowDecode<'de> for Map { - fn borrow_decode>(decoder: &mut D) -> Result { - let tuple_count = usize::decode(decoder)?; - let mut pair_vec = Vec::with_capacity(tuple_count); - for _ in 0..tuple_count { - let key = Var::borrow_decode(decoder)?; - let value = Var::borrow_decode(decoder)?; - pair_vec.push((key, value)); +impl Hash for Map { + fn hash(&self, state: &mut H) { + for item in self.iter() { + item.0.hash(state); + item.1.hash(state); } - Ok(Map::from_pairs(&pair_vec)) } } -impl Display for Map { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str("[ ")?; - for (key, value) in self.iter() { - write!(f, "{:?} -> {:?}, ", key, value)?; +#[cfg(test)] +mod tests { + use crate::var::var::Var; + use crate::var::variant::Variant; + use crate::var::{Associative, IndexMode}; + use crate::{v_bool, v_int, v_str}; + + #[test] + fn test_map_pack_unpack_index() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + match m.variant() { + Variant::Map(m) => { + assert_eq!(m.len(), 3); + } + _ => panic!("Expected map"), + } + + let key = Var::mk_str("a"); + let value = m.index(&key, IndexMode::ZeroBased).unwrap(); + match value.variant() { + Variant::Int(i) => assert_eq!(*i, 1), + _ => panic!("Expected integer"), } - f.write_str(" ]") + } + + #[test] + fn test_map_is_funcs() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + assert!(m.is_true()); + assert!(!m.is_sequence()); + assert!(m.is_associative()); + assert!(!m.is_scalar()); + assert_eq!(m.len().unwrap(), 3); + assert!(!m.is_empty().unwrap()); + + let m = Var::mk_map(&[]); + assert!(!m.is_true()); + assert!(!m.is_sequence()); + assert!(m.is_associative()); + assert!(!m.is_scalar()); + assert_eq!(m.len().unwrap(), 0); + assert!(m.is_empty().unwrap()); + } + + #[test] + fn test_map_equality_inequality() { + let m1 = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + let m2 = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + let m3 = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("d")), + ]); + + let m4 = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + ]); + + let m5 = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + (Var::mk_integer(4), Var::mk_str("d")), + ]); + + assert_eq!(m1, m2); + assert_ne!(m1, m3); + assert_ne!(m1, m4); + assert_ne!(m1, m5); + } + + #[test] + fn test_map_index() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + let key = Var::mk_str("b"); + let value = m.index(&key, IndexMode::ZeroBased).unwrap(); + match value.variant() { + Variant::Int(i) => assert_eq!(*i, 2), + _ => panic!("Expected integer"), + } + } + + #[test] + fn test_map_index_set() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + let r = m + .index_set( + &Var::mk_str("b"), + &Var::mk_integer(42), + IndexMode::ZeroBased, + ) + .unwrap(); + let r = r.variant(); + match r { + Variant::Map(m) => { + let r = m.index(&Var::mk_str("b")).unwrap(); + match r.variant() { + Variant::Int(i) => assert_eq!(*i, 42), + _ => panic!("Expected integer, got {:?}", r), + } + } + _ => panic!("Expected map, got {:?}", r), + } + + // Insert new item + let r = m + .index_set( + &Var::mk_str("d"), + &Var::mk_integer(42), + IndexMode::ZeroBased, + ) + .unwrap(); + let r = r.variant(); + match r { + Variant::Map(m) => { + let r = m.index(&Var::mk_str("d")).unwrap(); + match r.variant() { + Variant::Int(i) => assert_eq!(*i, 42), + _ => panic!("Expected integer, got {:?}", r), + } + } + _ => panic!("Expected map, got {:?}", r), + } + } + + #[test] + fn test_map_keys_values() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_integer(3), Var::mk_str("c")), + ]); + + let m = match m.variant() { + Variant::Map(m) => m, + _ => panic!("Expected map"), + }; + + // The keys come out in sorted order. + let keys = m.keys(); + assert_eq!(keys.len(), 3); + assert_eq!(keys[0], Var::mk_integer(3)); + assert_eq!(keys[1], Var::mk_str("a")); + assert_eq!(keys[2], Var::mk_str("b")); + + let values = m.values(); + assert_eq!(values.len(), 3); + assert_eq!(values[0], Var::mk_str("c")); + assert_eq!(values[1], Var::mk_integer(1)); + assert_eq!(values[2], Var::mk_integer(2)); + } + + #[test] + fn test_map_range() { + let m = Var::mk_map(&[ + (Var::mk_integer(0), Var::mk_integer(1)), + (Var::mk_integer(1), Var::mk_integer(2)), + (Var::mk_integer(2), Var::mk_integer(3)), + (Var::mk_integer(3), Var::mk_integer(4)), + ]); + + let r = m + .range( + &Var::mk_integer(1), + &Var::mk_integer(3), + IndexMode::OneBased, + ) + .unwrap(); + let r = match r.variant() { + Variant::Map(m) => m, + _ => panic!("Expected map"), + }; + + let key_value_results = r.iter().collect::>(); + assert_eq!( + key_value_results, + vec![(v_int(1), v_int(2)), (v_int(2), v_int(3))] + ); + } + + #[test] + // Disable because range_set is stubbed out in our impl + #[ignore] + fn test_map_range_set() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_str("c"), Var::mk_integer(3)), + (Var::mk_str("d"), Var::mk_integer(4)), + (Var::mk_str("e"), Var::mk_integer(5)), + ]); + + // Now replace b, c, d with x, y + let r = m + .range_set( + &Var::mk_str("b"), + &Var::mk_str("d"), + &Var::mk_map(&[ + (Var::mk_str("x"), Var::mk_integer(42)), + (Var::mk_str("y"), Var::mk_integer(43)), + ]), + IndexMode::ZeroBased, + ) + .unwrap(); + + let r = match r.variant() { + Variant::Map(m) => m, + _ => panic!("Expected map"), + }; + + assert_eq!(r.len(), 4); + let keys = r.keys(); + assert_eq!(keys.len(), 4); + assert_eq!(keys[0], Var::mk_str("a")); + assert_eq!(keys[1], Var::mk_str("e")); + assert_eq!(keys[2], Var::mk_str("x")); + assert_eq!(keys[3], Var::mk_str("y")); + } + + #[test] + fn test_map_contains_key() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_str("c"), Var::mk_integer(3)), + ]); + + let key = Var::mk_str("B"); + let not_key = Var::mk_str("d"); + + // Case-insensitive + assert_eq!(m.contains(&key, false).unwrap(), v_bool(true)); + assert_eq!(m.contains(¬_key, true).unwrap(), v_bool(false)); + + // Case sensitive + assert_eq!(m.contains(&key, true).unwrap(), v_bool(false)); + assert_eq!(m.contains(¬_key, false).unwrap(), v_bool(false)); + } + + #[test] + fn test_map_remove_key() { + let m = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_integer(1)), + (Var::mk_str("b"), Var::mk_integer(2)), + (Var::mk_str("c"), Var::mk_integer(3)), + ]); + + let key = Var::mk_str("b"); + let not_key = Var::mk_str("d"); + + let (r, removed) = m.remove(&key, false).expect("remove failed"); + assert_eq!(r.len().unwrap(), 2); + assert_eq!(removed.unwrap(), Var::mk_integer(2)); + + let (r, removed) = m.remove(¬_key, false).expect("remove failed"); + assert_eq!(r.len().unwrap(), 3); + assert_eq!(removed, None); + + // Case sensitive + let not_key = Var::mk_str("B"); + let (r, removed) = m.remove(¬_key, true).expect("remove failed"); + assert_eq!(r.len().unwrap(), 3); + assert_eq!(removed, None); + + let (r, removed) = m.remove(&key, true).expect("remove failed"); + assert_eq!(r.len().unwrap(), 2); + assert_eq!(removed.unwrap(), Var::mk_integer(2)); + } + + #[test] + /// Verify that sort order is preserved after insertion + fn test_map_insertion_ordering() { + let m = Var::mk_map(&[ + (Var::mk_integer(3), Var::mk_integer(3)), + (Var::mk_integer(1), Var::mk_integer(1)), + (Var::mk_integer(4), Var::mk_integer(4)), + (Var::mk_integer(5), Var::mk_integer(5)), + (Var::mk_integer(9), Var::mk_integer(9)), + (Var::mk_integer(2), Var::mk_integer(2)), + ]); + + let m = m + .index_set(&Var::mk_str("a"), &Var::mk_str("a"), IndexMode::OneBased) + .unwrap(); + let m = m + .index_set( + &Var::mk_integer(6), + &Var::mk_integer(6), + IndexMode::OneBased, + ) + .unwrap(); + + let m = match m.variant() { + Variant::Map(m) => m, + _ => panic!("Expected map"), + }; + + let pairs = m.iter().collect::>(); + assert_eq!( + pairs, + vec![ + (v_int(1), v_int(1)), + (v_int(2), v_int(2)), + (v_int(3), v_int(3)), + (v_int(4), v_int(4)), + (v_int(5), v_int(5)), + (v_int(6), v_int(6)), + (v_int(9), v_int(9)), + (v_str("a"), v_str("a")), + ] + ); + } + + #[test] + fn test_index_in() { + // ["3" -> "3", "1" -> "1", "4" -> "4", "5" -> "5", "9" -> "9", "2" -> "2"]; + let m = Var::mk_map(&[ + (Var::mk_str("3"), Var::mk_str("3")), + (Var::mk_str("1"), Var::mk_str("1")), + (Var::mk_str("4"), Var::mk_str("4")), + (Var::mk_str("5"), Var::mk_str("5")), + (Var::mk_str("9"), Var::mk_str("9")), + (Var::mk_str("2"), Var::mk_str("2")), + ]); + // "2" -> 2nd position + let key = Var::mk_str("2"); + let pos = m.index_in(&key, false, IndexMode::OneBased).unwrap(); + assert_eq!(pos, v_int(2)); + } + + #[test] + fn test_case_sensitive_compare() { + let m_a = Var::mk_map(&[ + (Var::mk_str("a"), Var::mk_str("a")), + (Var::mk_str("b"), Var::mk_str("b")), + (Var::mk_str("c"), Var::mk_str("c")), + ]); + + let m_b = Var::mk_map(&[ + (Var::mk_str("A"), Var::mk_str("A")), + (Var::mk_str("B"), Var::mk_str("B")), + (Var::mk_str("C"), Var::mk_str("C")), + ]); + + assert!(!m_a.eq_case_sensitive(&m_b)); + assert!(m_a.eq(&m_b)); } } diff --git a/crates/values/src/var/mod.rs b/crates/values/src/var/mod.rs index 6d10e834..dd219464 100644 --- a/crates/values/src/var/mod.rs +++ b/crates/values/src/var/mod.rs @@ -12,66 +12,47 @@ // this program. If not, see . // -#![allow(non_camel_case_types, non_snake_case)] - -use std::cmp::Ordering; -use std::fmt::{Debug, Display, Formatter}; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; - -use bincode::de::{BorrowDecoder, Decoder}; -use bincode::enc::Encoder; -use bincode::error::{DecodeError, EncodeError}; -use bincode::{BorrowDecode, Decode, Encode}; -use bytes::Bytes; -use decorum::R64; -use lazy_static::lazy_static; -use strum::FromRepr; - -use crate::encode::BINCODE_CONFIG; -use crate::util::quote_str; -pub use crate::var::error::{Error, ErrorPack}; -pub use crate::var::list::List; -pub use crate::var::map::Map; -pub use crate::var::objid::Objid; -pub use crate::var::string::Str; -pub use crate::var::symbol::Symbol; -pub use crate::var::variant::Variant; -use crate::{AsByteBuffer, DecodingError, EncodingError}; +//! TODO: +//! to_literal / formatting +//! move everything up and over into `../var` +//! go through all uses of existing var and update them to use the new var +//! any missed functionality along the way? +//! more tests, and make pass moot once above is done +//! later: +//! more documentation +//! builder vs reader mode? - optimization for values under construction. +//! some From/Into impls for common types might be missing mod error; mod list; -#[allow(dead_code)] -mod list_impl_buffer; -#[allow(dead_code)] -mod list_impl_vector; mod map; mod objid; +mod scalar; +mod storage; mod string; mod symbol; +#[allow(clippy::module_inception)] +mod var; mod variant; -mod varops; -lazy_static! { - static ref VAR_NONE: Var = Variant::None.into(); - static ref VAR_EMPTY_LIST: Var = Variant::List(List::new()).into(); - static ref VAR_EMPTY_STR: Var = Var::new(Variant::Str(Str::from_str("").unwrap())); -} - -// Macro to call v_list with vector arguments to construct instead of having to do v_list(&[...]) -#[allow(unused_macros)] -macro_rules! v_lst { - () => ( - $crate::values::var::v_empty_list() - ); - ($($x:expr),+ $(,)?) => ( - vec![$($x),+] - ); -} +pub use error::{Error, ErrorPack}; +pub use list::List; +pub use map::Map; +pub use objid::Objid; +use std::fmt::Debug; +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_floatr, v_int, v_list, + v_list_iter, v_map, v_none, v_obj, v_objid, v_str, v_string, Var, +}; +pub use variant::Variant; /// Integer encoding of values as represented in a `LambdaMOO` textdump, and by `bf_typeof` #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, FromRepr)] +#[allow(non_camel_case_types)] pub enum VarType { TYPE_INT = 0, TYPE_OBJ = 1, @@ -84,873 +65,120 @@ pub enum VarType { TYPE_MAP = 10, } -/// Var is our variant type / tagged union used to represent MOO's dynamically typed values. -#[derive(Clone)] -pub struct Var { - value: Variant, -} - -impl Var { - #[must_use] - pub fn new(value: Variant) -> Self { - Self { value } - } - - #[must_use] - pub fn is_root(&self) -> bool { - match self.variant() { - Variant::Obj(o) => o.is_sysobj(), - _ => false, - } - } -} - -impl Encode for Var { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - // Use our own encoded form. - let encoded = encode(self); - bincode::encode_into_writer(encoded.as_ref(), encoder.writer(), *BINCODE_CONFIG)?; - Ok(()) - } -} - -impl Decode for Var { - fn decode(decoder: &mut D) -> Result { - let slc: Vec = bincode::decode_from_reader(decoder.reader(), *BINCODE_CONFIG)?; - Ok(decode(Bytes::from(slc))) - } -} - -impl<'de> BorrowDecode<'de> for Var { - fn borrow_decode>(decoder: &mut D) -> Result { - let slc: Vec = bincode::decode_from_reader(decoder.reader(), *BINCODE_CONFIG)?; - Ok(decode(Bytes::from(slc))) - } -} - -fn encoded_size(v: &Var) -> usize { - match v.variant() { - Variant::None => 1, - Variant::Str(s) => 1 + s.as_bytes().unwrap().len(), - Variant::Obj(o) => 1 + o.as_bytes().unwrap().len(), - Variant::Int(_) => 9, - Variant::Float(_) => 9, - Variant::Err(_) => 2, - Variant::List(l) => 1 + l.as_bytes().unwrap().len(), - Variant::Map(m) => 1 + m.as_bytes().unwrap().len(), - } -} - -fn encode(v: &Var) -> Bytes { - let mut buffer = vec![]; - // Push the type first. - buffer.push(v.type_id() as u8); - match v.variant() { - Variant::None => { - // Nothing to do. - } - Variant::Str(s) => { - buffer.extend_from_slice(s.as_bytes().unwrap().as_ref()); - } - Variant::Obj(o) => { - buffer.extend_from_slice(o.as_bytes().unwrap().as_ref()); - } - Variant::Int(i) => { - buffer.extend_from_slice(&i.to_le_bytes()); - } - Variant::Float(f) => { - buffer.extend_from_slice(&f.to_le_bytes()); - } - Variant::Err(e) => { - buffer.extend_from_slice(&[*e as u8]); - } - Variant::List(l) => { - buffer.extend_from_slice(l.as_bytes().unwrap().as_ref()); - } - Variant::Map(m) => { - buffer.extend_from_slice(m.as_bytes().unwrap().as_ref()); - } - } - Bytes::from(buffer) -} - -fn decode(s: Bytes) -> Var { - assert!(!s.is_empty()); - let type_id = s.as_ref()[0]; - let type_id = VarType::from_repr(type_id).expect("Invalid type id"); - let bytes = s.slice(1..); - match type_id { - VarType::TYPE_NONE => VAR_NONE.clone(), - VarType::TYPE_STR => { - let s = Str::from_bytes(bytes).unwrap(); - Var::new(Variant::Str(s)) - } - VarType::TYPE_OBJ => { - let o = Objid::from_bytes(bytes).unwrap(); - Var::new(Variant::Obj(o)) - } - VarType::TYPE_INT => { - let mut i_bytes = [0; 8]; - i_bytes.copy_from_slice(bytes.as_ref()); - let i = i64::from_le_bytes(i_bytes); - Var::new(Variant::Int(i)) - } - VarType::TYPE_FLOAT => { - let mut f_bytes = [0; 8]; - f_bytes.copy_from_slice(bytes.as_ref()); - let f = f64::from_le_bytes(f_bytes); - Var::new(Variant::Float(f)) - } - VarType::TYPE_ERR => { - let e = Error::from_repr(bytes.as_ref()[0]).unwrap(); - Var::new(Variant::Err(e)) - } - VarType::TYPE_LIST => { - let l = List::from_bytes(bytes).unwrap(); - Var::new(Variant::List(l)) - } - VarType::TYPE_MAP => { - let m = Map::from_bytes(bytes).unwrap(); - Var::new(Variant::Map(m)) - } - _ => panic!("Invalid type id: {:?}", type_id), - } -} - -// For now the byte buffer repr of a Var is its Bincode encoding, but this will likely change in -// the future. -impl AsByteBuffer for Var { - fn size_bytes(&self) -> usize { - encoded_size(self) - } - - fn with_byte_buffer R>(&self, mut f: F) -> Result - where - Self: Sized, - { - let encoded = encode(self); - Ok(f(encoded.as_ref())) - } - - fn make_copy_as_vec(&self) -> Result, EncodingError> - where - Self: Sized, - { - let encoded = encode(self); - Ok(encoded.as_ref().to_vec()) - } - - fn from_bytes(bytes: Bytes) -> Result - where - Self: Sized, - { - Ok(decode(bytes)) - } - - fn as_bytes(&self) -> Result { - let encoded = encode(self); - Ok(encoded) - } -} - -#[must_use] -pub fn v_bool(b: bool) -> Var { - Var::new(Variant::Int(i64::from(b))) -} - -#[must_use] -pub fn v_int(i: i64) -> Var { - Var::new(Variant::Int(i)) -} - -#[must_use] -pub fn v_float(f: f64) -> Var { - Var::new(Variant::Float(f)) -} - -#[must_use] -pub fn v_str(s: &str) -> Var { - Var::new(Variant::Str(Str::from_str(s).unwrap())) -} - -#[must_use] -pub fn v_string(s: String) -> Var { - Var::new(Variant::Str(Str::from_string(s))) -} - -#[must_use] -pub fn v_objid(o: Objid) -> Var { - Var::new(Variant::Obj(o)) -} - -#[must_use] -pub fn v_obj(o: i64) -> Var { - Var::new(Variant::Obj(Objid(o))) -} - -#[must_use] -pub fn v_err(e: Error) -> Var { - Var::new(Variant::Err(e)) -} - -#[must_use] -pub fn v_list(l: &[Var]) -> Var { - Var::new(Variant::List(List::from_slice(l))) -} - -#[must_use] -pub fn v_listv(l: Vec) -> Var { - Var::new(Variant::List(List::from_slice(&l))) -} - -#[must_use] -pub fn v_empty_list() -> Var { - VAR_EMPTY_LIST.clone() -} - -#[must_use] -pub fn v_empty_str() -> Var { - VAR_EMPTY_STR.clone() -} - -#[must_use] -pub fn v_none() -> Var { - VAR_NONE.clone() -} - -#[must_use] -pub fn v_empty_map() -> Var { - Var::new(Variant::Map(Map::new())) -} - -#[must_use] -pub fn v_map_pairs(pairs: &[(Var, Var)]) -> Var { - Var::new(Variant::Map(Map::from_pairs(pairs))) +/// Sequence index modes: 0 or 1 indexed. +/// This is used to determine how to handle index operations on sequences. Internally containers use +/// 0-based indexing, but MOO uses 1-based indexing, so we allow the user to choose. +#[derive(Clone, Copy, Debug)] +pub enum IndexMode { + ZeroBased, + OneBased, } -impl Var { - /// Return a reference to the inner variant that this Var is wrapping. - #[must_use] - #[inline] - pub fn variant(&self) -> &Variant { - &self.value - } - - #[must_use] - #[inline] - pub fn variant_mut(&mut self) -> &mut Variant { - &mut self.value - } - - /// Destroy the Var and return the inner variant that it was wrapping. - #[must_use] - #[inline] - pub fn take_variant(self) -> Variant { - self.value - } - - #[must_use] - pub fn type_id(&self) -> VarType { - match self.variant() { - Variant::None => VarType::TYPE_NONE, - Variant::Str(_) => VarType::TYPE_STR, - Variant::Obj(_) => VarType::TYPE_OBJ, - Variant::Int(_) => VarType::TYPE_INT, - Variant::Float(_) => VarType::TYPE_FLOAT, - Variant::Err(_) => VarType::TYPE_ERR, - Variant::List(_) => VarType::TYPE_LIST, - Variant::Map(_) => VarType::TYPE_MAP, - } - } - - #[must_use] - pub fn eq_case_sensitive(&self, other: &Self) -> bool { - match (self.variant(), other.variant()) { - (Variant::Str(l), Variant::Str(r)) => l.as_str().eq(r.as_str()), - _ => self == other, - } - } - - #[must_use] - pub fn to_literal(&self) -> String { - match self.variant() { - Variant::None => "None".to_string(), - Variant::Int(i) => i.to_string(), - Variant::Float(f) => format!("{f:?}"), - Variant::Str(s) => quote_str(s.as_str()), - Variant::Obj(o) => format!("{o}"), - Variant::List(l) => { - let mut result = String::new(); - result.push('{'); - for (i, v) in l.iter().enumerate() { - if i > 0 { - result.push_str(", "); - } - result.push_str(&v.to_literal()); - } - result.push('}'); - result - } - Variant::Map(m) => { - let mut result = String::new(); - result.push('['); - for (i, (k, v)) in m.iter().enumerate() { - if i > 0 { - result.push_str(", "); - } - result.push_str(&k.to_literal()); - result.push_str(" -> "); - result.push_str(&v.to_literal()); - } - result.push(']'); - result - } - Variant::Err(e) => e.name().to_string(), - } - } -} - -impl Display for Var { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_literal().as_str()) - } -} - -impl Debug for Var { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(self.to_literal().as_str()) - } -} - -impl PartialEq for Var { - fn eq(&self, other: &Self) -> bool { - match (self.variant(), other.variant()) { - (Variant::None, Variant::None) => true, - (Variant::Str(l), Variant::Str(r)) => l == r, - (Variant::Obj(l), Variant::Obj(r)) => l == r, - (Variant::Int(l), Variant::Int(r)) => l == r, - (Variant::Float(l), Variant::Float(r)) => l == r, - (Variant::Err(l), Variant::Err(r)) => l == r, - (Variant::List(l), Variant::List(r)) => l == r, - (Variant::Map(l), Variant::Map(r)) => l == r, - (Variant::None, _) => false, - (Variant::Str(_), _) => false, - (Variant::Obj(_), _) => false, - (Variant::Int(_), _) => false, - (Variant::Float(_), _) => false, - (Variant::Err(_), _) => false, - (Variant::List(_), _) => false, - (Variant::Map(_), _) => false, - } - } -} - -impl PartialOrd for Var { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Var { - fn cmp(&self, other: &Self) -> Ordering { - match (self.variant(), other.variant()) { - (Variant::None, Variant::None) => Ordering::Equal, - (Variant::Str(l), Variant::Str(r)) => l.cmp(r), - (Variant::Obj(l), Variant::Obj(r)) => l.cmp(r), - (Variant::Int(l), Variant::Int(r)) => l.cmp(r), - (Variant::Float(l), Variant::Float(r)) => R64::from(*l).cmp(&R64::from(*r)), - (Variant::Err(l), Variant::Err(r)) => l.cmp(r), - (Variant::List(l), Variant::List(r)) => l.cmp(r), - (Variant::Map(l), Variant::Map(r)) => l.cmp(r), - (Variant::None, _) => Ordering::Less, - (Variant::Str(_), _) => Ordering::Less, - (Variant::Obj(_), _) => Ordering::Less, - (Variant::Int(_), _) => Ordering::Less, - (Variant::Float(_), _) => Ordering::Less, - (Variant::Err(_), _) => Ordering::Less, - (Variant::List(_), _) => Ordering::Less, - (Variant::Map(_), _) => Ordering::Less, - } - } -} - -impl Hash for Var { - fn hash(&self, state: &mut H) { - let t = self.type_id() as u8; - t.hash(state); - match self.variant() { - Variant::None => {} - Variant::Str(s) => s.hash(state), - Variant::Obj(o) => o.hash(state), - Variant::Int(i) => i.hash(state), - Variant::Float(f) => R64::from(*f).hash(state), - Variant::Err(e) => e.hash(state), - Variant::List(l) => l.hash(state), - Variant::Map(m) => m.hash(state), - } - } -} - -impl Eq for Var {} - -impl<'a> From<&'a str> for Var { - fn from(s: &'a str) -> Self { - v_str(s) - } -} - -impl From for Var { - fn from(s: String) -> Self { - v_str(&s) - } -} - -impl From for Var { - fn from(i: i64) -> Self { - v_int(i) - } -} -impl From<&i64> for Var { - fn from(i: &i64) -> Self { - v_int(*i) - } -} - -impl From for Var { - fn from(f: f64) -> Self { - v_float(f) - } -} - -impl From for Var { - fn from(o: Objid) -> Self { - v_objid(o) - } -} - -impl From> for Var { - fn from(l: Vec) -> Self { - v_listv(l) - } -} - -impl From<[T; COUNT]> for Var -where - for<'a> Var: From<&'a T>, -{ - fn from(a: [T; COUNT]) -> Self { - v_list(&a.iter().map(|v| v.into()).collect::>()) - } -} - -impl From for Var { - fn from(e: Error) -> Self { - v_err(e) - } -} - -#[cfg(test)] -mod tests { - use std::cmp::Ordering; - - use crate::var::error::Error; - use crate::var::error::Error::{E_RANGE, E_TYPE}; - use crate::var::{v_empty_list, v_err, v_float, v_int, v_list, v_obj, v_str}; - - #[test] - fn test_add() { - assert_eq!(v_int(1).add(&v_int(2)), Ok(v_int(3))); - assert_eq!(v_int(1).add(&v_float(2.0)), Ok(v_float(3.0))); - assert_eq!(v_float(1.).add(&v_int(2)), Ok(v_float(3.))); - assert_eq!(v_float(1.).add(&v_float(2.)), Ok(v_float(3.))); - assert_eq!(v_str("a").add(&v_str("b")), Ok(v_str("ab"))); - } - - #[test] - fn test_sub() -> Result<(), Error> { - assert_eq!(v_int(1).sub(&v_int(2))?, v_int(-1)); - assert_eq!(v_int(1).sub(&v_float(2.))?, v_float(-1.)); - assert_eq!(v_float(1.).sub(&v_int(2))?, v_float(-1.)); - assert_eq!(v_float(1.).sub(&v_float(2.))?, v_float(-1.)); - Ok(()) - } - - #[test] - fn test_mul() -> Result<(), Error> { - assert_eq!(v_int(1).mul(&v_int(2))?, v_int(2)); - assert_eq!(v_int(1).mul(&v_float(2.))?, v_float(2.)); - assert_eq!(v_float(1.).mul(&v_int(2))?, v_float(2.)); - assert_eq!(v_float(1.).mul(&v_float(2.))?, v_float(2.)); - Ok(()) - } - - #[test] - fn test_div() -> Result<(), Error> { - assert_eq!(v_int(1).div(&v_int(2))?, v_int(0)); - assert_eq!(v_int(1).div(&v_float(2.))?, v_float(0.5)); - assert_eq!(v_float(1.).div(&v_int(2))?, v_float(0.5)); - assert_eq!(v_float(1.).div(&v_float(2.))?, v_float(0.5)); - Ok(()) - } - - #[test] - fn test_modulus() { - assert_eq!(v_int(1).modulus(&v_int(2)), Ok(v_int(1))); - assert_eq!(v_int(1).modulus(&v_float(2.)), Ok(v_float(1.))); - assert_eq!(v_float(1.).modulus(&v_int(2)), Ok(v_float(1.))); - assert_eq!(v_float(1.).modulus(&v_float(2.)), Ok(v_float(1.))); - assert_eq!(v_str("moop").modulus(&v_int(2)), Ok(v_err(E_TYPE))); - } - - #[test] - fn test_pow() { - assert_eq!(v_int(1).pow(&v_int(2)), Ok(v_int(1))); - assert_eq!(v_int(2).pow(&v_int(2)), Ok(v_int(4))); - assert_eq!(v_int(2).pow(&v_float(2.)), Ok(v_float(4.))); - assert_eq!(v_float(2.).pow(&v_int(2)), Ok(v_float(4.))); - assert_eq!(v_float(2.).pow(&v_float(2.)), Ok(v_float(4.))); - } - - #[test] - fn test_negative() { - assert_eq!(v_int(1).negative(), Ok(v_int(-1))); - assert_eq!(v_float(1.).negative(), Ok(v_float(-1.0))); - } - - #[test] - fn test_index() { - assert_eq!(v_list(&[v_int(1), v_int(2)]).index(0), Ok(v_int(1))); - assert_eq!(v_list(&[v_int(1), v_int(2)]).index(1), Ok(v_int(2))); - assert_eq!(v_list(&[v_int(1), v_int(2)]).index(2), Ok(v_err(E_RANGE))); - assert_eq!(v_str("ab").index(0), Ok(v_str("a"))); - assert_eq!(v_str("ab").index(1), Ok(v_str("b"))); - assert_eq!(v_str("ab").index(2), Ok(v_err(E_RANGE))); - } - - #[test] - fn test_eq() { - assert_eq!(v_int(1), v_int(1)); - assert_eq!(v_float(1.), v_float(1.)); - assert_eq!(v_str("a"), v_str("a")); - assert_eq!(v_str("a"), v_str("A")); - assert_eq!(v_list(&[v_int(1), v_int(2)]), v_list(&[v_int(1), v_int(2)])); - assert_eq!(v_obj(1), v_obj(1)); - assert_eq!(v_err(E_TYPE), v_err(E_TYPE)); - } - - #[test] - fn test_ne() { - assert_ne!(v_int(1), v_int(2)); - assert_ne!(v_float(1.), v_float(2.)); - assert_ne!(v_str("a"), v_str("b")); - assert_ne!(v_list(&[v_int(1), v_int(2)]), v_list(&[v_int(1), v_int(3)])); - assert_ne!(v_obj(1), v_obj(2)); - assert_ne!(v_err(E_TYPE), v_err(E_RANGE)); - } - - #[test] - fn test_lt() { - assert!(v_int(1) < v_int(2)); - assert!(v_float(1.) < v_float(2.)); - assert!(v_str("a") < v_str("b")); - assert!(v_list(&[v_int(1), v_int(2)]) < v_list(&[v_int(1), v_int(3)])); - assert!(v_obj(1) < v_obj(2)); - assert!(v_err(E_TYPE) < v_err(E_RANGE)); - } - - #[test] - fn test_le() { - assert!(v_int(1) <= v_int(2)); - assert!(v_float(1.) <= v_float(2.)); - assert!(v_str("a") <= v_str("b")); - assert!(v_list(&[v_int(1), v_int(2)]) <= v_list(&[v_int(1), v_int(3)])); - assert!(v_obj(1) <= v_obj(2)); - assert!(v_err(E_TYPE) <= v_err(E_RANGE)); - } - - #[test] - fn test_gt() { - assert!(v_int(2) > v_int(1)); - assert!(v_float(2.) > v_float(1.)); - assert!(v_str("b") > v_str("a")); - assert!(v_list(&[v_int(1), v_int(3)]) > v_list(&[v_int(1), v_int(2)])); - assert!(v_obj(2) > v_obj(1)); - assert!(v_err(E_RANGE) > v_err(E_TYPE)); - } - - #[test] - fn test_ge() { - assert!(v_int(2) >= v_int(1)); - assert!(v_float(2.) >= v_float(1.)); - assert!(v_str("b") >= v_str("a")); - assert!(v_list(&[v_int(1), v_int(3)]) >= v_list(&[v_int(1), v_int(2)])); - assert!(v_obj(2) >= v_obj(1)); - assert!(v_err(E_RANGE) >= v_err(E_TYPE)); - } - - #[test] - fn test_partial_cmp() { - assert_eq!(v_int(1).partial_cmp(&v_int(1)), Some(Ordering::Equal)); - assert_eq!(v_float(1.).partial_cmp(&v_float(1.)), Some(Ordering::Equal)); - assert_eq!(v_str("a").partial_cmp(&v_str("a")), Some(Ordering::Equal)); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).partial_cmp(&v_list(&[v_int(1), v_int(2)])), - Some(Ordering::Equal) - ); - assert_eq!(v_obj(1).partial_cmp(&v_obj(1)), Some(Ordering::Equal)); - assert_eq!( - v_err(E_TYPE).partial_cmp(&v_err(E_TYPE)), - Some(Ordering::Equal) - ); - - assert_eq!(v_int(1).partial_cmp(&v_int(2)), Some(Ordering::Less)); - assert_eq!(v_float(1.).partial_cmp(&v_float(2.)), Some(Ordering::Less)); - assert_eq!(v_str("a").partial_cmp(&v_str("b")), Some(Ordering::Less)); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).partial_cmp(&v_list(&[v_int(1), v_int(3)])), - Some(Ordering::Less) - ); - assert_eq!(v_obj(1).partial_cmp(&v_obj(2)), Some(Ordering::Less)); - assert_eq!( - v_err(E_TYPE).partial_cmp(&v_err(E_RANGE)), - Some(Ordering::Less) - ); - - assert_eq!(v_int(2).partial_cmp(&v_int(1)), Some(Ordering::Greater)); - assert_eq!( - v_float(2.).partial_cmp(&v_float(1.)), - Some(Ordering::Greater) - ); - assert_eq!(v_str("b").partial_cmp(&v_str("a")), Some(Ordering::Greater)); - assert_eq!( - v_list(&[v_int(1), v_int(3)]).partial_cmp(&v_list(&[v_int(1), v_int(2)])), - Some(Ordering::Greater) - ); - assert_eq!(v_obj(2).partial_cmp(&v_obj(1)), Some(Ordering::Greater)); - assert_eq!( - v_err(E_RANGE).partial_cmp(&v_err(E_TYPE)), - Some(Ordering::Greater) - ); - } - - #[test] - fn test_cmp() { - assert_eq!(v_int(1).cmp(&v_int(1)), Ordering::Equal); - assert_eq!(v_float(1.).cmp(&v_float(1.)), Ordering::Equal); - assert_eq!(v_str("a").cmp(&v_str("a")), Ordering::Equal); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).cmp(&v_list(&[v_int(1), v_int(2)])), - Ordering::Equal - ); - assert_eq!(v_obj(1).cmp(&v_obj(1)), Ordering::Equal); - assert_eq!(v_err(E_TYPE).cmp(&v_err(E_TYPE)), Ordering::Equal); - - assert_eq!(v_int(1).cmp(&v_int(2)), Ordering::Less); - assert_eq!(v_float(1.).cmp(&v_float(2.)), Ordering::Less); - assert_eq!(v_str("a").cmp(&v_str("b")), Ordering::Less); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).cmp(&v_list(&[v_int(1), v_int(3)])), - Ordering::Less - ); - assert_eq!(v_obj(1).cmp(&v_obj(2)), Ordering::Less); - assert_eq!(v_err(E_TYPE).cmp(&v_err(E_RANGE)), Ordering::Less); - - assert_eq!(v_int(2).cmp(&v_int(1)), Ordering::Greater); - assert_eq!(v_float(2.).cmp(&v_float(1.)), Ordering::Greater); - assert_eq!(v_str("b").cmp(&v_str("a")), Ordering::Greater); - assert_eq!( - v_list(&[v_int(1), v_int(3)]).cmp(&v_list(&[v_int(1), v_int(2)])), - Ordering::Greater - ); - assert_eq!(v_obj(2).cmp(&v_obj(1)), Ordering::Greater); - assert_eq!(v_err(E_RANGE).cmp(&v_err(E_TYPE)), Ordering::Greater); - } - - #[test] - fn test_partial_ord() { - assert_eq!(v_int(1).partial_cmp(&v_int(1)).unwrap(), Ordering::Equal); - assert_eq!( - v_float(1.).partial_cmp(&v_float(1.)).unwrap(), - Ordering::Equal - ); - assert_eq!( - v_str("a").partial_cmp(&v_str("a")).unwrap(), - Ordering::Equal - ); - assert_eq!( - v_list(&[v_int(1), v_int(2)]) - .partial_cmp(&v_list(&[v_int(1), v_int(2)])) - .unwrap(), - Ordering::Equal - ); - assert_eq!(v_obj(1).partial_cmp(&v_obj(1)).unwrap(), Ordering::Equal); - assert_eq!( - v_err(E_TYPE).partial_cmp(&v_err(E_TYPE)).unwrap(), - Ordering::Equal - ); - - assert_eq!(v_int(1).partial_cmp(&v_int(2)).unwrap(), Ordering::Less); - assert_eq!( - v_float(1.).partial_cmp(&v_float(2.)).unwrap(), - Ordering::Less - ); - assert_eq!(v_str("a").partial_cmp(&v_str("b")).unwrap(), Ordering::Less); - assert_eq!( - v_list(&[v_int(1), v_int(2)]) - .partial_cmp(&v_list(&[v_int(1), v_int(3)])) - .unwrap(), - Ordering::Less - ); - assert_eq!(v_obj(1).partial_cmp(&v_obj(2)).unwrap(), Ordering::Less); - assert_eq!( - v_err(E_TYPE).partial_cmp(&v_err(E_RANGE)).unwrap(), - Ordering::Less - ); - - assert_eq!(v_int(2).partial_cmp(&v_int(1)).unwrap(), Ordering::Greater); - assert_eq!( - v_float(2.).partial_cmp(&v_float(1.)).unwrap(), - Ordering::Greater - ); - assert_eq!( - v_str("b").partial_cmp(&v_str("a")).unwrap(), - Ordering::Greater - ); - assert_eq!( - v_list(&[v_int(1), v_int(3)]) - .partial_cmp(&v_list(&[v_int(1), v_int(2)])) - .unwrap(), - Ordering::Greater - ); - assert_eq!(v_obj(2).partial_cmp(&v_obj(1)).unwrap(), Ordering::Greater); - assert_eq!( - v_err(E_RANGE).partial_cmp(&v_err(E_TYPE)).unwrap(), - Ordering::Greater - ); - } - - #[test] - fn test_ord() { - assert_eq!(v_int(1).cmp(&v_int(1)), Ordering::Equal); - assert_eq!(v_float(1.).cmp(&v_float(1.)), Ordering::Equal); - assert_eq!(v_str("a").cmp(&v_str("a")), Ordering::Equal); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).cmp(&v_list(&[v_int(1), v_int(2)])), - Ordering::Equal - ); - assert_eq!(v_obj(1).cmp(&v_obj(1)), Ordering::Equal); - assert_eq!(v_err(E_TYPE).cmp(&v_err(E_TYPE)), Ordering::Equal); - - assert_eq!(v_int(1).cmp(&v_int(2)), Ordering::Less); - assert_eq!(v_float(1.).cmp(&v_float(2.)), Ordering::Less); - assert_eq!(v_str("a").cmp(&v_str("b")), Ordering::Less); - assert_eq!( - v_list(&[v_int(1), v_int(2)]).cmp(&v_list(&[v_int(1), v_int(3)])), - Ordering::Less - ); - assert_eq!(v_obj(1).cmp(&v_obj(2)), Ordering::Less); - assert_eq!(v_err(E_TYPE).cmp(&v_err(E_RANGE)), Ordering::Less); - - assert_eq!(v_int(2).cmp(&v_int(1)), Ordering::Greater); - assert_eq!(v_float(2.).cmp(&v_float(1.)), Ordering::Greater); - assert_eq!(v_str("b").cmp(&v_str("a")), Ordering::Greater); - assert_eq!( - v_list(&[v_int(1), v_int(3)]).cmp(&v_list(&[v_int(1), v_int(2)])), - Ordering::Greater - ); - assert_eq!(v_obj(2).cmp(&v_obj(1)), Ordering::Greater); - assert_eq!(v_err(E_RANGE).cmp(&v_err(E_TYPE)), Ordering::Greater); - } - - #[test] - fn test_is_true() { - assert!(v_int(1).is_true()); - assert!(v_float(1.).is_true()); - assert!(v_str("a").is_true()); - assert!(v_list(&[v_int(1), v_int(2)]).is_true()); - assert!(!v_obj(1).is_true()); - assert!(!v_err(E_TYPE).is_true()); - } - - #[test] - fn test_listrangeset() { - let base = v_list(&[v_int(1), v_int(2), v_int(3), v_int(4)]); - - // {1,2,3,4}[1..2] = {"a", "b", "c"} => {1, "a", "b", "c", 4} - let value = v_list(&[v_str("a"), v_str("b"), v_str("c")]); - let expected = v_list(&[v_int(1), v_str("a"), v_str("b"), v_str("c"), v_int(4)]); - assert_eq!(base.range_set(value, 2, 3).unwrap(), expected); - - // {1,2,3,4}[1..2] = {"a"} => {1, "a", 4} - let value = v_list(&[v_str("a")]); - let expected = v_list(&[v_int(1), v_str("a"), v_int(4)]); - assert_eq!(base.range_set(value, 2, 3).unwrap(), expected); - - // {1,2,3,4}[1..2] = {} => {1,4} - let value = v_empty_list(); - let expected = v_list(&[v_int(1), v_int(4)]); - assert_eq!(base.range_set(value, 2, 3).unwrap(), expected); - - // {1,2,3,4}[1..2] = {"a", "b"} => {1, "a", "b", 4} - let value = v_list(&[v_str("a"), v_str("b")]); - let expected = v_list(&[v_int(1), v_str("a"), v_str("b"), v_int(4)]); - assert_eq!(base.range_set(value, 2, 3).unwrap(), expected); - } - - #[test] - fn test_strrangeset() { - // Test interior insertion - let base = v_str("12345"); - let value = v_str("abc"); - let expected = v_str("1abc45"); - let result = base.range_set(value, 2, 3); - assert_eq!(result, Ok(expected)); - - // Test interior replacement - let base = v_str("12345"); - let value = v_str("ab"); - let expected = v_str("1ab45"); - let result = base.range_set(value, 2, 3); - assert_eq!(result, Ok(expected)); - - // Test interior deletion - let base = v_str("12345"); - let value = v_str(""); - let expected = v_str("145"); - let result = base.range_set(value, 2, 3); - assert_eq!(result, Ok(expected)); - - // Test interior subtraction - let base = v_str("12345"); - let value = v_str("z"); - let expected = v_str("1z45"); - let result = base.range_set(value, 2, 3); - assert_eq!(result, Ok(expected)); - } - - #[test] - fn test_range() -> Result<(), Error> { - // test on integer list - let int_list = v_list(&[1.into(), 2.into(), 3.into(), 4.into(), 5.into()]); - assert_eq!( - int_list.range(2, 4)?, - v_list(&[2.into(), 3.into(), 4.into()]) - ); - - // test on string - let string = v_str("hello world"); - assert_eq!(string.range(2, 7)?, v_str("ello w")); - - // range with upper higher than lower, moo returns empty list for this (!) - let empty_list = v_empty_list(); - assert_eq!(empty_list.range(1, 0), Ok(v_empty_list())); - // test on out of range - let int_list = v_list(&[1.into(), 2.into(), 3.into()]); - assert_eq!(int_list.range(2, 4), Ok(v_err(E_RANGE))); - // test on type mismatch - let var_int = v_int(10); - assert_eq!(var_int.range(1, 5), Ok(v_err(E_TYPE))); - - Ok(()) - } +impl IndexMode { + pub fn adjust_i64(&self, index: i64) -> isize { + match self { + IndexMode::ZeroBased => index as isize, + IndexMode::OneBased => (index - 1) as isize, + } + } + + pub fn adjust_isize(&self, index: isize) -> isize { + match self { + IndexMode::ZeroBased => index, + IndexMode::OneBased => index - 1, + } + } + + pub fn reverse_adjust_isize(&self, index: isize) -> isize { + match self { + IndexMode::ZeroBased => index, + IndexMode::OneBased => index + 1, + } + } + + pub fn reverse_adjust_i64(&self, index: i64) -> isize { + match self { + IndexMode::ZeroBased => index as isize, + IndexMode::OneBased => (index + 1) as isize, + } + } +} + +pub enum TypeClass<'a> { + Sequence(&'a dyn Sequence), + Associative(&'a dyn Associative), + Scalar, +} + +impl<'a> TypeClass<'a> { + fn is_sequence(&self) -> bool { + matches!(self, TypeClass::Sequence(_)) + } + + fn is_associative(&self) -> bool { + matches!(self, TypeClass::Associative(_)) + } + + fn is_scalar(&self) -> bool { + matches!(self, TypeClass::Scalar) + } +} + +pub trait Sequence { + /// Return true if the sequence is empty. + fn is_empty(&self) -> bool; + /// Return the length of the sequence. + fn len(&self) -> usize; + /// Check if the sequence contains the element, returning its offset if it does. + /// `case_sensitive` is used to determine if the comparison should be case-sensitive. + /// (MOO case sensitivity is often false) + fn index_in(&self, value: &Var, case_sensitive: bool) -> Result, Error>; + /// Check if the sequence contains the element, returning true if it does. + fn contains(&self, value: &Var, case_sensitive: bool) -> Result; + /// Get the `index`nth element of the sequence. + fn index(&self, index: usize) -> Result; + /// Assign a new value to `index`nth element of the sequence. + fn index_set(&self, index: usize, value: &Var) -> Result; + // Take a copy, add a new value to the end, and return it. + fn push(&self, value: &Var) -> Result; + /// Insert a new value at `index` in the sequence. + fn insert(&self, index: usize, value: &Var) -> Result; + /// Return a sequence which is a subset of this sequence where the indices lay between `from` + /// and `to`, inclusive. + fn range(&self, from: isize, to: isize) -> Result; + /// Assign new values to the sequence where the indices lay between `from` and `to`, inclusive. + fn range_set(&self, from: isize, to: isize, with: &Var) -> Result; + /// Append the given sequence to this sequence. + fn append(&self, other: &Var) -> Result; + /// Remove the `index`nth element of the sequence and return it. + fn remove_at(&self, index: usize) -> Result; +} + +pub trait Associative { + /// Return true if the associative container is empty. + fn is_empty(&self) -> bool; + /// Return the number of key-value pairs in the associative container. + fn len(&self) -> usize; + /// Get the value associated with the given key. + fn index(&self, key: &Var) -> Result; + /// Find the position of the key in the associative container, that is, the offset of the key in + /// the list of keys. + /// `case_sensitive` is used to determine if the comparison should be case-sensitive. + /// (MOO case sensitivity is often false) + fn index_in(&self, key: &Var, case_sensitive: bool) -> Result, Error>; + /// Assign a new value to the given key. + fn index_set(&self, key: &Var, value: &Var) -> Result; + /// Return the key-value pairs in the associative container between the given `from` and `to` + fn range(&self, from: &Var, to: &Var) -> Result; + /// Assign new values to the key-value pairs in the associative container between the given `from` and `to` + fn range_set(&self, from: &Var, to: &Var, with: &Var) -> Result; + /// Return the keys in the associative container. + fn keys(&self) -> Vec; + /// Return the values in the associative container. + fn values(&self) -> Vec; + /// Check if the associative container contains the key, returning true if it does. + fn contains_key(&self, key: &Var, case_sensitive: bool) -> Result; + /// Return this map with the key/value pair removed. + /// Return the new map and the value that was removed, if any + fn remove(&self, key: &Var, case_sensitive: bool) -> (Var, Option); } diff --git a/crates/values/src/var/scalar.rs b/crates/values/src/var/scalar.rs new file mode 100644 index 00000000..9049814e --- /dev/null +++ b/crates/values/src/var/scalar.rs @@ -0,0 +1,221 @@ +// 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 . +// + +use crate::var::var::{v_err, v_float, v_int, Var}; +use crate::var::variant::Variant; +use crate::var::Error; +use crate::var::Error::{E_INVARG, E_TYPE}; +use num_traits::ToPrimitive; +use paste::paste; +use std::ops::{Div, Mul, Neg, Sub}; + +macro_rules! binary_numeric_coercion_op { + ($op:tt ) => { + pub fn $op(&self, v: &Var) -> Result { + match (self.variant(), v.variant()) { + (Variant::Float(l), Variant::Float(r)) => { + Ok(v_float(l.to_f64().unwrap().$op(r.to_f64().unwrap()))) + } + (Variant::Int(l), Variant::Int(r)) => { + paste! { l.[](*r).map(v_int).ok_or(E_INVARG) } + } + (Variant::Float(l), Variant::Int(r)) => { + Ok(v_float(l.to_f64().unwrap().$op(*r as f64))) + } + (Variant::Int(l), Variant::Float(r)) => { + Ok(v_float((*l as f64).$op(r.to_f64().unwrap()))) + } + (_, _) => Ok(v_err(E_TYPE)), + } + } + }; +} + +impl Var { + binary_numeric_coercion_op!(mul); + binary_numeric_coercion_op!(div); + binary_numeric_coercion_op!(sub); + + pub fn add(&self, v: &Self) -> Result { + match (self.variant(), v.variant()) { + (Variant::Float(l), Variant::Float(r)) => { + Ok(v_float(l.to_f64().unwrap() + r.to_f64().unwrap())) + } + (Variant::Int(l), Variant::Int(r)) => l.checked_add(*r).map(v_int).ok_or(E_INVARG), + (Variant::Float(l), Variant::Int(r)) => Ok(v_float(l.to_f64().unwrap() + (*r as f64))), + (Variant::Int(l), Variant::Float(r)) => Ok(v_float(*l as f64 + r.to_f64().unwrap())), + (Variant::Str(s), Variant::Str(r)) => Ok(s.append(r)), + (_, _) => Ok(v_err(E_TYPE)), + } + } + + pub fn negative(&self) -> Result { + match self.variant() { + Variant::Int(l) => l.checked_neg().map(v_int).ok_or(E_INVARG), + Variant::Float(f) => Ok(v_float(f.neg())), + _ => Ok(v_err(E_TYPE)), + } + } + + pub fn modulus(&self, v: &Self) -> Result { + match (self.variant(), v.variant()) { + (Variant::Float(l), Variant::Float(r)) => Ok(v_float(*l % *r)), + (Variant::Int(l), Variant::Int(r)) => l.checked_rem(*r).map(v_int).ok_or(E_INVARG), + (Variant::Float(l), Variant::Int(r)) => Ok(v_float(l.to_f64().unwrap() % (*r as f64))), + (Variant::Int(l), Variant::Float(r)) => Ok(v_float(*l as f64 % (r.to_f64().unwrap()))), + (_, _) => Ok(v_err(E_TYPE)), + } + } + + pub fn pow(&self, v: &Self) -> Result { + match (self.variant(), v.variant()) { + (Variant::Float(l), Variant::Float(r)) => Ok(v_float(l.powf(*r))), + (Variant::Int(l), Variant::Int(r)) => { + let r = u32::try_from(*r).map_err(|_| E_INVARG)?; + l.checked_pow(r).map(v_int).ok_or(E_INVARG) + } + (Variant::Float(l), Variant::Int(r)) => Ok(v_float(l.powi(*r as i32))), + (Variant::Int(l), Variant::Float(r)) => Ok(v_float((*l as f64).powf(*r))), + (_, _) => Ok(v_err(E_TYPE)), + } + } + + pub fn is_sysobj(&self) -> bool { + matches!(self.variant(), Variant::Obj(o) if o.0 == 0) + } +} + +#[cfg(test)] +mod tests { + use crate::var::var::{v_err, v_float, v_int, v_list, v_obj, v_str}; + use crate::var::Error; + use crate::var::Error::{E_RANGE, E_TYPE}; + + #[test] + fn test_add() { + assert_eq!(v_int(1).add(&v_int(2)), Ok(v_int(3))); + assert_eq!(v_int(1).add(&v_float(2.0)), Ok(v_float(3.0))); + assert_eq!(v_float(1.).add(&v_int(2)), Ok(v_float(3.))); + assert_eq!(v_float(1.).add(&v_float(2.)), Ok(v_float(3.))); + assert_eq!(v_str("a").add(&v_str("b")), Ok(v_str("ab"))); + } + + #[test] + fn test_sub() -> Result<(), Error> { + assert_eq!(v_int(1).sub(&v_int(2))?, v_int(-1)); + assert_eq!(v_int(1).sub(&v_float(2.))?, v_float(-1.)); + assert_eq!(v_float(1.).sub(&v_int(2))?, v_float(-1.)); + assert_eq!(v_float(1.).sub(&v_float(2.))?, v_float(-1.)); + Ok(()) + } + + #[test] + fn test_mul() -> Result<(), Error> { + assert_eq!(v_int(1).mul(&v_int(2))?, v_int(2)); + assert_eq!(v_int(1).mul(&v_float(2.))?, v_float(2.)); + assert_eq!(v_float(1.).mul(&v_int(2))?, v_float(2.)); + assert_eq!(v_float(1.).mul(&v_float(2.))?, v_float(2.)); + Ok(()) + } + + #[test] + fn test_div() -> Result<(), Error> { + assert_eq!(v_int(1).div(&v_int(2))?, v_int(0)); + assert_eq!(v_int(1).div(&v_float(2.))?, v_float(0.5)); + assert_eq!(v_float(1.).div(&v_int(2))?, v_float(0.5)); + assert_eq!(v_float(1.).div(&v_float(2.))?, v_float(0.5)); + Ok(()) + } + + #[test] + fn test_modulus() { + assert_eq!(v_int(1).modulus(&v_int(2)), Ok(v_int(1))); + assert_eq!(v_int(1).modulus(&v_float(2.)), Ok(v_float(1.))); + assert_eq!(v_float(1.).modulus(&v_int(2)), Ok(v_float(1.))); + assert_eq!(v_float(1.).modulus(&v_float(2.)), Ok(v_float(1.))); + assert_eq!(v_str("moop").modulus(&v_int(2)), Ok(v_err(E_TYPE))); + } + + #[test] + fn test_pow() { + assert_eq!(v_int(1).pow(&v_int(2)), Ok(v_int(1))); + assert_eq!(v_int(2).pow(&v_int(2)), Ok(v_int(4))); + assert_eq!(v_int(2).pow(&v_float(2.)), Ok(v_float(4.))); + assert_eq!(v_float(2.).pow(&v_int(2)), Ok(v_float(4.))); + assert_eq!(v_float(2.).pow(&v_float(2.)), Ok(v_float(4.))); + } + + #[test] + fn test_negative() { + assert_eq!(v_int(1).negative(), Ok(v_int(-1))); + assert_eq!(v_float(1.).negative(), Ok(v_float(-1.0))); + } + + #[test] + fn test_eq() { + assert_eq!(v_int(1), v_int(1)); + assert_eq!(v_float(1.), v_float(1.)); + assert_eq!(v_str("a"), v_str("a")); + assert_eq!(v_str("a"), v_str("A")); + assert_eq!(v_list(&[v_int(1), v_int(2)]), v_list(&[v_int(1), v_int(2)])); + assert_eq!(v_obj(1), v_obj(1)); + assert_eq!(v_err(E_TYPE), v_err(E_TYPE)); + } + + #[test] + fn test_ne() { + assert_ne!(v_int(1), v_int(2)); + assert_ne!(v_float(1.), v_float(2.)); + assert_ne!(v_str("a"), v_str("b")); + assert_ne!(v_list(&[v_int(1), v_int(2)]), v_list(&[v_int(1), v_int(3)])); + assert_ne!(v_obj(1), v_obj(2)); + assert_ne!(v_err(E_TYPE), v_err(E_RANGE)); + } + + #[test] + fn test_lt() { + assert!(v_int(1) < v_int(2)); + assert!(v_float(1.) < v_float(2.)); + assert!(v_str("a") < v_str("b")); + assert!(v_obj(1) < v_obj(2)); + assert!(v_err(E_TYPE) < v_err(E_RANGE)); + } + + #[test] + fn test_le() { + assert!(v_int(1) <= v_int(2)); + assert!(v_float(1.) <= v_float(2.)); + assert!(v_str("a") <= v_str("b")); + assert!(v_obj(1) <= v_obj(2)); + assert!(v_err(E_TYPE) <= v_err(E_RANGE)); + } + + #[test] + fn test_gt() { + assert!(v_int(2) > v_int(1)); + assert!(v_float(2.) > v_float(1.)); + assert!(v_str("b") > v_str("a")); + assert!(v_obj(2) > v_obj(1)); + assert!(v_err(E_RANGE) > v_err(E_TYPE)); + } + + #[test] + fn test_ge() { + assert!(v_int(2) >= v_int(1)); + assert!(v_float(2.) >= v_float(1.)); + assert!(v_str("b") >= v_str("a")); + assert!(v_obj(2) >= v_obj(1)); + assert!(v_err(E_RANGE) >= v_err(E_TYPE)); + } +} diff --git a/crates/values/src/var/storage.rs b/crates/values/src/var/storage.rs new file mode 100644 index 00000000..a19c5c2e --- /dev/null +++ b/crates/values/src/var/storage.rs @@ -0,0 +1,201 @@ +// 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 . +// + +use crate::{AsByteBuffer, DecodingError, EncodingError, Var}; +use bincode::de::{BorrowDecoder, Decoder}; +use bincode::enc::Encoder; +use bincode::error::{DecodeError, EncodeError}; +use bincode::{BorrowDecode, Decode, Encode}; +use bytes::Bytes; +use flexbuffers::{Buffer, BuilderOptions}; +use std::ops::{Deref, Range}; +use std::str::Utf8Error; + +/// A wrapper around bytes::Bytes that implements the Buffer trait for flexbuffers. +#[derive(Clone)] +pub struct VarBuffer(pub Bytes); + +impl Buffer for VarBuffer { + type BufferString = String; + + fn slice(&self, range: Range) -> Option { + if range.start > range.end { + return None; + } + Some(Self(self.0.slice(range))) + } + + fn shallow_copy(&self) -> Self { + Self(self.0.clone()) + } + + fn empty() -> Self { + Self(Bytes::new()) + } + + fn empty_str() -> Self::BufferString { + "".to_string() + } + + fn buffer_str(&self) -> Result { + std::str::from_utf8(self.0.as_ref()).map(|s| s.to_string()) + } +} + +impl Deref for VarBuffer { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} + +impl Var { + pub fn to_bytes(&self) -> Bytes { + let mut builder = flexbuffers::Builder::new(BuilderOptions::default()); + let mut vb = builder.start_vector(); + self.variant().push_to(&mut vb); + vb.end_vector(); + Bytes::from(builder.take_buffer()) + } +} + +impl AsByteBuffer for Var { + fn size_bytes(&self) -> usize { + let buf = self.to_bytes(); + buf.len() + } + + fn with_byte_buffer R>(&self, mut f: F) -> Result { + let buf = self.to_bytes(); + Ok(f(buf.as_ref())) + } + + fn make_copy_as_vec(&self) -> Result, EncodingError> { + let buf = self.to_bytes(); + Ok(buf.to_vec()) + } + + fn from_bytes(bytes: Bytes) -> Result + where + Self: Sized, + { + Ok(Var::from_bytes(bytes)) + } + + fn as_bytes(&self) -> Result { + Ok(self.to_bytes()) + } +} + +impl Encode for Var { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + let buf = self.to_bytes().to_vec(); + buf.encode(encoder) + } +} + +impl Decode for Var { + fn decode(decoder: &mut D) -> Result { + let vec = Vec::::decode(decoder)?; + let bytes = Bytes::from(vec); + Ok(Var::from_bytes(bytes)) + } +} + +impl<'de> BorrowDecode<'de> for Var { + fn borrow_decode>(decoder: &mut D) -> Result { + let vec = Vec::::decode(decoder)?; + let bytes = Bytes::from(vec); + Ok(Var::from_bytes(bytes)) + } +} + +#[cfg(test)] +mod tests { + use crate::Error::E_TYPE; + use crate::{v_err, v_float, v_int, v_list, v_map, v_objid, v_str, Objid, Var}; + + #[test] + fn pack_unpack_int() { + let v = v_int(1); + let bytes = v.to_bytes(); + let v2 = Var::from_bytes(bytes); + assert_eq!(v, v2); + } + + #[test] + fn pack_unpack_float() { + let v = v_float(42.42); + let bytes = v.to_bytes(); + let v2 = Var::from_bytes(bytes); + assert_eq!(v, v2); + } + + #[test] + fn pack_unpack_error() { + let v = v_err(E_TYPE); + let bytes = v.to_bytes(); + let v2 = Var::from_bytes(bytes); + assert_eq!(v, v2); + } + #[test] + fn pack_unpack_string() { + let v = v_str("hello"); + let bytes = v.to_bytes(); + let v2 = Var::from_bytes(bytes); + assert_eq!(v, v2); + } + + #[test] + fn pack_unpack_objid() { + let v = v_objid(Objid(1)); + let bytes = v.to_bytes(); + let v2 = Var::from_bytes(bytes); + assert_eq!(v, v2); + } + #[test] + fn pack_unpack_list() { + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let bytes = l.to_bytes(); + let l2 = Var::from_bytes(bytes); + assert_eq!(l, l2); + } + + #[test] + fn pack_unpack_list_nested() { + let l = v_list(&[v_int(1), v_int(2), v_int(3)]); + let l2 = v_list(&[v_int(1), l.clone(), v_int(3)]); + let bytes = l2.to_bytes(); + let l3 = Var::from_bytes(bytes); + assert_eq!(l2, l3); + } + + #[test] + fn pack_unpack_map() { + let m = v_map(&[(v_int(1), v_int(2)), (v_int(3), v_int(4))]); + let bytes = m.to_bytes(); + let m2 = Var::from_bytes(bytes); + assert_eq!(m, m2); + } + + #[test] + fn pack_unpack_map_nested() { + let m = v_map(&[(v_int(1), v_int(2)), (v_int(3), v_int(4))]); + let m2 = v_map(&[(v_int(1), m.clone()), (v_int(3), m.clone())]); + let bytes = m2.to_bytes(); + let m3 = Var::from_bytes(bytes); + assert_eq!(m2, m3); + } +} diff --git a/crates/values/src/var/string.rs b/crates/values/src/var/string.rs index 26815a22..93893751 100644 --- a/crates/values/src/var/string.rs +++ b/crates/values/src/var/string.rs @@ -12,190 +12,443 @@ // this program. If not, see . // +use crate::var::storage::VarBuffer; +use crate::var::var::{v_str, Var}; +use crate::var::variant::Variant; +use crate::var::Error; +use crate::var::Error::{E_INVARG, E_RANGE, E_TYPE}; +use crate::var::Sequence; +use flexbuffers::Reader; +use num_traits::ToPrimitive; +use std::cmp::max; use std::fmt::{Display, Formatter}; use std::hash::Hash; -use std::ops::Range; -use std::str; -use std::str::FromStr; -use str::from_utf8; -use bincode::de::{BorrowDecoder, Decoder}; -use bincode::enc::Encoder; -use bincode::error::{DecodeError, EncodeError}; -use bincode::{BorrowDecode, Decode, Encode}; -use bytes::Bytes; +#[derive(Clone)] +pub struct Str { + // Reader must be boxed to avoid overfilling the stack. + pub(crate) reader: Box>, +} -use crate::var::error::Error; -use crate::var::{v_err, v_str, v_string, Var}; -use crate::{AsByteBuffer, DecodingError, EncodingError}; +impl Str { + pub fn as_string(&self) -> String { + self.reader.get_str().unwrap() + } -#[derive(Clone, Debug)] -pub struct Str(Bytes); + pub fn index_set(&self, index: usize, value: &Self) -> Result { + if value.len() != 1 { + return Err(E_INVARG); + } + let string = self.as_string(); + if index >= string.len() { + return Err(E_RANGE); + } + let mut s = string.clone(); + s.replace_range(index..=index, &value.as_string()); + Ok(Var::mk_str(&s)) + } -impl Str { - #[must_use] - pub fn from_string(s: String) -> Self { - let s = s.into_bytes(); - let sr = Bytes::from(s); - Self(sr) + pub fn append(&self, other: &Self) -> Var { + let mut s = self.as_string().clone(); + s.push_str(&other.as_string()); + Var::mk_str(&s) + } +} + +impl Sequence for Str { + fn is_empty(&self) -> bool { + self.as_string().is_empty() } - pub fn get(&self, offset: usize) -> Option { - let s = from_utf8(&self.0).unwrap(); - let r = s.get(offset..offset + 1); - r.map(v_str) + fn len(&self) -> usize { + self.as_string().len() } - #[must_use] - pub fn set(&self, offset: usize, r: &Self) -> Var { - if r.len() != 1 { - return v_err(Error::E_RANGE); + fn index(&self, index: usize) -> Result { + if index >= self.as_string().len() { + return Err(E_RANGE); } - if offset >= self.0.len() { - return v_err(Error::E_RANGE); + let c = self.as_string().chars().nth(index).unwrap(); + let c_str = c.to_string(); + let v = Var::mk_str(&c_str); + Ok(v) + } + + fn index_set(&self, index: usize, value: &Var) -> Result { + if index >= self.as_string().len() { + return Err(E_RANGE); + } + + // Index set for strings requires that the `value` being set is a string, otherwise it's. + // E_TYPE. + // And it must be a single character character, otherwise, E_INVARG is returned. + let value = match value.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; + + if value.len() != 1 { + return Err(E_INVARG); } - let mut s = from_utf8(&self.0).unwrap().to_string(); - s.replace_range(offset..=offset, r.as_str()); - v_string(s) + + let mut s = self.as_string().clone(); + s.replace_range(index..=index, value.as_string().as_str()); + Ok(Var::mk_str(&s)) } - pub fn get_range(&self, range: Range) -> Option { - let s = from_utf8(&self.0).unwrap(); - let r = s.get(range); - r.map(v_str) + fn insert(&self, index: usize, value: &Var) -> Result { + // If value is not a string, return E_TYPE. + let value = match value.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; + + let mut new_copy = self.as_string().clone(); + new_copy.insert_str(index, value.as_string().as_str()); + Ok(Var::mk_str(&new_copy)) } - #[must_use] - pub fn append(&self, other: &Self) -> Var { - v_string(format!( - "{}{}", - from_utf8(&self.0).unwrap(), - from_utf8(&other.0).unwrap() - )) + fn range(&self, from: isize, to: isize) -> Result { + if to < from { + return Ok(Var::mk_str("")); + } + let s = self.as_string(); + let start = max(from, 0) as usize; + let to = to as usize; + if start >= s.len() || to >= s.len() { + return Err(E_RANGE); + } + let s = s.get(start..=to).unwrap(); + Ok(Var::mk_str(s)) } - #[must_use] - pub fn append_str(&self, other: &str) -> Var { - v_string(format!("{}{}", from_utf8(&self.0).unwrap(), other)) + fn range_set(&self, from: isize, to: isize, with: &Var) -> Result { + let with_val = match with.variant() { + Variant::Str(s) => s, + _ => return Err(Error::E_TYPE), + }; + + let base_len = self.len(); + let from = from.to_usize().unwrap_or(0); + let to = to.to_usize().unwrap_or(0); + if to + 1 > base_len { + return Err(E_RANGE); + } + let base_str = self.as_string(); + let mut ans = base_str.get(..from).unwrap_or("").to_string(); + ans.push_str(&with_val.as_string()); + if to == 0 { + ans.push_str(&base_str); + } else { + ans.push_str(base_str.get(to + 1..).unwrap_or("")); + } + Ok(v_str(&ans)) } - #[must_use] - pub fn append_string(&self, other: String) -> Var { - v_string(format!("{}{}", from_utf8(&self.0).unwrap(), other)) + fn push(&self, value: &Var) -> Result { + let value = match value.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; + + let mut new_copy = self.as_string().clone(); + new_copy.push_str(value.as_string().as_str()); + Ok(Var::mk_str(&new_copy)) } - #[must_use] - pub fn len(&self) -> usize { - from_utf8(&self.0).unwrap().len() + fn append(&self, other: &Var) -> Result { + let other = match other.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; + + let mut new_copy = self.as_string().clone(); + new_copy.push_str(other.as_string().as_str()); + Ok(Var::mk_str(&new_copy)) } - #[must_use] - pub fn is_empty(&self) -> bool { - from_utf8(&self.0).unwrap().is_empty() + fn remove_at(&self, index: usize) -> Result { + if index >= self.as_string().len() { + return Err(E_RANGE); + } + + let mut new_copy = self.as_string().clone(); + new_copy.remove(index); + Ok(Var::mk_str(&new_copy)) } - #[must_use] - pub fn as_str(&self) -> &str { - from_utf8(&self.0).unwrap() + fn contains(&self, value: &Var, case_sensitive: bool) -> Result { + let value = match value.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; + + let s = self.as_string(); + let value = value.as_string(); + let contains = if case_sensitive { + s.contains(&value) + } else { + s.to_lowercase().contains(&value.to_lowercase()) + }; + + Ok(contains) } + fn index_in(&self, value: &Var, case_sensitive: bool) -> Result, Error> { + let value = match value.variant() { + Variant::Str(s) => s, + _ => return Err(E_TYPE), + }; - #[must_use] - pub fn substring(&self, range: Range) -> Self { - let s = from_utf8(&self.0).unwrap(); - let s = s.get(range).unwrap_or(""); - Self::from_string(s.to_string()) + let s: String = self.as_string(); + let value = value.as_string(); + let contains = if case_sensitive { + // Get the index of the substring in the string. + s.find(&value) + } else { + s.to_lowercase().find(&value.to_lowercase()) + }; + + Ok(contains) } } -// MOO's string comparisons are all case-insensitive. To get case-sensitive you have to use -// bf_is_member and bf_strcmp. +impl Display for Str { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_string()) + } +} impl PartialEq for Str { + // MOO strings are case-insensitive on comparison unless an explicit case sensitive comparison + // is needed. fn eq(&self, other: &Self) -> bool { - let s = from_utf8(&self.0).unwrap(); - let o = from_utf8(&other.0).unwrap(); - s.eq_ignore_ascii_case(o) + self.as_string().to_lowercase() == other.as_string().to_lowercase() } } + impl Eq for Str {} +impl PartialOrd for Str { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Str { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.as_string() + .to_lowercase() + .cmp(&other.as_string().to_lowercase()) + } +} + impl Hash for Str { fn hash(&self, state: &mut H) { - let s = from_utf8(&self.0).unwrap(); - s.to_lowercase().hash(state) + self.as_string().to_lowercase().hash(state) } } -impl FromStr for Str { - type Err = (); +#[cfg(test)] +mod tests { + use crate::v_bool; + use crate::var::var::{v_int, v_str, Var}; + use crate::var::variant::Variant; + use crate::var::IndexMode; - fn from_str(s: &str) -> Result { - let s = s.to_string(); - Ok(Self::from_string(s)) + #[test] + fn test_str_pack_unpack() { + let s = Var::mk_str("hello"); + match s.variant() { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "hello"), + _ => panic!("Expected string"), + } } -} -impl Display for Str { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", from_utf8(&self.0).unwrap())) + #[test] + fn test_string_is_funcs() { + let s = Var::mk_str("hello"); + assert!(s.is_true()); + assert!(s.is_sequence()); + assert!(!s.is_associative()); + assert!(!s.is_scalar()); + assert_eq!(s.len().unwrap(), 5); + assert!(!s.is_empty().unwrap()); + + let s = Var::mk_str(""); + assert!(!s.is_true()); + assert!(s.is_sequence()); + assert!(!s.is_associative()); + assert!(!s.is_scalar()); + assert_eq!(s.len().unwrap(), 0); + assert!(s.is_empty().unwrap()); } -} -impl Encode for Str { - fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { - let str = self.as_str().to_string(); - str.encode(encoder) + #[test] + fn test_string_equality_inquality() { + let s1 = Var::mk_str("hello"); + let s2 = Var::mk_str("hello"); + let s3 = Var::mk_str("world"); + let s4 = Var::mk_str("hello world"); + + assert_eq!(s1, s2); + assert_ne!(s1, s3); + assert_ne!(s1, s4); } -} -impl Decode for Str { - fn decode(decoder: &mut D) -> Result { - let str = String::decode(decoder)?; - Ok(Self::from_string(str)) + #[test] + fn test_string_index() { + let s = Var::mk_str("hello"); + let r = s.index(&Var::mk_integer(1), IndexMode::ZeroBased).unwrap(); + let r = r.variant(); + match r { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "e"), + _ => panic!("Expected string, got {:?}", r), + } } -} -impl<'de> BorrowDecode<'de> for Str { - fn borrow_decode>(decoder: &mut D) -> Result { - let str = String::borrow_decode(decoder)?; - Ok(Self::from_string(str)) + #[test] + fn test_string_index_set() { + let s = Var::mk_str("hello"); + let r = s + .index_set(&Var::mk_integer(1), &Var::mk_str("a"), IndexMode::ZeroBased) + .unwrap(); + let r = r.variant(); + match r { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "hallo"), + _ => panic!("Expected string, got {:?}", r), + } + + let fail_bad_index = s.index_set( + &Var::mk_integer(10), + &Var::mk_str("a"), + IndexMode::ZeroBased, + ); + assert!(fail_bad_index.is_err()); + assert_eq!(fail_bad_index.unwrap_err(), crate::var::Error::E_RANGE); } -} -impl Ord for Str { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let s = from_utf8(&self.0).unwrap(); - let o = from_utf8(&other.0).unwrap(); - s.to_lowercase().cmp(&o.to_lowercase()) + #[test] + fn test_one_index_slice() { + let s = v_str("hello world"); + let result = s.range(&v_int(2), &v_int(7), IndexMode::OneBased).unwrap(); + assert_eq!(result, v_str("ello w")); } -} -impl PartialOrd for Str { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) + #[test] + fn test_zero_index_slice() { + let s = v_str("hello world"); + let result = s.range(&v_int(1), &v_int(6), IndexMode::ZeroBased).unwrap(); + assert_eq!(result, v_str("ello w")); } -} -impl AsByteBuffer for Str { - fn size_bytes(&self) -> usize { - self.0.len() + #[test] + fn test_string_range_set() { + // Test a one-indexed assignment, comparing against a known MOO behaviour. + let base = v_str("mandalorian"); + let (start, end) = (v_int(4), v_int(7)); + let replace = v_str("bozo"); + let expected = v_str("manbozorian"); + let result = base.range_set(&start, &end, &replace, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + + // Test interior insertion + let base = v_str("12345"); + let value = v_str("abc"); + let expected = v_str("1abc45"); + let result = base.range_set(&v_int(2), &v_int(3), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); + + // Test interior replacement + let base = v_str("12345"); + let value = v_str("ab"); + let expected = v_str("1ab45"); + let result = base.range_set(&v_int(1), &v_int(2), &value, IndexMode::ZeroBased); + assert_eq!(result, Ok(expected)); + + // Test interior deletion + let base = v_str("12345"); + let value = v_str(""); + let expected = v_str("145"); + let result = base.range_set(&v_int(1), &v_int(2), &value, IndexMode::ZeroBased); + assert_eq!(result, Ok(expected)); + + // Test interior subtraction + let base = v_str("12345"); + let value = v_str("z"); + let expected = v_str("1z45"); + let result = base.range_set(&v_int(1), &v_int(2), &value, IndexMode::ZeroBased); + assert_eq!(result, Ok(expected)); } - fn with_byte_buffer R>(&self, mut f: F) -> Result { - Ok(f(self.0.as_ref())) + /// Moo supports this weird behavior + #[test] + fn test_string_range_set_odd_range_end() { + let base = v_str("me:words"); + let value = v_str(""); + let expected = v_str("me:words"); + let result = base.range_set(&v_int(1), &v_int(0), &value, IndexMode::OneBased); + assert_eq!(result, Ok(expected)); } - fn make_copy_as_vec(&self) -> Result, EncodingError> { - Ok(self.0.to_vec()) + #[test] + fn test_string_push() { + let s = Var::mk_str("hello"); + let r = s.push(&Var::mk_str(" world")).unwrap(); + let r = r.variant(); + match r { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "hello world"), + _ => panic!("Expected string, got {:?}", r), + } + } + + #[test] + fn test_string_append() { + let s1 = Var::mk_str("hello"); + let s2 = Var::mk_str(" world"); + let r = s1.append(&s2).unwrap(); + let r = r.variant(); + match r { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "hello world"), + _ => panic!("Expected string, got {:?}", r), + } + } + + #[test] + fn test_string_remove_at() { + let s = Var::mk_str("hello"); + let r = s + .remove_at(&Var::mk_integer(1), IndexMode::ZeroBased) + .unwrap(); + let r = r.variant(); + match r { + Variant::Str(s) => assert_eq!(s.as_string().as_str(), "hllo"), + _ => panic!("Expected string, got {:?}", r), + } + + let fail_bad_index = s.remove_at(&Var::mk_integer(10), IndexMode::ZeroBased); + assert!(fail_bad_index.is_err()); + assert_eq!(fail_bad_index.unwrap_err(), crate::var::Error::E_RANGE); } - fn from_bytes(bytes: Bytes) -> Result - where - Self: Sized, - { - Ok(Self(bytes)) + #[test] + fn test_string_contains() { + // Check both case-sensitive and case-insensitive + let s = Var::mk_str("hello"); + assert_eq!(s.contains(&Var::mk_str("ell"), true).unwrap(), v_bool(true)); + assert_eq!( + s.contains(&Var::mk_str("Ell"), false).unwrap(), + v_bool(true) + ); + assert_eq!( + s.contains(&Var::mk_str("world"), true).unwrap(), + v_bool(false) + ); } - fn as_bytes(&self) -> Result { - Ok(self.0.clone()) + #[test] + fn test_string_case_sensitive() { + let s = Var::mk_str("hello"); + let s2 = Var::mk_str("Hello"); + assert_eq!(s, s2); + assert!(!s.eq_case_sensitive(&s2)); } } diff --git a/crates/values/src/var/var.rs b/crates/values/src/var/var.rs new file mode 100644 index 00000000..84b0b0f8 --- /dev/null +++ b/crates/values/src/var/var.rs @@ -0,0 +1,607 @@ +// 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 . +// + +use crate::var::list::List; +use crate::var::storage::VarBuffer; +use crate::var::variant::Variant; +use crate::var::Associative; +use crate::var::Error::{E_INVARG, E_RANGE, E_TYPE}; +use crate::var::{map, IndexMode, Sequence, TypeClass}; +use crate::var::{Error, Objid, VarType}; +use bytes::Bytes; +use decorum::R64; +use flexbuffers::{BuilderOptions, Reader}; +use num_traits::ToPrimitive; +use std::cmp::{min, Ordering}; +use std::fmt::{Debug, Formatter}; + +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct Var(pub Variant); + +impl Debug for Var { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Var { + pub fn from_bytes(data: Bytes) -> Self { + let buf = VarBuffer(data); + let reader = Reader::get_root(buf).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn mk_integer(i: i64) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_INT as u8); + vb.push(i); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn mk_none() -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_NONE as u8); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn mk_str(s: &str) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_STR as u8); + vb.push(s); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn mk_float(f: R64) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_FLOAT as u8); + vb.push(f.to_f64().unwrap()); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn mk_error(e: Error) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_ERR as u8); + vb.push(e as u8); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn type_code(&self) -> VarType { + match self.variant() { + Variant::Int(_) => VarType::TYPE_INT, + Variant::Obj(_) => VarType::TYPE_OBJ, + Variant::Str(_) => VarType::TYPE_STR, + Variant::Err(_) => VarType::TYPE_ERR, + Variant::List(_) => VarType::TYPE_LIST, + Variant::None => VarType::TYPE_NONE, + Variant::Float(_) => VarType::TYPE_FLOAT, + Variant::Map(_) => VarType::TYPE_MAP, + } + } + + pub fn mk_list(values: &[Var]) -> Self { + List::build(values) + } + + pub fn mk_list_iter>(values: IT) -> Self { + Var::from_iter(values) + } + + pub fn mk_map(pairs: &[(Var, Var)]) -> Self { + map::Map::build(pairs.iter()) + } + + pub fn mk_object(o: Objid) -> Self { + let mut builder = flexbuffers::Builder::new(BuilderOptions::empty()); + let mut vb = builder.start_vector(); + vb.push(VarType::TYPE_OBJ as u8); + vb.push(o.0); + vb.end_vector(); + let buf = builder.take_buffer(); + let buf = Bytes::from(buf); + let reader = Reader::get_root(VarBuffer(buf)).unwrap(); + let v = Variant::from_reader(reader); + Var(v) + } + + pub fn variant(&self) -> &Variant { + &self.0 + } + + pub fn is_true(&self) -> bool { + match self.variant() { + Variant::None => false, + Variant::Obj(_) => false, + Variant::Int(i) => *i != 0, + Variant::Float(f) => *f != 0.0, + Variant::List(l) => !l.reader.is_empty(), + Variant::Str(s) => !s.as_string().is_empty(), + Variant::Map(m) => !m.reader.is_empty(), + Variant::Err(_) => false, + } + } + + /// 0-based index into a sequence type, or map lookup by key + /// If not a sequence, returns Err(E_INVARG) + /// For strings and lists, the index must be a positive integer, or Err(E_TYPE) + /// Range errors are Err(E_RANGE) + /// Otherwise returns the value + pub fn index(&self, index: &Var, index_mode: IndexMode) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let idx = match index.variant() { + Variant::Int(i) => { + let i = index_mode.adjust_i64(*i); + if i < 0 { + return Err(E_RANGE); + } + i as usize + } + _ => { + return Err(E_TYPE); + } + }; + + if idx >= s.len() { + return Err(E_RANGE); + } + + s.index(idx) + } + TypeClass::Associative(a) => a.index(index), + TypeClass::Scalar => Err(E_TYPE), + } + } + + /// Assign a new value to `index`nth element of the sequence, or to a key in an associative type. + pub fn index_set( + &self, + idx: &Self, + value: &Self, + index_mode: IndexMode, + ) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let idx = match idx.variant() { + Variant::Int(i) => { + let i = index_mode.adjust_i64(*i); + + if i < 0 { + return Err(E_RANGE); + } + i as usize + } + _ => return Err(E_INVARG), + }; + s.index_set(idx, value) + } + TypeClass::Associative(a) => a.index_set(idx, value), + TypeClass::Scalar => Err(E_TYPE), + } + } + + /// Insert a new value at `index` in a sequence only. + /// If the value is not a sequence, returns Err(E_INVARG). + /// To add a value to a map use `index_set`. + /// If the index is negative, it is treated as 0. + pub fn insert(&self, index: &Var, value: &Var, index_mode: IndexMode) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let index = match index.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + let index = if index < 0 { + 0 + } else { + min(index as usize, s.len()) + }; + + if index > s.len() { + return Err(E_RANGE); + } + + s.insert(index, value) + } + _ => Err(E_TYPE), + } + } + + pub fn range(&self, from: &Var, to: &Var, index_mode: IndexMode) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let from = match from.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + + let to = match to.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + + s.range(from, to) + } + TypeClass::Associative(a) => a.range(from, to), + TypeClass::Scalar => Err(E_TYPE), + } + } + + pub fn range_set( + &self, + from: &Var, + to: &Var, + with: &Var, + index_mode: IndexMode, + ) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let from = match from.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + + let to = match to.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + + s.range_set(from, to, with) + } + TypeClass::Associative(a) => a.range_set(from, to, with), + TypeClass::Scalar => Err(E_TYPE), + } + } + + pub fn append(&self, other: &Var) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => s.append(other), + _ => Err(E_TYPE), + } + } + + pub fn push(&self, value: &Var) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => s.push(value), + _ => Err(E_TYPE), + } + } + + pub fn contains(&self, value: &Var, case_sensitive: bool) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let c = s.contains(value, case_sensitive)?; + Ok(v_bool(c)) + } + TypeClass::Associative(a) => { + let c = a.contains_key(value, case_sensitive)?; + Ok(v_bool(c)) + } + TypeClass::Scalar => Err(E_INVARG), + } + } + + pub fn index_in( + &self, + value: &Var, + case_sensitive: bool, + index_mode: IndexMode, + ) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let idx = s + .index_in(value, case_sensitive)? + .map(|i| i as i64) + .unwrap_or(-1); + Ok(v_int(index_mode.reverse_adjust_isize(idx as isize) as i64)) + } + TypeClass::Associative(a) => { + let idx = a + .index_in(value, case_sensitive)? + .map(|i| i as i64) + .unwrap_or(-1); + Ok(v_int(index_mode.reverse_adjust_isize(idx as isize) as i64)) + } + _ => Err(E_INVARG), + } + } + + pub fn remove_at(&self, index: &Var, index_mode: IndexMode) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => { + let index = match index.variant() { + Variant::Int(i) => index_mode.adjust_i64(*i), + _ => return Err(E_INVARG), + }; + + if index < 0 { + return Err(E_RANGE); + } + + s.remove_at(index as usize) + } + _ => Err(E_TYPE), + } + } + + pub fn remove(&self, value: &Var, case_sensitive: bool) -> Result<(Var, Option), Error> { + match self.type_class() { + TypeClass::Associative(a) => Ok(a.remove(value, case_sensitive)), + _ => Err(E_INVARG), + } + } + + pub fn is_sequence(&self) -> bool { + self.type_class().is_sequence() + } + + pub fn is_associative(&self) -> bool { + self.type_class().is_associative() + } + + pub fn is_scalar(&self) -> bool { + self.type_class().is_scalar() + } + + pub fn is_string(&self) -> bool { + matches!(self.variant(), Variant::Str(_)) + } + + pub fn as_sequence(&self) -> Option<&dyn Sequence> { + match self.variant() { + Variant::List(l) => Some(l), + Variant::Str(s) => Some(s), + _ => None, + } + } + + pub fn is_empty(&self) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => Ok(s.is_empty()), + TypeClass::Associative(a) => Ok(a.is_empty()), + TypeClass::Scalar => Err(E_INVARG), + } + } + + pub fn eq_case_sensitive(&self, other: &Var) -> bool { + match (self.variant(), other.variant()) { + (Variant::Str(s1), Variant::Str(s2)) => s1.as_string() == s2.as_string(), + (Variant::List(l1), Variant::List(l2)) => { + if l1.len() != l2.len() { + return false; + } + let left_items = l1.iter(); + let right_items = l2.iter(); + for (left, right) in left_items.zip(right_items) { + if !left.eq_case_sensitive(&right) { + return false; + } + } + true + } + (Variant::Map(m1), Variant::Map(m2)) => { + if m1.len() != m2.len() { + return false; + } + let left_pairs = m1.iter(); + let right_pairs = m2.iter(); + for (left, right) in left_pairs.zip(right_pairs) { + if !left.0.eq_case_sensitive(&right.0) || !left.1.eq_case_sensitive(&right.1) { + return false; + } + } + true + } + _ => self.eq(other), + } + } + + pub fn cmp_case_sensitive(&self, other: &Var) -> Ordering { + match (self.variant(), other.variant()) { + (Variant::Str(s1), Variant::Str(s2)) => s1.as_string().cmp(&s2.as_string()), + _ => self.cmp(other), + } + } + + pub fn len(&self) -> Result { + match self.type_class() { + TypeClass::Sequence(s) => Ok(s.len()), + TypeClass::Associative(a) => Ok(a.len()), + TypeClass::Scalar => Err(E_INVARG), + } + } + + pub fn type_class(&self) -> TypeClass { + match self.variant() { + Variant::List(s) => TypeClass::Sequence(s), + Variant::Str(s) => TypeClass::Sequence(s), + Variant::Map(m) => TypeClass::Associative(m), + _ => TypeClass::Scalar, + } + } +} + +pub fn v_int(i: i64) -> Var { + Var::mk_integer(i) +} + +pub fn v_bool(b: bool) -> Var { + if b { + v_int(1) + } else { + v_int(0) + } +} + +pub fn v_none() -> Var { + // TODO lazy_static singleton + Var::mk_none() +} + +pub fn v_str(s: &str) -> Var { + Var::mk_str(s) +} + +pub fn v_string(s: String) -> Var { + Var::mk_str(&s) +} + +pub fn v_list(values: &[Var]) -> Var { + Var::mk_list(values) +} + +pub fn v_list_iter>(values: IT) -> Var { + Var::mk_list_iter(values) +} + +pub fn v_map(pairs: &[(Var, Var)]) -> Var { + Var::mk_map(pairs) +} + +pub fn v_float(f: f64) -> Var { + Var::mk_float(R64::from(f)) +} + +pub fn v_floatr(f: R64) -> Var { + Var::mk_float(f) +} + +pub fn v_err(e: Error) -> Var { + Var::mk_error(e) +} + +pub fn v_obj(o: i64) -> Var { + Var::mk_object(Objid(o)) +} + +pub fn v_objid(o: Objid) -> Var { + Var::mk_object(o) +} + +pub fn v_empty_list() -> Var { + // TODO: lazy static + v_list(&[]) +} + +pub fn v_empty_str() -> Var { + // TODO: lazy static + v_str("") +} + +pub fn v_empty_map() -> Var { + // TODO: lazy static + v_map(&[]) +} + +impl From for Var { + fn from(i: i64) -> Self { + Var::mk_integer(i) + } +} + +impl From<&str> for Var { + fn from(s: &str) -> Self { + Var::mk_str(s) + } +} + +impl From for Var { + fn from(s: String) -> Self { + Var::mk_str(&s) + } +} + +impl From for Var { + fn from(o: Objid) -> Self { + Var::mk_object(o) + } +} + +impl From for Var { + fn from(e: Error) -> Self { + Var::mk_error(e) + } +} + +#[cfg(test)] +mod tests { + use crate::var::var::Var; + use crate::var::variant::Variant; + + #[test] + fn test_int_pack_unpack() { + let i = Var::mk_integer(42); + + match i.variant() { + Variant::Int(i) => assert_eq!(*i, 42), + _ => panic!("Expected integer"), + } + } + + #[test] + fn test_float_pack_unpack() { + let f = Var::mk_float(42.0.into()); + + match f.variant() { + Variant::Float(f) => assert_eq!(*f, 42.0), + _ => panic!("Expected float"), + } + } + + #[test] + fn test_alpha_numeric_sort_order() { + // "a" should come after "6" + let six = Var::mk_integer(6); + let a = Var::mk_str("a"); + assert_eq!(six.cmp(&a), std::cmp::Ordering::Less); + + // and 9 before "a" as well + let nine = Var::mk_integer(9); + assert_eq!(nine.cmp(&a), std::cmp::Ordering::Less); + + // now in the other order. + assert_eq!(a.cmp(&six), std::cmp::Ordering::Greater); + assert_eq!(a.cmp(&nine), std::cmp::Ordering::Greater); + } +} diff --git a/crates/values/src/var/variant.rs b/crates/values/src/var/variant.rs index eab78a77..0dc14217 100644 --- a/crates/values/src/var/variant.rs +++ b/crates/values/src/var/variant.rs @@ -12,45 +12,250 @@ // this program. If not, see . // -use std::fmt::{Display, Formatter, Result as FmtResult}; - -use crate::var::error::Error; use crate::var::list::List; -use crate::var::map::Map; -use crate::var::objid::Objid; -use crate::var::string::Str; - -use super::Var; +use crate::var::storage::VarBuffer; +use crate::var::Associative; +use crate::var::{map, string, Sequence}; +use crate::var::{Error, Objid, VarType}; +use decorum::R64; +use flexbuffers::{Reader, VectorBuilder}; +use num_traits::ToPrimitive; +use std::cmp::Ordering; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; -#[derive(Clone, Debug)] +/// Our series of types +#[derive(Clone)] pub enum Variant { None, - Str(Str), Obj(Objid), Int(i64), Float(f64), - Err(Error), List(List), - Map(Map), + Str(string::Str), + Map(map::Map), + Err(Error), +} + +impl Hash for Variant { + fn hash(&self, state: &mut H) { + match self { + Variant::None => 0.hash(state), + Variant::Obj(o) => o.0.hash(state), + Variant::Int(i) => i.hash(state), + Variant::Float(f) => f.to_f64().unwrap().to_bits().hash(state), + Variant::List(l) => l.hash(state), + Variant::Str(s) => s.hash(state), + Variant::Map(m) => m.hash(state), + Variant::Err(e) => e.hash(state), + } + } +} + +impl Ord for Variant { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Variant::None, Variant::None) => Ordering::Equal, + (Variant::Obj(l), Variant::Obj(r)) => l.cmp(r), + (Variant::Int(l), Variant::Int(r)) => l.cmp(r), + (Variant::Float(l), Variant::Float(r)) => { + // For floats, we wrap in decorum first. + let l = R64::from(l.to_f64().unwrap()); + let r = R64::from(r.to_f64().unwrap()); + l.cmp(&r) + } + (Variant::List(l), Variant::List(r)) => l.cmp(r), + (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::None, _) => Ordering::Less, + (_, Variant::None) => Ordering::Greater, + (Variant::Obj(_), _) => Ordering::Less, + (_, Variant::Obj(_)) => Ordering::Greater, + (Variant::Int(_), _) => Ordering::Less, + (_, Variant::Int(_)) => Ordering::Greater, + (Variant::Float(_), _) => Ordering::Less, + (_, Variant::Float(_)) => Ordering::Greater, + (Variant::List(_), _) => Ordering::Less, + (_, Variant::List(_)) => Ordering::Greater, + (Variant::Str(_), _) => Ordering::Less, + (_, Variant::Str(_)) => Ordering::Greater, + (Variant::Map(_), _) => Ordering::Less, + (_, Variant::Map(_)) => Ordering::Greater, + } + } +} + +impl PartialOrd for Variant { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } -impl Display for Variant { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { +impl Debug for Variant { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - Self::None => write!(f, "None"), - Self::Str(s) => write!(f, "{s}"), - Self::Obj(o) => write!(f, "{o}"), - Self::Int(i) => write!(f, "{i}"), - Self::Float(fl) => write!(f, "{fl}"), - Self::Err(e) => write!(f, "{e}"), - Self::List(l) => write!(f, "{l}"), - Self::Map(m) => write!(f, "{m}"), + Variant::None => write!(f, "None"), + Variant::Obj(o) => write!(f, "Object({})", o.0), + Variant::Int(i) => write!(f, "Integer({})", i), + Variant::Float(fl) => write!(f, "Float({})", fl), + Variant::List(l) => { + // Items... + let r = l.reader.iter(); + let i: Vec<_> = r.map(Variant::from_reader).collect(); + write!(f, "List([size = {}, items = {:?}])", l.len(), i) + } + Variant::Str(s) => write!(f, "String({:?})", s.as_string()), + Variant::Map(m) => { + // Items... + let r = m.reader.iter(); + let i: Vec<_> = r.map(Variant::from_reader).collect(); + write!(f, "Map([size = {}, items = {:?}])", m.len(), i) + } + Variant::Err(e) => write!(f, "Error({:?})", e), } } } -impl From for Var { - fn from(val: Variant) -> Self { - Self::new(val) +impl Variant { + pub(crate) fn from_reader(vec: Reader) -> Self { + // Each Var is a vector of two elements, the type and the value. + let vec = vec.as_vector(); + + let mut vec_iter = vec.iter(); + let type_reader = vec_iter.next().unwrap(); + let t = type_reader.as_u8(); + let t = VarType::from_repr(t).unwrap(); + + // Now we can match on the type and pull out the value. + match t { + VarType::TYPE_NONE => Variant::None, + VarType::TYPE_INT => { + let v = vec_iter.next().unwrap(); + let i = v.as_i64(); + Self::Int(i) + } + VarType::TYPE_FLOAT => { + let v = vec_iter.next().unwrap(); + let f = v.as_f64(); + Self::Float(f) + } + VarType::TYPE_OBJ => { + let v = vec_iter.next().unwrap(); + let o = v.as_i64(); + Self::Obj(Objid(o)) + } + VarType::TYPE_STR => { + let v = vec_iter.next().unwrap(); + Self::Str(string::Str { + reader: Box::new(v), + }) + } + VarType::TYPE_ERR => { + // Error code encoded as u8 + let v = vec_iter.next().unwrap(); + let e = v.as_u8(); + let e = Error::from_repr(e).unwrap(); + Self::Err(e) + } + VarType::TYPE_LIST => { + let v = vec_iter.next().unwrap(); + let l = v.as_vector(); + Self::List(List { + reader: Box::new(l), + }) + } + VarType::TYPE_LABEL => { + unimplemented!("Labels are not supported in actual values") + } + VarType::TYPE_MAP => { + let v = vec_iter.next().unwrap(); + let m = v.as_vector(); + Self::Map(map::Map { + reader: Box::new(m), + }) + } + } + } + + /// Push a copy of Self into a flexbuffer + pub(crate) fn push_to(&self, item_vec: &mut VectorBuilder) { + match self { + Variant::None => { + item_vec.push(VarType::TYPE_NONE as u8); + } + Variant::Obj(o) => { + item_vec.push(VarType::TYPE_OBJ as u8); + item_vec.push(o.0); + } + Variant::Int(i) => { + item_vec.push(VarType::TYPE_INT as u8); + item_vec.push(*i); + } + Variant::Float(f) => { + item_vec.push(VarType::TYPE_FLOAT as u8); + item_vec.push(f.to_f64().unwrap()); + } + Variant::List(l) => { + item_vec.push(VarType::TYPE_LIST as u8); + let mut vb = item_vec.start_vector(); + // Then we iterate over the rest of the elements. + for i in 0..l.reader.len() { + let item_reader = l.reader.idx(i); + let v = Variant::from_reader(item_reader); + v.push_item(&mut vb); + } + vb.end_vector(); + } + Variant::Map(m) => { + item_vec.push(VarType::TYPE_MAP as u8); + let mut vb = item_vec.start_vector(); + let mut iter = m.reader.iter(); + // Now iterate over the pairs. + for _ in 0..m.len() { + let k = iter.next().unwrap(); + let v = iter.next().unwrap(); + let key = Variant::from_reader(k); + let value = Variant::from_reader(v); + key.push_item(&mut vb); + value.push_item(&mut vb); + } + vb.end_vector(); + } + Variant::Str(s) => { + item_vec.push(VarType::TYPE_STR as u8); + item_vec.push(s.as_string().as_str()); + } + Variant::Err(e) => { + item_vec.push(VarType::TYPE_ERR as u8); + item_vec.push(*e as u8); + } + } + } + + /// Push a copy along with an item vector + pub(crate) fn push_item(&self, item_vec: &mut VectorBuilder) { + let mut vb = item_vec.start_vector(); + self.push_to(&mut vb); + vb.end_vector(); } } + +impl PartialEq for Variant { + fn eq(&self, other: &Self) -> bool { + // If the types are different, they're not equal. + match (self, other) { + (Variant::Str(s), Variant::Str(o)) => s == o, + (Variant::Int(s), Variant::Int(o)) => s == o, + (Variant::Float(s), Variant::Float(o)) => s == o, + (Variant::Obj(s), Variant::Obj(o)) => s == o, + (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::None, Variant::None) => true, + _ => false, + } + } +} + +impl Eq for Variant {} diff --git a/crates/values/src/var/varops.rs b/crates/values/src/var/varops.rs deleted file mode 100644 index f0db2e2d..00000000 --- a/crates/values/src/var/varops.rs +++ /dev/null @@ -1,255 +0,0 @@ -// 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 . -// - -use std::ops::{Div, Mul, Neg, Sub}; - -use num_traits::Zero; -use paste::paste; - -use crate::var::error::Error; -use crate::var::error::Error::{E_INVARG, E_RANGE, E_TYPE}; -use crate::var::variant::Variant; -use crate::var::{v_empty_list, v_empty_str, v_listv, Var}; -use crate::var::{v_err, v_float, v_int}; - -macro_rules! binary_numeric_coercion_op { - ($op:tt ) => { - pub fn $op(&self, v: &Var) -> Result { - match (self.variant(), v.variant()) { - (Variant::Float(l), Variant::Float(r)) => Ok(v_float(l.$op(*r))), - (Variant::Int(l), Variant::Int(r)) => { - paste! { l.[](*r).map(v_int).ok_or(E_INVARG) } - } - (Variant::Float(l), Variant::Int(r)) => Ok(v_float(l.$op(*r as f64))), - (Variant::Int(l), Variant::Float(r)) => Ok(v_float((*l as f64).$op(*r))), - (_, _) => Ok(v_err(E_TYPE)), - } - } - }; -} - -impl Var { - #[must_use] - pub fn is_true(&self) -> bool { - match self.variant() { - Variant::Str(s) => !s.is_empty(), - Variant::Int(i) => *i != 0, - Variant::Float(f) => !f.is_zero(), - Variant::List(l) => !l.is_empty(), - Variant::Map(m) => !m.is_empty(), - _ => false, - } - } - - pub fn index_set(&mut self, i: usize, value: Self) -> Result { - match self.variant_mut() { - Variant::List(l) => { - if !i < l.len() { - return Err(E_RANGE); - } - - Ok(l.set(i, value)) - } - Variant::Str(s) => { - if !i < s.len() { - return Err(E_RANGE); - } - - let Variant::Str(value) = value.variant() else { - return Err(E_INVARG); - }; - - if value.len() != 1 { - return Err(E_INVARG); - } - - Ok(s.set(i, value)) - } - _ => Err(E_TYPE), - } - } - - /// 1-indexed position of the first occurrence of `v` in `self`, or `E_TYPE` if `self` is not a - /// list. - // TODO: Make Var consistent on 0-indexing vs 1-indexing - // Various places have 1-indexing polluting the Var API, but in others we - // assume 0-indexing and adjust in the opcodes. 0 indexing should be done in Var, and opcodes and builtins - // should be the ones to adjust 1-indexing. - // Examples: index_in, range, rangeset - #[must_use] - pub fn index_in(&self, v: &Self) -> Self { - let position = match self.variant() { - Variant::List(l) => l.iter().position(|x| x == *v), - Variant::Map(m) => m.iter().position(|(_k, x)| x == v), - _ => { - return v_err(E_TYPE); - } - }; - - v_int(position.map(|pos| pos + 1).unwrap_or(0) as i64) - } - - binary_numeric_coercion_op!(mul); - binary_numeric_coercion_op!(div); - binary_numeric_coercion_op!(sub); - - pub fn add(&self, v: &Self) -> Result { - match (self.variant(), v.variant()) { - (Variant::Float(l), Variant::Float(r)) => Ok(v_float(*l + *r)), - (Variant::Int(l), Variant::Int(r)) => l.checked_add(*r).map(v_int).ok_or(E_INVARG), - (Variant::Float(l), Variant::Int(r)) => Ok(v_float(*l + (*r as f64))), - (Variant::Int(l), Variant::Float(r)) => Ok(v_float(*l as f64 + *r)), - (Variant::Str(s), Variant::Str(r)) => Ok(s.append(r)), - (_, _) => Ok(v_err(E_TYPE)), - } - } - - pub fn negative(&self) -> Result { - match self.variant() { - Variant::Int(l) => l.checked_neg().map(v_int).ok_or(E_INVARG), - Variant::Float(f) => Ok(v_float(f.neg())), - _ => Ok(v_err(E_TYPE)), - } - } - - pub fn modulus(&self, v: &Self) -> Result { - match (self.variant(), v.variant()) { - (Variant::Float(l), Variant::Float(r)) => Ok(v_float(*l % *r)), - (Variant::Int(l), Variant::Int(r)) => l.checked_rem(*r).map(v_int).ok_or(E_INVARG), - (Variant::Float(l), Variant::Int(r)) => Ok(v_float(*l % (*r as f64))), - (Variant::Int(l), Variant::Float(r)) => Ok(v_float(*l as f64 % (*r))), - (_, _) => Ok(v_err(E_TYPE)), - } - } - - pub fn pow(&self, v: &Self) -> Result { - match (self.variant(), v.variant()) { - (Variant::Float(l), Variant::Float(r)) => Ok(v_float(l.powf(*r))), - (Variant::Int(l), Variant::Int(r)) => { - let r = u32::try_from(*r).map_err(|_| E_INVARG)?; - l.checked_pow(r).map(v_int).ok_or(E_INVARG) - } - (Variant::Float(l), Variant::Int(r)) => Ok(v_float(l.powi(*r as i32))), - (Variant::Int(l), Variant::Float(r)) => Ok(v_float((*l as f64).powf(*r))), - (_, _) => Ok(v_err(E_TYPE)), - } - } - - pub fn len(&self) -> Result { - match self.variant() { - Variant::Str(s) => Ok(v_int(s.len() as i64)), - Variant::List(l) => Ok(v_int(l.len() as i64)), - _ => Ok(v_err(E_TYPE)), - } - } - - pub fn index(&self, idx: usize) -> Result { - match self.variant() { - Variant::List(l) => match l.get(idx) { - None => Ok(v_err(E_RANGE)), - Some(v) => Ok(v.clone()), - }, - Variant::Str(s) => match s.get(idx) { - None => Ok(v_err(E_RANGE)), - Some(v) => Ok(v), - }, - _ => Ok(v_err(E_TYPE)), - } - } - - pub fn range(&self, from: i64, to: i64) -> Result { - match self.variant() { - Variant::Str(s) => { - let len = s.len() as i64; - if to < from { - return Ok(v_empty_str()); - } - if from <= 0 || from > len + 1 || to > len { - return Ok(v_err(E_RANGE)); - } - let (from, to) = (from as usize, to as usize); - Ok(s.get_range(from - 1..to).unwrap()) - } - Variant::List(l) => { - let len = l.len() as i64; - if to < from { - return Ok(v_empty_list()); - } - if from <= 0 || from > len + 1 || to < 1 || to > len { - return Ok(v_err(E_RANGE)); - } - let mut res = Vec::with_capacity((to - from + 1) as usize); - for i in from..=to { - let v = l.get((i - 1) as usize).unwrap(); - res.push(v); - } - Ok(v_listv(res)) - } - _ => Ok(v_err(E_TYPE)), - } - } - - // TODO: consolidate Map logic into here. - pub fn range_set(&self, value: Self, from: i64, to: i64) -> Result { - let (base_len, val_len) = match (self.variant(), value.variant()) { - (Variant::Str(base_str), Variant::Str(val_str)) => { - (base_str.len() as i64, val_str.len() as i64) - } - (Variant::List(base_list), Variant::List(val_list)) => { - (base_list.len() as i64, val_list.len() as i64) - } - _ => return Ok(v_err(E_TYPE)), - }; - - // In range assignments only, MOO treats "0" for start (and even negative values) as - // "start of range." - // So we'll just min 'from' to '1' here. - // Does not hold for range retrievals. - let from = from.max(1); - if from > base_len + 1 || to > base_len { - return Ok(v_err(E_RANGE)); - } - - let lenleft = if from > 1 { from - 1 } else { 0 }; - let lenmiddle = val_len; - let lenright = if base_len > to { base_len - to } else { 0 }; - let newsize = lenleft + lenmiddle + lenright; - - let (from, to) = (from as usize, to as usize); - let ans = match (self.variant(), value.variant()) { - (Variant::Str(base_str), Variant::Str(_value_str)) => { - let ans = base_str.get_range(0..from - 1).unwrap_or_else(v_empty_str); - let ans = ans.add(&value)?; - - ans.add( - &base_str - .get_range(to..base_str.len()) - .unwrap_or_else(v_empty_str), - )? - } - (Variant::List(base_list), Variant::List(value_list)) => { - let mut ans: Vec = Vec::with_capacity(newsize as usize); - - let base_list = base_list.iter().collect::>(); - ans.extend_from_slice(&base_list[..from - 1]); - ans.extend(value_list.iter()); - ans.extend_from_slice(&base_list[to..]); - v_listv(ans) - } - _ => unreachable!(), - }; - - Ok(ans) - } -} diff --git a/crates/web-host/src/host/mod.rs b/crates/web-host/src/host/mod.rs index d1d86331..ff999ff9 100644 --- a/crates/web-host/src/host/mod.rs +++ b/crates/web-host/src/host/mod.rs @@ -15,8 +15,7 @@ pub mod web_host; mod ws_connection; -use moor_values::var::Var; -use moor_values::var::Variant; +use moor_values::{Var, Variant}; use serde::Serialize; use serde_derive::Deserialize; use serde_json::{json, Number}; @@ -58,12 +57,8 @@ pub fn var_as_json(v: &Var) -> serde_json::Value { } serde_json::Value::Array(v) } - Variant::Map(m) => { - let mut v = serde_json::Map::new(); - for (k, e) in m.iter() { - v.insert(k.to_string(), var_as_json(e)); - } - serde_json::Value::Object(v) + Variant::Map(_m) => { + unimplemented!("Maps are not supported in JSON serialization"); } } } diff --git a/crates/web-host/src/host/web_host.rs b/crates/web-host/src/host/web_host.rs index edcc562c..f95fde7d 100644 --- a/crates/web-host/src/host/web_host.rs +++ b/crates/web-host/src/host/web_host.rs @@ -21,7 +21,7 @@ use axum::response::{IntoResponse, Response}; use axum::{Form, Json}; use eyre::eyre; -use moor_values::var::Objid; +use moor_values::Objid; use rpc_async_client::rpc_client::RpcSendClient; use rpc_common::AuthToken; use rpc_common::RpcRequest::{Attach, ConnectionEstablish}; diff --git a/crates/web-host/src/host/ws_connection.rs b/crates/web-host/src/host/ws_connection.rs index 3a52c1a8..a3d8a596 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::var::{Objid, Var}; +use moor_values::{Objid, Var}; use rpc_async_client::pubsub_client::broadcast_recv; use rpc_async_client::pubsub_client::events_recv; use rpc_async_client::rpc_client::RpcSendClient;