From b34d5fe3a18d0769a3f09abd21ae884d8086b9a4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 26 May 2020 14:14:03 +0800 Subject: [PATCH] Complete ImmutableString. --- README.md | 7 +- RELEASES.md | 10 +- src/any.rs | 22 +-- src/engine.rs | 1 + src/fn_native.rs | 13 +- src/fn_register.rs | 2 +- src/lib.rs | 24 +-- src/packages/arithmetic.rs | 163 ++++++--------------- src/packages/logic.rs | 41 ------ src/packages/map_basic.rs | 5 +- src/packages/mod.rs | 2 +- src/packages/string_basic.rs | 62 +------- src/packages/string_more.rs | 19 ++- src/parser.rs | 6 +- src/utils.rs | 276 ++++++++++++++++++++++++++++++++++- 15 files changed, 379 insertions(+), 274 deletions(-) diff --git a/README.md b/README.md index a2720f62..8120cb6c 100644 --- a/README.md +++ b/README.md @@ -514,9 +514,9 @@ This is useful on some 32-bit targets where using 64-bit integers incur a perfor If no floating-point is needed or supported, use the [`no_float`] feature to remove it. -Strings in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type -is implemented as an `Rc`- or `Arc`-wrapped `String`. Any modification done to a Rhai string will cause the string to be cloned -and the modifications made to the copy. +[Strings] in Rhai are _immutable_, meaning that they can be shared but not modified. In actual, the `ImmutableString` type +is an alias to `Rc` or `Arc` (depending on the [`sync`] feature). +Any modification done to a Rhai string will cause the string to be cloned and the modifications made to the copy. The `to_string` function converts a standard type into a [string] for display purposes. @@ -612,6 +612,7 @@ The following conversion traits are implemented for `Dynamic`: * `From` (`i32` if [`only_i32`]) * `From` (if not [`no_float`]) * `From` +* `From` * `From` * `From` * `From>` (into an [array]) diff --git a/RELEASES.md b/RELEASES.md index d5251d9c..56e04b6b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -4,8 +4,8 @@ Rhai Release Notes Version 0.14.2 ============== -Regression ----------- +Regression fix +-------------- * Do not optimize script with `eval_expression` - it is assumed to be one-off and short. @@ -18,6 +18,10 @@ Breaking changes * Default maximum limit on levels of nested function calls is fine-tuned and set to a different value. * Some operator functions are now built in (see _Speed enhancements_ below), so they are available even under `Engine::new_raw`. +* Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. + This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters + should switch to `rhai::ImmutableString` (which is either `Rc` or `Arc` depending on + whether the `sync` feature is used). New features ------------ @@ -35,6 +39,8 @@ Speed enhancements significant speed-up. * Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` (and therefore the `CorePackage`) because they are now always available, even under `Engine::new_raw`. +* Operator-assignment statements (e.g. `+=`) are now handled directly and much faster. +* Strings are now _immutable_ and use the `rhai::ImmutableString` type, eliminating large amounts of cloning. Version 0.14.1 diff --git a/src/any.rs b/src/any.rs index 5a72e957..3bcbb049 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,5 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::fn_native::shared_unwrap; use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; @@ -21,9 +20,7 @@ use crate::stdlib::{ boxed::Box, collections::HashMap, fmt, - rc::Rc, string::String, - sync::Arc, vec::Vec, }; @@ -417,13 +414,10 @@ impl Dynamic { match self.0 { Union::Unit(value) => unsafe_try_cast(value), Union::Bool(value) => unsafe_try_cast(value), - Union::Str(value) => { - if type_id == TypeId::of::() { - unsafe_try_cast(value) - } else { - unsafe_try_cast((*value).clone()) - } + Union::Str(value) if type_id == TypeId::of::() => { + unsafe_try_cast(value) } + Union::Str(value) => unsafe_try_cast(value.into_owned()), Union::Char(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value), #[cfg(not(feature = "no_float"))] @@ -464,12 +458,10 @@ impl Dynamic { match self.0 { Union::Unit(value) => unsafe_try_cast(value).unwrap(), Union::Bool(value) => unsafe_try_cast(value).unwrap(), - Union::Str(value) => if type_id == TypeId::of::() { - unsafe_try_cast(value) - } else { - unsafe_try_cast((*value).clone()) + Union::Str(value) if type_id == TypeId::of::() => { + unsafe_try_cast(value).unwrap() } - .unwrap(), + Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(), Union::Char(value) => unsafe_try_cast(value).unwrap(), Union::Int(value) => unsafe_try_cast(value).unwrap(), #[cfg(not(feature = "no_float"))] @@ -588,7 +580,7 @@ impl Dynamic { /// Returns the name of the actual type if the cast fails. pub fn take_string(self) -> Result { match self.0 { - Union::Str(s) => Ok(shared_unwrap(s).unwrap_or_else(|s| (*s).clone())), + Union::Str(s) => Ok(s.into_owned()), _ => Err(self.type_name()), } } diff --git a/src/engine.rs b/src/engine.rs index 40930d6f..54907c0b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2055,6 +2055,7 @@ fn run_builtin_binary_op( let y = y.downcast_ref::().unwrap(); match op { + "+" => return Ok(Some((x + y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), ">" => return Ok(Some((x > y).into())), diff --git a/src/fn_native.rs b/src/fn_native.rs index 1975a3c5..6c43adc6 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -19,6 +19,8 @@ pub type Shared = Rc; #[cfg(feature = "sync")] pub type Shared = Arc; +/// Consume a `Shared` resource and return a mutable reference to the wrapped value. +/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. pub fn shared_make_mut(value: &mut Shared) -> &mut T { #[cfg(not(feature = "sync"))] { @@ -30,14 +32,19 @@ pub fn shared_make_mut(value: &mut Shared) -> &mut T { } } -pub fn shared_unwrap(value: Shared) -> Result> { +/// Consume a `Shared` resource, assuming that it is unique (i.e. not shared). +/// +/// # Panics +/// +/// Panics if the resource is shared (i.e. has other outstanding references). +pub fn shared_take(value: Shared) -> T { #[cfg(not(feature = "sync"))] { - Rc::try_unwrap(value) + Rc::try_unwrap(value).map_err(|_| ()).unwrap() } #[cfg(feature = "sync")] { - Arc::try_unwrap(value) + Arc::try_unwrap(value).map_err(|_| ()).unwrap() } } diff --git a/src/fn_register.rs b/src/fn_register.rs index 1cfe4506..cb208d12 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -8,7 +8,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs}; use crate::parser::FnAccess; use crate::result::EvalAltResult; -use crate::stdlib::{any::TypeId, boxed::Box, mem, string::ToString}; +use crate::stdlib::{any::TypeId, boxed::Box, mem}; /// Trait to register custom functions with the `Engine`. pub trait RegisterFn { diff --git a/src/lib.rs b/src/lib.rs index 454e3d13..106e83d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,18 +49,18 @@ //! //! ## Optional features //! -//! | Feature | Description | -//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | -//! | `no_function` | Disable script-defined functions if not needed. | -//! | `no_index` | Disable arrays and indexing features if not needed. | -//! | `no_object` | Disable support for custom types and objects. | -//! | `no_float` | Disable floating-point numbers and math if not needed. | -//! | `no_optimize` | Disable the script optimizer. | -//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | -//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | -//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | -//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | +//! | Feature | Description | +//! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------| +//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | +//! | `no_function` | Disable script-defined functions if not needed. | +//! | `no_index` | Disable arrays and indexing features if not needed. | +//! | `no_object` | Disable support for custom types and objects. | +//! | `no_float` | Disable floating-point numbers and math if not needed. | +//! | `no_optimize` | Disable the script optimizer. | +//! | `only_i32` | Set the system integer type to `i32` and disable all other integer types. `INT` is set to `i32`. | +//! | `only_i64` | Set the system integer type to `i64` and disable all other integer types. `INT` is set to `i64`. | +//! | `no_std` | Build for `no-std`. Notice that additional dependencies will be pulled in to replace `std` features. | +//! | `sync` | Restrict all values types to those that are `Send + Sync`. Under this feature, `Engine`, `Scope` and `AST` are all `Send + Sync`. | //! //! [Check out the README on GitHub for details on the Rhai language!](https://github.com/jonathandturner/rhai) diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 8f7a0b5b..ead54f8c 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -269,141 +269,69 @@ macro_rules! reg_op { } def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { - // Checked basic arithmetic - #[cfg(not(feature = "unchecked"))] + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] { - // reg_op!(lib, "+", add, INT); - // reg_op!(lib, "-", sub, INT); - // reg_op!(lib, "*", mul, INT); - // reg_op!(lib, "/", div, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] + #[cfg(not(feature = "unchecked"))] { - // reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + // Checked basic arithmetic reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "-", sub, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "/", div, i8, u8, i16, u16, i32, u32, u64, i128, u128); - } - } - - // Unchecked basic arithmetic - #[cfg(feature = "unchecked")] - { - // reg_op!(lib, "+", add_u, INT); - // reg_op!(lib, "-", sub_u, INT); - // reg_op!(lib, "*", mul_u, INT); - // reg_op!(lib, "/", div_u, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - // reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); - reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); - reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); - reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); - } - } - - // Basic arithmetic for floating-point - no need to check - #[cfg(not(feature = "no_float"))] - { - // reg_op!(lib, "+", add_u, f32, f64); - // reg_op!(lib, "-", sub_u, f32, f64); - // reg_op!(lib, "*", mul_u, f32, f64); - // reg_op!(lib, "/", div_u, f32, f64); - reg_op!(lib, "+", add_u, f32); - reg_op!(lib, "-", sub_u, f32); - reg_op!(lib, "*", mul_u, f32); - reg_op!(lib, "/", div_u, f32); - } - - // Bit operations - // reg_op!(lib, "|", binary_or, INT); - // reg_op!(lib, "&", binary_and, INT); - // reg_op!(lib, "^", binary_xor, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - // reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128); - reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128); - reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128); - } - - // Checked bit shifts - #[cfg(not(feature = "unchecked"))] - { - // reg_op!(lib, "<<", shl, INT); - // reg_op!(lib, ">>", shr, INT); - // reg_op!(lib, "%", modulo, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - // reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + // Checked bit shifts reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "%", modulo, i8, u8, i16, u16, i32, u32, u64, i128, u128); } - } - // Unchecked bit shifts - #[cfg(feature = "unchecked")] - { - // reg_op!(lib, "<<", shl_u, INT, INT); - // reg_op!(lib, ">>", shr_u, INT, INT); - // reg_op!(lib, "%", modulo_u, INT); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] + #[cfg(feature = "unchecked")] { - // reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + // Unchecked basic arithmetic + reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); + reg_op!(lib, "-", sub_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); + reg_op!(lib, "*", mul_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); + reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); + // Unchecked bit shifts reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">>", shr_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128); } } - // Checked power - #[cfg(not(feature = "unchecked"))] - { - // lib.set_fn_2("~", pow_i_i); - - #[cfg(not(feature = "no_float"))] - lib.set_fn_2("~", pow_f_i); - } - - // Unchecked power - #[cfg(feature = "unchecked")] - { - // lib.set_fn_2("~", pow_i_i_u); - - #[cfg(not(feature = "no_float"))] - lib.set_fn_2("~", pow_f_i_u); - } - - // Floating-point modulo and power + // Basic arithmetic for floating-point - no need to check #[cfg(not(feature = "no_float"))] { - // reg_op!(lib, "%", modulo_u, f32, f64); + reg_op!(lib, "+", add_u, f32); + reg_op!(lib, "-", sub_u, f32); + reg_op!(lib, "*", mul_u, f32); + reg_op!(lib, "/", div_u, f32); + } + + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + reg_op!(lib, "|", binary_or, i8, u8, i16, u16, i32, u32, u64, i128, u128); + reg_op!(lib, "&", binary_and, i8, u8, i16, u16, i32, u32, u64, i128, u128); + reg_op!(lib, "^", binary_xor, i8, u8, i16, u16, i32, u32, u64, i128, u128); + } + + #[cfg(not(feature = "no_float"))] + { + // Checked power + #[cfg(not(feature = "unchecked"))] + lib.set_fn_2("~", pow_f_i); + + // Unchecked power + #[cfg(feature = "unchecked")] + lib.set_fn_2("~", pow_f_i_u); + + // Floating-point modulo and power reg_op!(lib, "%", modulo_u, f32); - // lib.set_fn_2("~", pow_f_f); + + // Floating-point unary + reg_unary!(lib, "-", neg_u, f32, f64); + reg_unary!(lib, "abs", abs_u, f32, f64); } // Checked unary @@ -433,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); } } - - // Floating-point unary - #[cfg(not(feature = "no_float"))] - { - reg_unary!(lib, "-", neg_u, f32, f64); - reg_unary!(lib, "abs", abs_u, f32, f64); - } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 50d74466..41ca035f 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,6 +1,5 @@ use crate::def_package; use crate::module::FuncReturn; -use crate::parser::{ImmutableString, INT}; // Comparison operators pub fn lt(x: T, y: T) -> FuncReturn { @@ -23,12 +22,6 @@ pub fn ne(x: T, y: T) -> FuncReturn { } // Logic operators -fn and(x: bool, y: bool) -> FuncReturn { - Ok(x && y) -} -fn or(x: bool, y: bool) -> FuncReturn { - Ok(x || y) -} fn not(x: bool) -> FuncReturn { Ok(!x) } @@ -40,30 +33,9 @@ macro_rules! reg_op { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - // reg_op!(lib, "<", lt, INT, char); - // reg_op!(lib, "<=", lte, INT, char); - // reg_op!(lib, ">", gt, INT, char); - // reg_op!(lib, ">=", gte, INT, char); - // reg_op!(lib, "==", eq, INT, char, bool, ()); - // reg_op!(lib, "!=", ne, INT, char, bool, ()); - - // Special versions for strings - at least avoid copying the first string - // lib.set_fn_2("<", |x: ImmutableString, y: ImmutableString| Ok(*x < y)); - // lib.set_fn_2("<=", |x: ImmutableString, y: ImmutableString| Ok(*x <= y)); - // lib.set_fn_2(">", |x: ImmutableString, y: ImmutableString| Ok(*x > y)); - // lib.set_fn_2(">=", |x: ImmutableString, y: ImmutableString| Ok(*x >= y)); - // lib.set_fn_2("==", |x: ImmutableString, y: ImmutableString| Ok(*x == y)); - // lib.set_fn_2("!=", |x: ImmutableString, y: ImmutableString| Ok(*x != y)); - #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - // reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); - // reg_op!(lib, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); reg_op!(lib, "<", lt, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128); reg_op!(lib, ">", gt, i8, u8, i16, u16, i32, u32, u64, i128, u128); @@ -74,12 +46,6 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { #[cfg(not(feature = "no_float"))] { - // reg_op!(lib, "<", lt, f32, f64); - // reg_op!(lib, "<=", lte, f32, f64); - // reg_op!(lib, ">", gt, f32, f64); - // reg_op!(lib, ">=", gte, f32, f64); - // reg_op!(lib, "==", eq, f32, f64); - // reg_op!(lib, "!=", ne, f32, f64); reg_op!(lib, "<", lt, f32); reg_op!(lib, "<=", lte, f32); reg_op!(lib, ">", gt, f32); @@ -88,12 +54,5 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { reg_op!(lib, "!=", ne, f32); } - // `&&` and `||` are treated specially as they short-circuit. - // They are implemented as special `Expr` instances, not function calls. - //reg_op!(lib, "||", or, bool); - //reg_op!(lib, "&&", and, bool); - - // lib.set_fn_2("|", or); - // lib.set_fn_2("&", and); lib.set_fn_1("!", not); }); diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 6f7fb0ef..9be0fb42 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -6,10 +6,7 @@ use crate::engine::Map; use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; -use crate::stdlib::{ - string::{String, ToString}, - vec::Vec, -}; +use crate::stdlib::{string::ToString, vec::Vec}; fn map_get_keys(map: &mut Map) -> FuncReturn> { Ok(map.iter().map(|(k, _)| k.to_string().into()).collect()) diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ddd6a30d..3b89b9f4 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -4,7 +4,7 @@ use crate::fn_native::{CallableFunction, IteratorFn, Shared}; use crate::module::Module; use crate::utils::StaticVec; -use crate::stdlib::{any::TypeId, boxed::Box, collections::HashMap, rc::Rc, sync::Arc, vec::Vec}; +use crate::stdlib::any::TypeId; pub(crate) mod arithmetic; mod array_basic; diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index f4275467..ad51e02c 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,6 +1,5 @@ use crate::def_package; use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; -use crate::fn_native::shared_make_mut; use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; @@ -13,7 +12,7 @@ use crate::engine::Map; use crate::stdlib::{ fmt::{Debug, Display}, format, - string::{String, ToString}, + string::ToString, }; // Register print and debug @@ -79,58 +78,9 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin lib.set_fn_1_mut(KEYWORD_DEBUG, format_map); } - lib.set_fn_2( - "+", - |s: ImmutableString, ch: char| { - if s.is_empty() { - return Ok(ch.to_string().into()); - } - - let mut s = (*s).clone(); - s.push(ch); - Ok(s) - }, - ); - lib.set_fn_2( - "+", - |s:ImmutableString, s2:ImmutableString| { - if s.is_empty() { - return Ok(s2); - } else if s2.is_empty() { - return Ok(s); - } - - let mut s = (*s).clone(); - s.push_str(s2.as_str()); - Ok(s.into()) - }, - ); - lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { - shared_make_mut(s).push(ch); - Ok(()) - }); - lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { - shared_make_mut(s).push(ch); - Ok(()) - }); - lib.set_fn_2_mut( - "+=", - |s: &mut ImmutableString, s2: ImmutableString| { - if !s2.is_empty() { - shared_make_mut(s).push_str(s2.as_str()); - } - - Ok(()) - } - ); - lib.set_fn_2_mut( - "append", - |s: &mut ImmutableString, s2: ImmutableString| { - if !s2.is_empty() { - shared_make_mut(s).push_str(s2.as_str()); - } - - Ok(()) - } - ); + lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch)); + lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); + lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) }); + lib.set_fn_2_mut("+=", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) }); + lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) }); }); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 666f9061..c3c22c31 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,5 +1,4 @@ use crate::def_package; -use crate::fn_native::shared_make_mut; use crate::module::FuncReturn; use crate::parser::{ImmutableString, INT}; use crate::utils::StaticVec; @@ -48,12 +47,12 @@ fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn FuncReturn<()> { let offset = if s.is_empty() || len <= 0 { - shared_make_mut(s).clear(); + s.make_mut().clear(); return Ok(()); } else if start < 0 { 0 } else if (start as usize) >= s.chars().count() { - shared_make_mut(s).clear(); + s.make_mut().clear(); return Ok(()); } else { start as usize @@ -67,7 +66,7 @@ fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> len as usize }; - let copy = shared_make_mut(s); + let copy = s.make_mut(); copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); @@ -166,17 +165,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str }, ); lib.set_fn_1_mut("clear", |s: &mut ImmutableString| { - shared_make_mut(s).clear(); + s.make_mut().clear(); Ok(()) }); lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { - shared_make_mut(s).push(ch); + s.make_mut().push(ch); Ok(()) }); lib.set_fn_2_mut( "append", |s: &mut ImmutableString, add: ImmutableString| { - shared_make_mut(s).push_str(add.as_str()); + s.make_mut().push_str(add.as_str()); Ok(()) } ); @@ -198,11 +197,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str |s: &mut ImmutableString, len: INT| { if len > 0 { let chars: StaticVec<_> = s.chars().collect(); - let copy = shared_make_mut(s); + let copy = s.make_mut(); copy.clear(); copy.extend(chars.into_iter().take(len as usize)); } else { - shared_make_mut(s).clear(); + s.make_mut().clear(); } Ok(()) }, @@ -210,7 +209,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str lib.set_fn_3_mut( "pad", |s: &mut ImmutableString, len: INT, ch: char| { - let copy = shared_make_mut(s); + let copy = s.make_mut(); for _ in 0..copy.chars().count() - len as usize { copy.push(ch); } diff --git a/src/parser.rs b/src/parser.rs index bbb5c808..02e83c97 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,7 +4,6 @@ use crate::any::{Dynamic, Union}; use crate::calc_fn_hash; use crate::engine::{make_getter, make_setter, Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; -use crate::fn_native::Shared; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::token::{Position, Token, TokenIterator}; @@ -49,11 +48,10 @@ pub type INT = i32; #[cfg(not(feature = "no_float"))] pub type FLOAT = f64; -/// The system immutable string type. -pub type ImmutableString = Shared; - type PERR = ParseErrorType; +pub use crate::utils::ImmutableString; + /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. diff --git a/src/utils.rs b/src/utils.rs index 126f6809..f4bca92e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,14 +4,18 @@ //! //! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. +use crate::fn_native::{shared_make_mut, shared_take, Shared}; + use crate::stdlib::{ any::TypeId, + borrow::Borrow, fmt, hash::{Hash, Hasher}, iter::FromIterator, mem, mem::MaybeUninit, - ops::{Drop, Index, IndexMut}, + ops::{Add, AddAssign, Deref, Drop, Index, IndexMut}, + str::FromStr, vec::Vec, }; @@ -560,3 +564,273 @@ impl From> for StaticVec { arr } } + +/// The system immutable string type. +/// +/// An `ImmutableString` wraps an `Rc` (or `Arc` under the `sync` feature) +/// so that it can be simply shared and not cloned. +/// +/// # Examples +/// +/// ``` +/// use rhai::ImmutableString; +/// +/// let s1: ImmutableString = "hello".into(); +/// +/// // No actual cloning of the string is involved below. +/// let s2 = s1.clone(); +/// let s3 = s2.clone(); +/// +/// assert_eq!(s1, s2); +/// +/// // Clones the underlying string (because it is already shared) and extracts it. +/// let mut s: String = s1.into_owned(); +/// +/// // Changing the clone has no impact on the previously shared version. +/// s.push_str(", world!"); +/// +/// // The old version still exists. +/// assert_eq!(s2, s3); +/// assert_eq!(s2.as_str(), "hello"); +/// +/// // Not equals! +/// assert_ne!(s2.as_str(), s.as_str()); +/// assert_eq!(s, "hello, world!"); +/// ``` +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] +pub struct ImmutableString(Shared); + +impl Deref for ImmutableString { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for ImmutableString { + fn as_ref(&self) -> &String { + &self.0 + } +} + +impl Borrow for ImmutableString { + fn borrow(&self) -> &str { + self.0.as_str() + } +} + +impl From<&str> for ImmutableString { + fn from(value: &str) -> Self { + Self(value.to_string().into()) + } +} +impl From for ImmutableString { + fn from(value: String) -> Self { + Self(value.into()) + } +} + +impl From> for ImmutableString { + fn from(value: Box) -> Self { + Self(value.into()) + } +} + +impl From for String { + fn from(value: ImmutableString) -> Self { + value.into_owned() + } +} + +impl FromStr for ImmutableString { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_string().into())) + } +} + +impl FromIterator for ImmutableString { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect::().into()) + } +} + +impl<'a> FromIterator<&'a char> for ImmutableString { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().cloned().collect::().into()) + } +} + +impl<'a> FromIterator<&'a str> for ImmutableString { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect::().into()) + } +} + +impl<'a> FromIterator for ImmutableString { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect::().into()) + } +} + +impl fmt::Display for ImmutableString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.0.as_str(), f) + } +} + +impl fmt::Debug for ImmutableString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.0.as_str(), f) + } +} + +impl Add for ImmutableString { + type Output = Self; + + fn add(mut self, rhs: Self) -> Self::Output { + if rhs.is_empty() { + self + } else if self.is_empty() { + rhs + } else { + self.make_mut().push_str(rhs.0.as_str()); + self + } + } +} + +impl Add for &ImmutableString { + type Output = ImmutableString; + + fn add(self, rhs: Self) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else if self.is_empty() { + rhs.clone() + } else { + let mut s = self.clone(); + s.make_mut().push_str(rhs.0.as_str()); + s + } + } +} + +impl AddAssign<&ImmutableString> for ImmutableString { + fn add_assign(&mut self, rhs: &ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0.clone(); + } else { + self.make_mut().push_str(rhs.0.as_str()); + } + } + } +} + +impl Add<&str> for ImmutableString { + type Output = Self; + + fn add(mut self, rhs: &str) -> Self::Output { + if rhs.is_empty() { + self + } else { + self.make_mut().push_str(rhs); + self + } + } +} + +impl Add<&str> for &ImmutableString { + type Output = ImmutableString; + + fn add(self, rhs: &str) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else { + let mut s = self.clone(); + s.make_mut().push_str(rhs); + s + } + } +} + +impl AddAssign<&str> for ImmutableString { + fn add_assign(&mut self, rhs: &str) { + if !rhs.is_empty() { + self.make_mut().push_str(rhs); + } + } +} + +impl Add for ImmutableString { + type Output = Self; + + fn add(mut self, rhs: String) -> Self::Output { + if rhs.is_empty() { + self + } else if self.is_empty() { + rhs.into() + } else { + self.make_mut().push_str(&rhs); + self + } + } +} + +impl Add for &ImmutableString { + type Output = ImmutableString; + + fn add(self, rhs: String) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else if self.is_empty() { + rhs.into() + } else { + let mut s = self.clone(); + s.make_mut().push_str(&rhs); + s + } + } +} + +impl Add for ImmutableString { + type Output = Self; + + fn add(mut self, rhs: char) -> Self::Output { + self.make_mut().push(rhs); + self + } +} + +impl Add for &ImmutableString { + type Output = ImmutableString; + + fn add(self, rhs: char) -> Self::Output { + let mut s = self.clone(); + s.make_mut().push(rhs); + s + } +} + +impl AddAssign for ImmutableString { + fn add_assign(&mut self, rhs: char) { + self.make_mut().push(rhs); + } +} + +impl ImmutableString { + /// Consume the `ImmutableString` and convert it into a `String`. + /// If there are other references to the same string, a cloned copy is returned. + pub fn into_owned(mut self) -> String { + self.make_mut(); // Make sure it is unique reference + shared_take(self.0) // Should succeed + } + /// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references). + /// Then return a mutable reference to the `String`. + pub fn make_mut(&mut self) -> &mut String { + shared_make_mut(&mut self.0) + } +}